feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* UPDATE 액션 노드 속성 편집 (개선 버전)
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { useEffect, useState } from "react";
|
2025-10-02 17:51:15 +09:00
|
|
|
|
import { Plus, Trash2, Check, ChevronsUpDown, ArrowRight, Database, Globe, Link2 } from "lucide-react";
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
import { Label } from "@/components/ui/label";
|
|
|
|
|
|
import { Input } from "@/components/ui/input";
|
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
|
|
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
|
|
|
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
|
|
|
|
|
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
|
|
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
|
|
import { useFlowEditorStore } from "@/lib/stores/flowEditorStore";
|
|
|
|
|
|
import { tableTypeApi } from "@/lib/api/screen";
|
2025-10-02 17:51:15 +09:00
|
|
|
|
import { getTestedExternalConnections, getExternalTables, getExternalColumns } from "@/lib/api/nodeExternalConnections";
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
import type { UpdateActionNodeData } from "@/types/node-editor";
|
2025-10-02 17:51:15 +09:00
|
|
|
|
import type { ExternalConnection, ExternalTable, ExternalColumn } from "@/lib/api/nodeExternalConnections";
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
|
|
|
|
|
|
interface UpdateActionPropertiesProps {
|
|
|
|
|
|
nodeId: string;
|
|
|
|
|
|
data: UpdateActionNodeData;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface TableOption {
|
|
|
|
|
|
tableName: string;
|
|
|
|
|
|
displayName: string;
|
|
|
|
|
|
description: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface ColumnInfo {
|
|
|
|
|
|
columnName: string;
|
|
|
|
|
|
columnLabel?: string;
|
|
|
|
|
|
dataType: string;
|
|
|
|
|
|
isNullable: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const OPERATORS = [
|
|
|
|
|
|
{ value: "EQUALS", label: "=" },
|
|
|
|
|
|
{ value: "NOT_EQUALS", label: "≠" },
|
|
|
|
|
|
{ value: "GREATER_THAN", label: ">" },
|
|
|
|
|
|
{ value: "LESS_THAN", label: "<" },
|
|
|
|
|
|
{ value: "GREATER_THAN_OR_EQUAL", label: "≥" },
|
|
|
|
|
|
{ value: "LESS_THAN_OR_EQUAL", label: "≤" },
|
|
|
|
|
|
{ value: "LIKE", label: "LIKE" },
|
|
|
|
|
|
{ value: "NOT_LIKE", label: "NOT LIKE" },
|
|
|
|
|
|
{ value: "IN", label: "IN" },
|
|
|
|
|
|
{ value: "NOT_IN", label: "NOT IN" },
|
|
|
|
|
|
{ value: "IS_NULL", label: "IS NULL" },
|
|
|
|
|
|
{ value: "IS_NOT_NULL", label: "IS NOT NULL" },
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
|
|
export function UpdateActionProperties({ nodeId, data }: UpdateActionPropertiesProps) {
|
2025-10-02 17:51:15 +09:00
|
|
|
|
const { updateNode, nodes, edges, getExternalConnectionsCache } = useFlowEditorStore();
|
|
|
|
|
|
|
|
|
|
|
|
// 🔥 타겟 타입 상태
|
|
|
|
|
|
const [targetType, setTargetType] = useState<"internal" | "external" | "api">(data.targetType || "internal");
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
|
|
|
|
|
|
const [displayName, setDisplayName] = useState(data.displayName || data.targetTable);
|
|
|
|
|
|
const [targetTable, setTargetTable] = useState(data.targetTable);
|
|
|
|
|
|
const [fieldMappings, setFieldMappings] = useState(data.fieldMappings || []);
|
|
|
|
|
|
const [whereConditions, setWhereConditions] = useState(data.whereConditions || []);
|
|
|
|
|
|
const [batchSize, setBatchSize] = useState(data.options?.batchSize?.toString() || "");
|
|
|
|
|
|
const [ignoreErrors, setIgnoreErrors] = useState(data.options?.ignoreErrors || false);
|
|
|
|
|
|
|
2025-10-02 17:51:15 +09:00
|
|
|
|
// 내부 DB 테이블 관련 상태
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
const [tables, setTables] = useState<TableOption[]>([]);
|
|
|
|
|
|
const [tablesLoading, setTablesLoading] = useState(false);
|
|
|
|
|
|
const [tablesOpen, setTablesOpen] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 컬럼 관련 상태
|
|
|
|
|
|
const [targetColumns, setTargetColumns] = useState<ColumnInfo[]>([]);
|
|
|
|
|
|
const [columnsLoading, setColumnsLoading] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 소스 필드 목록 (연결된 입력 노드에서 가져오기)
|
|
|
|
|
|
const [sourceFields, setSourceFields] = useState<Array<{ name: string; label?: string }>>([]);
|
2025-10-13 12:00:41 +09:00
|
|
|
|
// REST API 소스 노드 연결 여부
|
|
|
|
|
|
const [hasRestAPISource, setHasRestAPISource] = useState(false);
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
|
2025-10-02 17:51:15 +09:00
|
|
|
|
// 🔥 외부 DB 관련 상태
|
|
|
|
|
|
const [externalConnections, setExternalConnections] = useState<ExternalConnection[]>([]);
|
|
|
|
|
|
const [externalConnectionsLoading, setExternalConnectionsLoading] = useState(false);
|
|
|
|
|
|
const [selectedExternalConnectionId, setSelectedExternalConnectionId] = useState<number | undefined>(
|
|
|
|
|
|
data.externalConnectionId,
|
|
|
|
|
|
);
|
|
|
|
|
|
const [externalTables, setExternalTables] = useState<ExternalTable[]>([]);
|
|
|
|
|
|
const [externalTablesLoading, setExternalTablesLoading] = useState(false);
|
|
|
|
|
|
const [externalTargetTable, setExternalTargetTable] = useState(data.externalTargetTable);
|
|
|
|
|
|
const [externalColumns, setExternalColumns] = useState<ExternalColumn[]>([]);
|
|
|
|
|
|
const [externalColumnsLoading, setExternalColumnsLoading] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 🔥 REST API 관련 상태
|
|
|
|
|
|
const [apiEndpoint, setApiEndpoint] = useState(data.apiEndpoint || "");
|
|
|
|
|
|
const [apiMethod, setApiMethod] = useState<"PUT" | "PATCH">(data.apiMethod || "PUT");
|
|
|
|
|
|
const [apiAuthType, setApiAuthType] = useState<"none" | "basic" | "bearer" | "apikey">(data.apiAuthType || "none");
|
|
|
|
|
|
const [apiAuthConfig, setApiAuthConfig] = useState(data.apiAuthConfig || {});
|
|
|
|
|
|
const [apiHeaders, setApiHeaders] = useState<Record<string, string>>(data.apiHeaders || {});
|
|
|
|
|
|
const [apiBodyTemplate, setApiBodyTemplate] = useState(data.apiBodyTemplate || "");
|
|
|
|
|
|
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
// 데이터 변경 시 로컬 상태 업데이트
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
setDisplayName(data.displayName || data.targetTable);
|
|
|
|
|
|
setTargetTable(data.targetTable);
|
|
|
|
|
|
setFieldMappings(data.fieldMappings || []);
|
|
|
|
|
|
setWhereConditions(data.whereConditions || []);
|
|
|
|
|
|
setBatchSize(data.options?.batchSize?.toString() || "");
|
|
|
|
|
|
setIgnoreErrors(data.options?.ignoreErrors || false);
|
|
|
|
|
|
}, [data]);
|
|
|
|
|
|
|
2025-10-02 17:51:15 +09:00
|
|
|
|
// 내부 DB 테이블 목록 로딩
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
useEffect(() => {
|
2025-10-02 17:51:15 +09:00
|
|
|
|
if (targetType === "internal") {
|
|
|
|
|
|
loadTables();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [targetType]);
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
|
2025-10-02 17:51:15 +09:00
|
|
|
|
// 타겟 테이블 변경 시 컬럼 로딩 (내부 DB)
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
useEffect(() => {
|
2025-10-02 17:51:15 +09:00
|
|
|
|
if (targetType === "internal" && targetTable) {
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
loadColumns(targetTable);
|
|
|
|
|
|
}
|
2025-10-02 17:51:15 +09:00
|
|
|
|
}, [targetType, targetTable]);
|
|
|
|
|
|
|
|
|
|
|
|
// 🔥 외부 DB: 커넥션 목록 로딩
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (targetType === "external") {
|
|
|
|
|
|
loadExternalConnections();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [targetType]);
|
|
|
|
|
|
|
|
|
|
|
|
// 🔥 외부 DB: 테이블 목록 로딩
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (targetType === "external" && selectedExternalConnectionId) {
|
|
|
|
|
|
loadExternalTables(selectedExternalConnectionId);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [targetType, selectedExternalConnectionId]);
|
|
|
|
|
|
|
|
|
|
|
|
// 🔥 외부 DB: 컬럼 목록 로딩
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (targetType === "external" && selectedExternalConnectionId && externalTargetTable) {
|
|
|
|
|
|
loadExternalColumns(selectedExternalConnectionId, externalTargetTable);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [targetType, selectedExternalConnectionId, externalTargetTable]);
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
|
|
|
|
|
|
// 연결된 소스 노드에서 필드 가져오기 (재귀적으로 모든 상위 노드 탐색)
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const getAllSourceFields = (
|
|
|
|
|
|
targetNodeId: string,
|
|
|
|
|
|
visitedNodes = new Set<string>(),
|
2025-10-13 12:00:41 +09:00
|
|
|
|
): { fields: Array<{ name: string; label?: string }>; hasRestAPI: boolean } => {
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
if (visitedNodes.has(targetNodeId)) {
|
2025-10-13 12:00:41 +09:00
|
|
|
|
return { fields: [], hasRestAPI: false };
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
}
|
|
|
|
|
|
visitedNodes.add(targetNodeId);
|
|
|
|
|
|
|
|
|
|
|
|
const inputEdges = edges.filter((edge) => edge.target === targetNodeId);
|
|
|
|
|
|
const sourceNodeIds = inputEdges.map((edge) => edge.source);
|
|
|
|
|
|
const sourceNodes = nodes.filter((node) => sourceNodeIds.includes(node.id));
|
|
|
|
|
|
|
|
|
|
|
|
const fields: Array<{ name: string; label?: string }> = [];
|
2025-10-13 12:00:41 +09:00
|
|
|
|
let foundRestAPI = false;
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
|
|
|
|
|
|
sourceNodes.forEach((node) => {
|
2025-10-13 17:47:24 +09:00
|
|
|
|
// 1️⃣ 데이터 변환 노드: 변환된 필드 + 상위 노드의 원본 필드
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
if (node.type === "dataTransform") {
|
2025-10-13 12:00:41 +09:00
|
|
|
|
const upperResult = getAllSourceFields(node.id, visitedNodes);
|
|
|
|
|
|
const upperFields = upperResult.fields;
|
|
|
|
|
|
foundRestAPI = foundRestAPI || upperResult.hasRestAPI;
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
|
2025-10-13 12:00:41 +09:00
|
|
|
|
if ((node.data as any).transformations && Array.isArray((node.data as any).transformations)) {
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
const inPlaceFields = new Set<string>();
|
|
|
|
|
|
|
2025-10-13 12:00:41 +09:00
|
|
|
|
(node.data as any).transformations.forEach((transform: any) => {
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
const targetField = transform.targetField || transform.sourceField;
|
|
|
|
|
|
const isInPlace = !transform.targetField || transform.targetField === transform.sourceField;
|
|
|
|
|
|
|
|
|
|
|
|
if (isInPlace) {
|
|
|
|
|
|
inPlaceFields.add(transform.sourceField);
|
|
|
|
|
|
} else if (targetField) {
|
|
|
|
|
|
fields.push({
|
|
|
|
|
|
name: targetField,
|
|
|
|
|
|
label: transform.targetFieldLabel || targetField,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
upperFields.forEach((field) => {
|
|
|
|
|
|
fields.push(field);
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
fields.push(...upperFields);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-13 17:47:24 +09:00
|
|
|
|
// 2️⃣ REST API 소스 노드
|
2025-10-13 12:00:41 +09:00
|
|
|
|
else if (node.type === "restAPISource") {
|
|
|
|
|
|
foundRestAPI = true;
|
|
|
|
|
|
const responseFields = (node.data as any).responseFields;
|
|
|
|
|
|
if (responseFields && Array.isArray(responseFields)) {
|
|
|
|
|
|
responseFields.forEach((field: any) => {
|
|
|
|
|
|
const fieldName = field.name || field.fieldName;
|
|
|
|
|
|
const fieldLabel = field.label || field.displayName;
|
|
|
|
|
|
if (fieldName) {
|
|
|
|
|
|
fields.push({
|
|
|
|
|
|
name: fieldName,
|
|
|
|
|
|
label: fieldLabel,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-13 17:47:24 +09:00
|
|
|
|
// 3️⃣ 테이블/외부DB 소스 노드
|
|
|
|
|
|
else if (node.type === "tableSource" || node.type === "externalDBSource") {
|
|
|
|
|
|
const nodeFields = (node.data as any).fields || (node.data as any).outputFields;
|
|
|
|
|
|
|
|
|
|
|
|
if (nodeFields && Array.isArray(nodeFields)) {
|
|
|
|
|
|
nodeFields.forEach((field: any) => {
|
|
|
|
|
|
const fieldName = field.name || field.fieldName || field.column_name;
|
|
|
|
|
|
const fieldLabel = field.label || field.displayName || field.label_ko;
|
|
|
|
|
|
if (fieldName) {
|
|
|
|
|
|
fields.push({
|
|
|
|
|
|
name: fieldName,
|
|
|
|
|
|
label: fieldLabel,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
});
|
2025-10-13 17:47:24 +09:00
|
|
|
|
} else {
|
|
|
|
|
|
// 필드가 없으면 상위 노드로 계속 탐색
|
|
|
|
|
|
const upperResult = getAllSourceFields(node.id, visitedNodes);
|
|
|
|
|
|
fields.push(...upperResult.fields);
|
|
|
|
|
|
foundRestAPI = foundRestAPI || upperResult.hasRestAPI;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 4️⃣ 통과 노드 (조건, 기타 모든 노드): 상위 노드로 계속 탐색
|
|
|
|
|
|
else {
|
|
|
|
|
|
const upperResult = getAllSourceFields(node.id, visitedNodes);
|
|
|
|
|
|
fields.push(...upperResult.fields);
|
|
|
|
|
|
foundRestAPI = foundRestAPI || upperResult.hasRestAPI;
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-10-13 12:00:41 +09:00
|
|
|
|
return { fields, hasRestAPI: foundRestAPI };
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-13 12:00:41 +09:00
|
|
|
|
const result = getAllSourceFields(nodeId);
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
|
|
|
|
|
|
// 중복 제거
|
2025-10-13 12:00:41 +09:00
|
|
|
|
const uniqueFields = Array.from(new Map(result.fields.map((field) => [field.name, field])).values());
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
|
|
|
|
|
|
setSourceFields(uniqueFields);
|
2025-10-13 12:00:41 +09:00
|
|
|
|
setHasRestAPISource(result.hasRestAPI);
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
}, [nodeId, nodes, edges]);
|
|
|
|
|
|
|
|
|
|
|
|
const loadTables = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
setTablesLoading(true);
|
|
|
|
|
|
const tableList = await tableTypeApi.getTables();
|
|
|
|
|
|
console.log("🔍 UPDATE 노드 - 테이블 목록:", tableList);
|
|
|
|
|
|
|
|
|
|
|
|
const options: TableOption[] = tableList.map((table) => {
|
|
|
|
|
|
const label = (table as any).tableLabel || table.displayName || table.tableName || "알 수 없는 테이블";
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
tableName: table.tableName,
|
|
|
|
|
|
displayName: table.displayName || table.tableName,
|
|
|
|
|
|
description: table.description || "",
|
|
|
|
|
|
label,
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
console.log("✅ UPDATE 노드 - 테이블 옵션:", options);
|
|
|
|
|
|
setTables(options);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("❌ UPDATE 노드 - 테이블 목록 로딩 실패:", error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setTablesLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-02 17:51:15 +09:00
|
|
|
|
// 🔥 외부 DB 커넥션 목록 로딩
|
|
|
|
|
|
const loadExternalConnections = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
setExternalConnectionsLoading(true);
|
|
|
|
|
|
|
|
|
|
|
|
// 캐시 확인
|
|
|
|
|
|
const cached = getExternalConnectionsCache();
|
|
|
|
|
|
if (cached) {
|
|
|
|
|
|
setExternalConnections(cached);
|
|
|
|
|
|
setExternalConnectionsLoading(false);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const connections = await getTestedExternalConnections();
|
|
|
|
|
|
setExternalConnections(connections);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("외부 커넥션 목록 로딩 실패:", error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setExternalConnectionsLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 🔥 외부 DB 테이블 목록 로딩
|
|
|
|
|
|
const loadExternalTables = async (connectionId: number) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
setExternalTablesLoading(true);
|
|
|
|
|
|
const tables = await getExternalTables(connectionId);
|
|
|
|
|
|
setExternalTables(tables);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("외부 테이블 목록 로딩 실패:", error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setExternalTablesLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 🔥 외부 DB 컬럼 목록 로딩
|
|
|
|
|
|
const loadExternalColumns = async (connectionId: number, tableName: string) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
setExternalColumnsLoading(true);
|
|
|
|
|
|
const columns = await getExternalColumns(connectionId, tableName);
|
|
|
|
|
|
setExternalColumns(columns);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("외부 컬럼 목록 로딩 실패:", error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setExternalColumnsLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
const loadColumns = async (tableName: string) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
setColumnsLoading(true);
|
|
|
|
|
|
console.log(`🔍 UPDATE 노드 - 컬럼 조회 중: ${tableName}`);
|
|
|
|
|
|
|
|
|
|
|
|
const columns = await tableTypeApi.getColumns(tableName);
|
|
|
|
|
|
|
|
|
|
|
|
const columnInfo: ColumnInfo[] = columns.map((col: any) => ({
|
|
|
|
|
|
columnName: col.column_name || col.columnName,
|
|
|
|
|
|
columnLabel: col.label_ko || col.columnLabel,
|
|
|
|
|
|
dataType: col.data_type || col.dataType || "unknown",
|
|
|
|
|
|
isNullable: col.is_nullable === "YES" || col.isNullable === true,
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
setTargetColumns(columnInfo);
|
|
|
|
|
|
console.log(`✅ UPDATE 노드 - 컬럼 ${columnInfo.length}개 로딩 완료`);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("❌ UPDATE 노드 - 컬럼 목록 로딩 실패:", error);
|
|
|
|
|
|
setTargetColumns([]);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setColumnsLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleTableSelect = async (newTableName: string) => {
|
|
|
|
|
|
const selectedTable = tables.find((t) => t.tableName === newTableName);
|
|
|
|
|
|
const newDisplayName = selectedTable?.label || selectedTable?.displayName || newTableName;
|
|
|
|
|
|
|
|
|
|
|
|
setTargetTable(newTableName);
|
|
|
|
|
|
setDisplayName(newDisplayName);
|
|
|
|
|
|
|
|
|
|
|
|
await loadColumns(newTableName);
|
|
|
|
|
|
|
|
|
|
|
|
// 즉시 반영
|
|
|
|
|
|
updateNode(nodeId, {
|
|
|
|
|
|
displayName: newDisplayName,
|
|
|
|
|
|
targetTable: newTableName,
|
|
|
|
|
|
fieldMappings,
|
|
|
|
|
|
whereConditions,
|
|
|
|
|
|
options: {
|
|
|
|
|
|
batchSize: batchSize ? parseInt(batchSize) : undefined,
|
|
|
|
|
|
ignoreErrors,
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
setTablesOpen(false);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleAddMapping = () => {
|
|
|
|
|
|
setFieldMappings([
|
|
|
|
|
|
...fieldMappings,
|
|
|
|
|
|
{
|
|
|
|
|
|
sourceField: null,
|
|
|
|
|
|
targetField: "",
|
|
|
|
|
|
staticValue: undefined,
|
|
|
|
|
|
},
|
|
|
|
|
|
]);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleRemoveMapping = (index: number) => {
|
|
|
|
|
|
const newMappings = fieldMappings.filter((_, i) => i !== index);
|
|
|
|
|
|
setFieldMappings(newMappings);
|
|
|
|
|
|
|
|
|
|
|
|
// 즉시 반영
|
|
|
|
|
|
updateNode(nodeId, {
|
|
|
|
|
|
displayName,
|
|
|
|
|
|
targetTable,
|
|
|
|
|
|
fieldMappings: newMappings,
|
|
|
|
|
|
whereConditions,
|
|
|
|
|
|
options: {
|
|
|
|
|
|
batchSize: batchSize ? parseInt(batchSize) : undefined,
|
|
|
|
|
|
ignoreErrors,
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleMappingChange = (index: number, field: string, value: any) => {
|
|
|
|
|
|
const newMappings = [...fieldMappings];
|
|
|
|
|
|
|
|
|
|
|
|
// 필드 변경 시 라벨도 함께 저장
|
|
|
|
|
|
if (field === "sourceField") {
|
|
|
|
|
|
const sourceField = sourceFields.find((f) => f.name === value);
|
|
|
|
|
|
newMappings[index] = {
|
|
|
|
|
|
...newMappings[index],
|
|
|
|
|
|
sourceField: value,
|
|
|
|
|
|
sourceFieldLabel: sourceField?.label,
|
|
|
|
|
|
};
|
|
|
|
|
|
} else if (field === "targetField") {
|
|
|
|
|
|
const targetColumn = targetColumns.find((c) => c.columnName === value);
|
|
|
|
|
|
newMappings[index] = {
|
|
|
|
|
|
...newMappings[index],
|
|
|
|
|
|
targetField: value,
|
|
|
|
|
|
targetFieldLabel: targetColumn?.columnLabel,
|
|
|
|
|
|
};
|
|
|
|
|
|
} else {
|
|
|
|
|
|
newMappings[index] = { ...newMappings[index], [field]: value };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setFieldMappings(newMappings);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-02 17:51:15 +09:00
|
|
|
|
// 🔥 타겟 타입 변경 핸들러
|
|
|
|
|
|
const handleTargetTypeChange = (newType: "internal" | "external" | "api") => {
|
|
|
|
|
|
setTargetType(newType);
|
|
|
|
|
|
updateNode(nodeId, {
|
|
|
|
|
|
targetType: newType,
|
|
|
|
|
|
...(newType === "internal" && {
|
|
|
|
|
|
targetTable: data.targetTable,
|
|
|
|
|
|
targetConnection: data.targetConnection,
|
|
|
|
|
|
displayName: data.displayName,
|
|
|
|
|
|
}),
|
|
|
|
|
|
...(newType === "external" && {
|
|
|
|
|
|
externalConnectionId: data.externalConnectionId,
|
|
|
|
|
|
externalConnectionName: data.externalConnectionName,
|
|
|
|
|
|
externalDbType: data.externalDbType,
|
|
|
|
|
|
externalTargetTable: data.externalTargetTable,
|
|
|
|
|
|
externalTargetSchema: data.externalTargetSchema,
|
|
|
|
|
|
}),
|
|
|
|
|
|
...(newType === "api" && {
|
|
|
|
|
|
apiEndpoint: data.apiEndpoint,
|
|
|
|
|
|
apiMethod: data.apiMethod,
|
|
|
|
|
|
apiAuthType: data.apiAuthType,
|
|
|
|
|
|
apiAuthConfig: data.apiAuthConfig,
|
|
|
|
|
|
apiHeaders: data.apiHeaders,
|
|
|
|
|
|
apiBodyTemplate: data.apiBodyTemplate,
|
|
|
|
|
|
}),
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
const handleAddCondition = () => {
|
|
|
|
|
|
setWhereConditions([
|
|
|
|
|
|
...whereConditions,
|
|
|
|
|
|
{
|
|
|
|
|
|
field: "",
|
|
|
|
|
|
operator: "EQUALS",
|
|
|
|
|
|
staticValue: "",
|
|
|
|
|
|
},
|
|
|
|
|
|
]);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleRemoveCondition = (index: number) => {
|
|
|
|
|
|
const newConditions = whereConditions.filter((_, i) => i !== index);
|
|
|
|
|
|
setWhereConditions(newConditions);
|
|
|
|
|
|
|
|
|
|
|
|
// 즉시 반영
|
|
|
|
|
|
updateNode(nodeId, {
|
|
|
|
|
|
displayName,
|
|
|
|
|
|
targetTable,
|
|
|
|
|
|
fieldMappings,
|
|
|
|
|
|
whereConditions: newConditions,
|
|
|
|
|
|
options: {
|
|
|
|
|
|
batchSize: batchSize ? parseInt(batchSize) : undefined,
|
|
|
|
|
|
ignoreErrors,
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleConditionChange = (index: number, field: string, value: any) => {
|
|
|
|
|
|
const newConditions = [...whereConditions];
|
|
|
|
|
|
|
|
|
|
|
|
// 필드 변경 시 라벨도 함께 저장
|
|
|
|
|
|
if (field === "field") {
|
|
|
|
|
|
const targetColumn = targetColumns.find((c) => c.columnName === value);
|
|
|
|
|
|
newConditions[index] = {
|
|
|
|
|
|
...newConditions[index],
|
|
|
|
|
|
field: value,
|
|
|
|
|
|
fieldLabel: targetColumn?.columnLabel,
|
|
|
|
|
|
};
|
|
|
|
|
|
} else if (field === "sourceField") {
|
|
|
|
|
|
const sourceField = sourceFields.find((f) => f.name === value);
|
|
|
|
|
|
newConditions[index] = {
|
|
|
|
|
|
...newConditions[index],
|
|
|
|
|
|
sourceField: value,
|
|
|
|
|
|
sourceFieldLabel: sourceField?.label,
|
|
|
|
|
|
};
|
|
|
|
|
|
} else {
|
|
|
|
|
|
newConditions[index] = { ...newConditions[index], [field]: value };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setWhereConditions(newConditions);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleSave = () => {
|
|
|
|
|
|
updateNode(nodeId, {
|
|
|
|
|
|
displayName,
|
|
|
|
|
|
targetTable,
|
|
|
|
|
|
fieldMappings,
|
|
|
|
|
|
whereConditions,
|
|
|
|
|
|
options: {
|
|
|
|
|
|
batchSize: batchSize ? parseInt(batchSize) : undefined,
|
|
|
|
|
|
ignoreErrors,
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const selectedTableLabel = tables.find((t) => t.tableName === targetTable)?.label || targetTable;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<ScrollArea className="h-full">
|
|
|
|
|
|
<div className="space-y-4 p-4">
|
|
|
|
|
|
{/* 기본 정보 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3 className="mb-3 text-sm font-semibold">기본 정보</h3>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label htmlFor="displayName" className="text-xs">
|
|
|
|
|
|
표시 이름
|
|
|
|
|
|
</Label>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
id="displayName"
|
|
|
|
|
|
value={displayName}
|
|
|
|
|
|
onChange={(e) => setDisplayName(e.target.value)}
|
|
|
|
|
|
className="mt-1"
|
|
|
|
|
|
placeholder="노드 표시 이름"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-10-02 17:51:15 +09:00
|
|
|
|
{/* 🔥 타겟 타입 선택 */}
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
<div>
|
2025-10-02 17:51:15 +09:00
|
|
|
|
<Label className="mb-2 block text-xs font-medium">타겟 선택</Label>
|
|
|
|
|
|
<div className="grid grid-cols-3 gap-2">
|
|
|
|
|
|
{/* 내부 데이터베이스 */}
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => handleTargetTypeChange("internal")}
|
|
|
|
|
|
className={cn(
|
|
|
|
|
|
"relative flex flex-col items-center gap-2 rounded-lg border-2 p-3 transition-all",
|
|
|
|
|
|
targetType === "internal" ? "border-blue-500 bg-blue-50" : "border-gray-200 hover:border-gray-300",
|
|
|
|
|
|
)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Database className={cn("h-5 w-5", targetType === "internal" ? "text-blue-600" : "text-gray-400")} />
|
|
|
|
|
|
<span
|
|
|
|
|
|
className={cn("text-xs font-medium", targetType === "internal" ? "text-blue-700" : "text-gray-600")}
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
>
|
2025-10-02 17:51:15 +09:00
|
|
|
|
내부 DB
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{targetType === "internal" && <Check className="absolute top-2 right-2 h-4 w-4 text-blue-600" />}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 외부 데이터베이스 */}
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => handleTargetTypeChange("external")}
|
|
|
|
|
|
className={cn(
|
|
|
|
|
|
"relative flex flex-col items-center gap-2 rounded-lg border-2 p-3 transition-all",
|
|
|
|
|
|
targetType === "external"
|
|
|
|
|
|
? "border-green-500 bg-green-50"
|
|
|
|
|
|
: "border-gray-200 hover:border-gray-300",
|
|
|
|
|
|
)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Globe className={cn("h-5 w-5", targetType === "external" ? "text-green-600" : "text-gray-400")} />
|
|
|
|
|
|
<span
|
|
|
|
|
|
className={cn(
|
|
|
|
|
|
"text-xs font-medium",
|
|
|
|
|
|
targetType === "external" ? "text-green-700" : "text-gray-600",
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
)}
|
2025-10-02 17:51:15 +09:00
|
|
|
|
>
|
|
|
|
|
|
외부 DB
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{targetType === "external" && <Check className="absolute top-2 right-2 h-4 w-4 text-green-600" />}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
{/* REST API */}
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => handleTargetTypeChange("api")}
|
|
|
|
|
|
className={cn(
|
|
|
|
|
|
"relative flex flex-col items-center gap-2 rounded-lg border-2 p-3 transition-all",
|
|
|
|
|
|
targetType === "api" ? "border-purple-500 bg-purple-50" : "border-gray-200 hover:border-gray-300",
|
|
|
|
|
|
)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Link2 className={cn("h-5 w-5", targetType === "api" ? "text-purple-600" : "text-gray-400")} />
|
|
|
|
|
|
<span
|
|
|
|
|
|
className={cn("text-xs font-medium", targetType === "api" ? "text-purple-700" : "text-gray-600")}
|
|
|
|
|
|
>
|
|
|
|
|
|
REST API
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{targetType === "api" && <Check className="absolute top-2 right-2 h-4 w-4 text-purple-600" />}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
</div>
|
2025-10-02 17:51:15 +09:00
|
|
|
|
|
|
|
|
|
|
{/* 내부 DB: 타겟 테이블 Combobox */}
|
|
|
|
|
|
{targetType === "internal" && (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-xs">타겟 테이블</Label>
|
|
|
|
|
|
<Popover open={tablesOpen} onOpenChange={setTablesOpen}>
|
|
|
|
|
|
<PopoverTrigger asChild>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="outline"
|
|
|
|
|
|
role="combobox"
|
|
|
|
|
|
aria-expanded={tablesOpen}
|
|
|
|
|
|
className="mt-1 w-full justify-between"
|
|
|
|
|
|
disabled={tablesLoading}
|
|
|
|
|
|
>
|
|
|
|
|
|
{tablesLoading ? (
|
|
|
|
|
|
<span className="text-muted-foreground">로딩 중...</span>
|
|
|
|
|
|
) : targetTable ? (
|
|
|
|
|
|
<span className="truncate">{selectedTableLabel}</span>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<span className="text-muted-foreground">테이블을 선택하세요</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</PopoverTrigger>
|
|
|
|
|
|
<PopoverContent className="w-[320px] p-0" align="start">
|
|
|
|
|
|
<Command>
|
|
|
|
|
|
<CommandInput placeholder="테이블 검색..." />
|
|
|
|
|
|
<CommandEmpty>테이블을 찾을 수 없습니다.</CommandEmpty>
|
|
|
|
|
|
<CommandList>
|
|
|
|
|
|
<CommandGroup>
|
|
|
|
|
|
{tables.map((table) => (
|
|
|
|
|
|
<CommandItem
|
|
|
|
|
|
key={table.tableName}
|
|
|
|
|
|
value={`${table.label} ${table.displayName} ${table.tableName}`}
|
|
|
|
|
|
onSelect={() => handleTableSelect(table.tableName)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Check
|
|
|
|
|
|
className={cn(
|
|
|
|
|
|
"mr-2 h-4 w-4",
|
|
|
|
|
|
targetTable === table.tableName ? "opacity-100" : "opacity-0",
|
|
|
|
|
|
)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div className="flex flex-col">
|
|
|
|
|
|
<span className="font-medium">{table.label || table.displayName}</span>
|
|
|
|
|
|
<span className="text-muted-foreground text-xs">{table.tableName}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</CommandItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</CommandGroup>
|
|
|
|
|
|
</CommandList>
|
|
|
|
|
|
</Command>
|
|
|
|
|
|
</PopoverContent>
|
|
|
|
|
|
</Popover>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 🔥 외부 DB 설정 (INSERT 노드와 동일 패턴) */}
|
|
|
|
|
|
{targetType === "external" && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{/* 외부 커넥션 선택 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="mb-1.5 block text-xs font-medium">외부 데이터베이스 커넥션</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={selectedExternalConnectionId?.toString()}
|
|
|
|
|
|
onValueChange={(value) => {
|
|
|
|
|
|
const connectionId = parseInt(value);
|
|
|
|
|
|
const selectedConnection = externalConnections.find((c) => c.id === connectionId);
|
|
|
|
|
|
setSelectedExternalConnectionId(connectionId);
|
|
|
|
|
|
setExternalTargetTable("");
|
|
|
|
|
|
setExternalColumns([]);
|
|
|
|
|
|
updateNode(nodeId, {
|
|
|
|
|
|
externalConnectionId: connectionId,
|
|
|
|
|
|
externalConnectionName: selectedConnection?.name,
|
|
|
|
|
|
externalDbType: selectedConnection?.db_type,
|
|
|
|
|
|
});
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="h-8 text-xs">
|
|
|
|
|
|
<SelectValue placeholder="커넥션을 선택하세요" />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
{externalConnectionsLoading ? (
|
|
|
|
|
|
<div className="p-2 text-center text-xs text-gray-500">로딩 중...</div>
|
|
|
|
|
|
) : externalConnections.length === 0 ? (
|
|
|
|
|
|
<div className="p-2 text-center text-xs text-gray-500">사용 가능한 커넥션이 없습니다</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
externalConnections.map((conn) => (
|
|
|
|
|
|
<SelectItem key={conn.id} value={conn.id!.toString()}>
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<span className="font-medium">{conn.db_type}</span>
|
|
|
|
|
|
<span className="text-gray-500">-</span>
|
|
|
|
|
|
<span>{conn.name}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))
|
|
|
|
|
|
)}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 외부 테이블 선택 */}
|
|
|
|
|
|
{selectedExternalConnectionId && (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="mb-1.5 block text-xs font-medium">테이블</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={externalTargetTable}
|
|
|
|
|
|
onValueChange={(value) => {
|
|
|
|
|
|
const selectedTable = externalTables.find((t) => t.table_name === value);
|
|
|
|
|
|
setExternalTargetTable(value);
|
|
|
|
|
|
updateNode(nodeId, {
|
|
|
|
|
|
externalTargetTable: value,
|
|
|
|
|
|
externalTargetSchema: selectedTable?.schema,
|
|
|
|
|
|
});
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="h-8 text-xs">
|
|
|
|
|
|
<SelectValue placeholder="테이블을 선택하세요" />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
{externalTablesLoading ? (
|
|
|
|
|
|
<div className="p-2 text-center text-xs text-gray-500">로딩 중...</div>
|
|
|
|
|
|
) : externalTables.length === 0 ? (
|
|
|
|
|
|
<div className="p-2 text-center text-xs text-gray-500">테이블이 없습니다</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
externalTables.map((table) => (
|
|
|
|
|
|
<SelectItem key={table.table_name} value={table.table_name}>
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<span className="font-medium">{table.table_name}</span>
|
|
|
|
|
|
{table.schema && <span className="text-xs text-gray-500">({table.schema})</span>}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))
|
|
|
|
|
|
)}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 외부 컬럼 표시 */}
|
|
|
|
|
|
{externalTargetTable && externalColumns.length > 0 && (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="mb-1.5 block text-xs font-medium">컬럼 목록</Label>
|
|
|
|
|
|
<div className="max-h-32 space-y-1 overflow-y-auto rounded border bg-gray-50 p-2">
|
|
|
|
|
|
{externalColumns.map((col) => (
|
|
|
|
|
|
<div key={col.column_name} className="flex items-center justify-between text-xs">
|
|
|
|
|
|
<span className="font-medium">{col.column_name}</span>
|
|
|
|
|
|
<span className="text-gray-500">{col.data_type}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 🔥 REST API 설정 */}
|
|
|
|
|
|
{targetType === "api" && (
|
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
|
{/* API 엔드포인트 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="mb-1.5 block text-xs font-medium">API 엔드포인트</Label>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
placeholder="https://api.example.com/v1/users/{id}"
|
|
|
|
|
|
value={apiEndpoint}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
setApiEndpoint(e.target.value);
|
|
|
|
|
|
updateNode(nodeId, { apiEndpoint: e.target.value });
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="h-8 text-xs"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* HTTP 메서드 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="mb-1.5 block text-xs font-medium">HTTP 메서드</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={apiMethod}
|
|
|
|
|
|
onValueChange={(value: "PUT" | "PATCH") => {
|
|
|
|
|
|
setApiMethod(value);
|
|
|
|
|
|
updateNode(nodeId, { apiMethod: value });
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="h-8 text-xs">
|
|
|
|
|
|
<SelectValue />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
<SelectItem value="PUT">PUT</SelectItem>
|
|
|
|
|
|
<SelectItem value="PATCH">PATCH</SelectItem>
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 인증 타입 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="mb-1.5 block text-xs font-medium">인증 방식</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={apiAuthType}
|
|
|
|
|
|
onValueChange={(value: "none" | "basic" | "bearer" | "apikey") => {
|
|
|
|
|
|
setApiAuthType(value);
|
|
|
|
|
|
updateNode(nodeId, { apiAuthType: value });
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="h-8 text-xs">
|
|
|
|
|
|
<SelectValue />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
<SelectItem value="none">인증 없음</SelectItem>
|
|
|
|
|
|
<SelectItem value="bearer">Bearer Token</SelectItem>
|
|
|
|
|
|
<SelectItem value="basic">Basic Auth</SelectItem>
|
|
|
|
|
|
<SelectItem value="apikey">API Key</SelectItem>
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 인증 설정 */}
|
|
|
|
|
|
{apiAuthType !== "none" && (
|
|
|
|
|
|
<div className="space-y-2 rounded border bg-gray-50 p-3">
|
|
|
|
|
|
<Label className="block text-xs font-medium">인증 정보</Label>
|
|
|
|
|
|
|
|
|
|
|
|
{apiAuthType === "bearer" && (
|
|
|
|
|
|
<Input
|
|
|
|
|
|
placeholder="Bearer Token"
|
|
|
|
|
|
value={(apiAuthConfig as any)?.token || ""}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
const newConfig = { token: e.target.value };
|
|
|
|
|
|
setApiAuthConfig(newConfig);
|
|
|
|
|
|
updateNode(nodeId, { apiAuthConfig: newConfig });
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="h-8 text-xs"
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{apiAuthType === "basic" && (
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<Input
|
|
|
|
|
|
placeholder="사용자명"
|
|
|
|
|
|
value={(apiAuthConfig as any)?.username || ""}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
const newConfig = { ...(apiAuthConfig as any), username: e.target.value };
|
|
|
|
|
|
setApiAuthConfig(newConfig);
|
|
|
|
|
|
updateNode(nodeId, { apiAuthConfig: newConfig });
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="h-8 text-xs"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
type="password"
|
|
|
|
|
|
placeholder="비밀번호"
|
|
|
|
|
|
value={(apiAuthConfig as any)?.password || ""}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
const newConfig = { ...(apiAuthConfig as any), password: e.target.value };
|
|
|
|
|
|
setApiAuthConfig(newConfig);
|
|
|
|
|
|
updateNode(nodeId, { apiAuthConfig: newConfig });
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="h-8 text-xs"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{apiAuthType === "apikey" && (
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
<Input
|
|
|
|
|
|
placeholder="헤더 이름 (예: X-API-Key)"
|
|
|
|
|
|
value={(apiAuthConfig as any)?.headerName || ""}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
const newConfig = { ...(apiAuthConfig as any), headerName: e.target.value };
|
|
|
|
|
|
setApiAuthConfig(newConfig);
|
|
|
|
|
|
updateNode(nodeId, { apiAuthConfig: newConfig });
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="h-8 text-xs"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
placeholder="API Key"
|
|
|
|
|
|
value={(apiAuthConfig as any)?.apiKey || ""}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
const newConfig = { ...(apiAuthConfig as any), apiKey: e.target.value };
|
|
|
|
|
|
setApiAuthConfig(newConfig);
|
|
|
|
|
|
updateNode(nodeId, { apiAuthConfig: newConfig });
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="h-8 text-xs"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 커스텀 헤더 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="mb-1.5 block text-xs font-medium">커스텀 헤더 (선택사항)</Label>
|
|
|
|
|
|
<div className="space-y-2 rounded border bg-gray-50 p-3">
|
|
|
|
|
|
{Object.entries(apiHeaders).map(([key, value], index) => (
|
|
|
|
|
|
<div key={index} className="flex gap-2">
|
|
|
|
|
|
<Input
|
|
|
|
|
|
placeholder="헤더 이름"
|
|
|
|
|
|
value={key}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
const newHeaders = { ...apiHeaders };
|
|
|
|
|
|
delete newHeaders[key];
|
|
|
|
|
|
newHeaders[e.target.value] = value;
|
|
|
|
|
|
setApiHeaders(newHeaders);
|
|
|
|
|
|
updateNode(nodeId, { apiHeaders: newHeaders });
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="h-7 flex-1 text-xs"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
placeholder="헤더 값"
|
|
|
|
|
|
value={value}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
const newHeaders = { ...apiHeaders, [key]: e.target.value };
|
|
|
|
|
|
setApiHeaders(newHeaders);
|
|
|
|
|
|
updateNode(nodeId, { apiHeaders: newHeaders });
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="h-7 flex-1 text-xs"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
const newHeaders = { ...apiHeaders };
|
|
|
|
|
|
delete newHeaders[key];
|
|
|
|
|
|
setApiHeaders(newHeaders);
|
|
|
|
|
|
updateNode(nodeId, { apiHeaders: newHeaders });
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="h-7 w-7 p-0"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Trash2 className="h-3 w-3" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
<Button
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
variant="outline"
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
const newHeaders = { ...apiHeaders, "": "" };
|
|
|
|
|
|
setApiHeaders(newHeaders);
|
|
|
|
|
|
updateNode(nodeId, { apiHeaders: newHeaders });
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="h-7 w-full text-xs"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Plus className="mr-1 h-3 w-3" />
|
|
|
|
|
|
헤더 추가
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 요청 바디 설정 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="mb-1.5 block text-xs font-medium">
|
|
|
|
|
|
요청 바디 템플릿
|
|
|
|
|
|
<span className="ml-1 text-gray-500">{`{{fieldName}}`}으로 소스 필드 참조</span>
|
|
|
|
|
|
</Label>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
placeholder={`{\n "id": "{{id}}",\n "name": "{{name}}",\n "email": "{{email}}"\n}`}
|
|
|
|
|
|
value={apiBodyTemplate}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
setApiBodyTemplate(e.target.value);
|
|
|
|
|
|
updateNode(nodeId, { apiBodyTemplate: e.target.value });
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="w-full rounded border p-2 font-mono text-xs"
|
|
|
|
|
|
rows={8}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<p className="mt-1 text-xs text-gray-500">
|
|
|
|
|
|
소스 데이터의 필드명을 {`{{필드명}}`} 형태로 참조할 수 있습니다.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* WHERE 조건 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div className="mb-2 flex items-center justify-between">
|
|
|
|
|
|
<h3 className="text-sm font-semibold">WHERE 조건</h3>
|
|
|
|
|
|
<Button size="sm" variant="outline" onClick={handleAddCondition} className="h-7 px-2 text-xs">
|
|
|
|
|
|
<Plus className="mr-1 h-3 w-3" />
|
|
|
|
|
|
조건 추가
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{!targetTable && !columnsLoading && (
|
|
|
|
|
|
<div className="rounded border border-dashed bg-yellow-50 p-3 text-center text-xs text-yellow-700">
|
|
|
|
|
|
⚠️ 먼저 타겟 테이블을 선택하세요
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{targetTable && whereConditions.length === 0 && (
|
|
|
|
|
|
<div className="rounded border border-dashed bg-gray-50 p-3 text-center text-xs text-gray-500">
|
|
|
|
|
|
WHERE 조건을 추가하세요
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{targetTable && whereConditions.length > 0 && targetColumns.length > 0 && (
|
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
|
{whereConditions.map((condition, index) => (
|
|
|
|
|
|
<div key={index} className="rounded border bg-blue-50 p-3">
|
|
|
|
|
|
<div className="mb-2 flex items-center justify-between">
|
|
|
|
|
|
<span className="text-xs font-medium text-gray-700">조건 #{index + 1}</span>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
onClick={() => handleRemoveCondition(index)}
|
|
|
|
|
|
className="h-6 w-6 p-0 text-red-600 hover:text-red-700"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Trash2 className="h-3 w-3" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
{/* 타겟 필드 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-xs text-gray-600">타겟 필드</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={condition.field}
|
|
|
|
|
|
onValueChange={(value) => handleConditionChange(index, "field", value)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="mt-1 h-8 text-xs">
|
|
|
|
|
|
<SelectValue placeholder="필드 선택" />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
{targetColumns.map((col) => (
|
|
|
|
|
|
<SelectItem key={col.columnName} value={col.columnName} className="text-xs">
|
|
|
|
|
|
<div className="flex items-center justify-between gap-2">
|
|
|
|
|
|
<span className="font-mono">{col.columnLabel || col.columnName}</span>
|
|
|
|
|
|
<span className="text-muted-foreground">{col.dataType}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 연산자 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-xs text-gray-600">연산자</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={condition.operator}
|
|
|
|
|
|
onValueChange={(value) => handleConditionChange(index, "operator", value)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="mt-1 h-8 text-xs">
|
|
|
|
|
|
<SelectValue />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
{OPERATORS.map((op) => (
|
|
|
|
|
|
<SelectItem key={op.value} value={op.value} className="text-xs">
|
|
|
|
|
|
{op.label}
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* IS_NULL, IS_NOT_NULL이 아닐 때만 소스 필드와 정적 값 표시 */}
|
|
|
|
|
|
{condition.operator !== "IS_NULL" && condition.operator !== "IS_NOT_NULL" && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{/* 소스 필드 또는 값 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-xs text-gray-600">소스 필드 (선택)</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={condition.sourceField || "_NONE_"}
|
|
|
|
|
|
onValueChange={(value) =>
|
|
|
|
|
|
handleConditionChange(index, "sourceField", value === "_NONE_" ? undefined : value)
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="mt-1 h-8 text-xs">
|
|
|
|
|
|
<SelectValue placeholder="소스 필드 선택 (선택)" />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
<SelectItem value="_NONE_" className="text-xs text-gray-400">
|
|
|
|
|
|
없음 (정적 값 사용)
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
{sourceFields.map((field) => (
|
|
|
|
|
|
<SelectItem key={field.name} value={field.name} className="text-xs">
|
|
|
|
|
|
<div className="flex items-center justify-between gap-2">
|
|
|
|
|
|
<span className="font-medium">{field.label || field.name}</span>
|
|
|
|
|
|
{field.label && field.label !== field.name && (
|
|
|
|
|
|
<span className="text-muted-foreground font-mono text-xs">{field.name}</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 정적 값 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-xs text-gray-600">정적 값</Label>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
value={condition.staticValue || ""}
|
|
|
|
|
|
onChange={(e) => handleConditionChange(index, "staticValue", e.target.value || undefined)}
|
|
|
|
|
|
placeholder="비교할 고정 값"
|
|
|
|
|
|
className="mt-1 h-8 text-xs"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<p className="mt-1 text-xs text-gray-400">소스 필드가 비어있을 때만 사용됩니다</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* IS_NULL, IS_NOT_NULL일 때 안내 메시지 */}
|
|
|
|
|
|
{(condition.operator === "IS_NULL" || condition.operator === "IS_NOT_NULL") && (
|
|
|
|
|
|
<div className="rounded bg-blue-50 p-3 text-xs text-blue-700">
|
|
|
|
|
|
ℹ️ {condition.operator === "IS_NULL" ? "IS NULL" : "IS NOT NULL"} 조건은 값 비교가 필요하지
|
|
|
|
|
|
않습니다.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-10-02 17:51:15 +09:00
|
|
|
|
{/* 필드 매핑 (REST API 타입에서는 숨김) */}
|
|
|
|
|
|
{targetType !== "api" && (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div className="mb-2 flex items-center justify-between">
|
|
|
|
|
|
<h3 className="text-sm font-semibold">업데이트 필드</h3>
|
|
|
|
|
|
<Button size="sm" variant="outline" onClick={handleAddMapping} className="h-7 px-2 text-xs">
|
|
|
|
|
|
<Plus className="mr-1 h-3 w-3" />
|
|
|
|
|
|
매핑 추가
|
|
|
|
|
|
</Button>
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-10-02 17:51:15 +09:00
|
|
|
|
{!targetTable && !columnsLoading && (
|
|
|
|
|
|
<div className="rounded border border-dashed bg-yellow-50 p-3 text-center text-xs text-yellow-700">
|
|
|
|
|
|
⚠️ 먼저 타겟 테이블을 선택하세요
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{targetTable && !columnsLoading && targetColumns.length === 0 && (
|
|
|
|
|
|
<div className="rounded border border-dashed bg-red-50 p-3 text-center text-xs text-red-700">
|
|
|
|
|
|
❌ 컬럼 정보를 불러올 수 없습니다
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{targetTable && targetColumns.length > 0 && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{fieldMappings.length > 0 ? (
|
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
|
{fieldMappings.map((mapping, index) => (
|
|
|
|
|
|
<div key={index} className="rounded border bg-gray-50 p-3">
|
|
|
|
|
|
<div className="mb-2 flex items-center justify-between">
|
|
|
|
|
|
<span className="text-xs font-medium text-gray-700">매핑 #{index + 1}</span>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
onClick={() => handleRemoveMapping(index)}
|
|
|
|
|
|
className="h-6 w-6 p-0 text-red-600 hover:text-red-700"
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
>
|
2025-10-02 17:51:15 +09:00
|
|
|
|
<Trash2 className="h-3 w-3" />
|
|
|
|
|
|
</Button>
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-10-02 17:51:15 +09:00
|
|
|
|
<div className="space-y-2">
|
2025-10-13 12:00:41 +09:00
|
|
|
|
{/* 소스 필드 - REST API인 경우 입력, 아니면 드롭다운 */}
|
2025-10-02 17:51:15 +09:00
|
|
|
|
<div>
|
2025-10-13 12:00:41 +09:00
|
|
|
|
<Label className="text-xs text-gray-600">
|
|
|
|
|
|
소스 필드{hasRestAPISource && " (REST API - 직접 입력)"}
|
|
|
|
|
|
</Label>
|
|
|
|
|
|
{hasRestAPISource ? (
|
|
|
|
|
|
<Input
|
|
|
|
|
|
value={mapping.sourceField || ""}
|
|
|
|
|
|
onChange={(e) => handleMappingChange(index, "sourceField", e.target.value || null)}
|
|
|
|
|
|
placeholder="API 응답 JSON의 필드명을 입력하세요"
|
|
|
|
|
|
className="mt-1 h-8 text-xs"
|
|
|
|
|
|
/>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={mapping.sourceField || ""}
|
|
|
|
|
|
onValueChange={(value) => handleMappingChange(index, "sourceField", value || null)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="mt-1 h-8 text-xs">
|
|
|
|
|
|
<SelectValue placeholder="소스 필드 선택" />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
{sourceFields.length === 0 ? (
|
|
|
|
|
|
<div className="p-2 text-center text-xs text-gray-400">
|
|
|
|
|
|
연결된 소스 노드가 없습니다
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
sourceFields.map((field) => (
|
|
|
|
|
|
<SelectItem key={field.name} value={field.name} className="text-xs">
|
|
|
|
|
|
<div className="flex items-center justify-between gap-2">
|
|
|
|
|
|
<span className="font-medium">{field.label || field.name}</span>
|
|
|
|
|
|
{field.label && field.label !== field.name && (
|
|
|
|
|
|
<span className="text-muted-foreground font-mono text-xs">
|
|
|
|
|
|
{field.name}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))
|
|
|
|
|
|
)}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
)}
|
2025-10-02 17:51:15 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center justify-center py-1">
|
|
|
|
|
|
<ArrowRight className="h-4 w-4 text-blue-600" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 타겟 필드 드롭다운 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-xs text-gray-600">타겟 필드</Label>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={mapping.targetField}
|
|
|
|
|
|
onValueChange={(value) => handleMappingChange(index, "targetField", value)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="mt-1 h-8 text-xs">
|
|
|
|
|
|
<SelectValue placeholder="타겟 필드 선택" />
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
{targetColumns.map((col) => (
|
|
|
|
|
|
<SelectItem key={col.columnName} value={col.columnName} className="text-xs">
|
|
|
|
|
|
<div className="flex items-center justify-between gap-2">
|
|
|
|
|
|
<span className="font-mono">{col.columnLabel || col.columnName}</span>
|
|
|
|
|
|
<span className="text-muted-foreground">
|
|
|
|
|
|
{col.dataType}
|
|
|
|
|
|
{!col.isNullable && <span className="text-red-500">*</span>}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 정적 값 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label className="text-xs text-gray-600">정적 값 (선택)</Label>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
value={mapping.staticValue || ""}
|
|
|
|
|
|
onChange={(e) => handleMappingChange(index, "staticValue", e.target.value || undefined)}
|
|
|
|
|
|
placeholder="소스 필드 대신 고정 값 사용"
|
|
|
|
|
|
className="mt-1 h-8 text-xs"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<p className="mt-1 text-xs text-gray-400">소스 필드가 비어있을 때만 사용됩니다</p>
|
|
|
|
|
|
</div>
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-02 17:51:15 +09:00
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="rounded border border-dashed bg-gray-50 p-3 text-center text-xs text-gray-500">
|
|
|
|
|
|
업데이트할 필드를 추가하세요
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
feat: 노드 기반 데이터 플로우 시스템 구현
- 노드 에디터 UI 구현 (React Flow 기반)
- TableSource, DataTransform, INSERT, UPDATE, DELETE, UPSERT 노드
- 드래그앤드롭 노드 추가 및 연결
- 속성 패널을 통한 노드 설정
- 실시간 필드 라벨 표시 (column_labels 테이블 연동)
- 데이터 변환 노드 (DataTransform) 기능
- EXPLODE: 구분자로 1개 행 → 여러 행 확장
- UPPERCASE, LOWERCASE, TRIM, CONCAT, SPLIT, REPLACE 등 12가지 변환 타입
- In-place 변환 지원 (타겟 필드 생략 시 소스 필드 덮어쓰기)
- 변환된 필드가 하위 액션 노드에 자동 전달
- 노드 플로우 실행 엔진
- 위상 정렬을 통한 노드 실행 순서 결정
- 레벨별 병렬 실행 (Promise.allSettled)
- 부분 실패 허용 (한 노드 실패 시 연결된 하위 노드만 스킵)
- 트랜잭션 기반 안전한 데이터 처리
- UPSERT 액션 로직 구현
- DB 제약 조건 없이 SELECT → UPDATE or INSERT 방식
- 복합 충돌 키 지원 (예: sales_no + product_name)
- 파라미터 인덱스 정확한 매핑
- 데이터 소스 자동 감지
- 테이블 선택 데이터 (selectedRowsData) 자동 주입
- 폼 입력 데이터 (formData) 자동 주입
- TableSource 노드가 외부 데이터 우선 사용
- 버튼 컴포넌트 통합
- 기존 관계 실행 + 새 노드 플로우 실행 하이브리드 지원
- 노드 플로우 선택 UI 추가
- API 클라이언트 통합 (Axios)
- 개발 문서 작성
- 노드 기반 제어 시스템 개선 계획
- 노드 연결 규칙 설계
- 노드 실행 엔진 설계
- 노드 구조 개선안
- 버튼 통합 분석
2025-10-02 16:22:29 +09:00
|
|
|
|
|
|
|
|
|
|
{/* 옵션 */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3 className="mb-3 text-sm font-semibold">옵션</h3>
|
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<Label htmlFor="batchSize" className="text-xs">
|
|
|
|
|
|
배치 크기
|
|
|
|
|
|
</Label>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
id="batchSize"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
value={batchSize}
|
|
|
|
|
|
onChange={(e) => setBatchSize(e.target.value)}
|
|
|
|
|
|
className="mt-1"
|
|
|
|
|
|
placeholder="예: 100"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center space-x-2">
|
|
|
|
|
|
<Checkbox
|
|
|
|
|
|
id="ignoreErrors"
|
|
|
|
|
|
checked={ignoreErrors}
|
|
|
|
|
|
onCheckedChange={(checked) => setIgnoreErrors(checked as boolean)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Label htmlFor="ignoreErrors" className="cursor-pointer text-xs font-normal">
|
|
|
|
|
|
오류 무시
|
|
|
|
|
|
</Label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 적용 버튼 */}
|
|
|
|
|
|
<div className="sticky bottom-0 border-t bg-white pt-3">
|
|
|
|
|
|
<Button onClick={handleSave} className="w-full" size="sm">
|
|
|
|
|
|
적용
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<p className="mt-2 text-center text-xs text-gray-500">✅ 변경 사항이 즉시 노드에 반영됩니다.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</ScrollArea>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|