워크플로우 restapi도 연결가능하고여러개 가능하게 구현시켜놓음
This commit is contained in:
@@ -30,12 +30,25 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { ExternalDbConnectionAPI } from "@/lib/api/externalDbConnection";
|
||||
|
||||
// 다중 REST API 연결 설정
|
||||
interface RestApiConnectionConfig {
|
||||
connectionId: number;
|
||||
connectionName: string;
|
||||
endpoint: string;
|
||||
jsonPath: string;
|
||||
alias: string;
|
||||
}
|
||||
|
||||
interface FlowStepPanelProps {
|
||||
step: FlowStep;
|
||||
flowId: number;
|
||||
flowTableName?: string; // 플로우 정의에서 선택한 테이블명
|
||||
flowDbSourceType?: "internal" | "external"; // 플로우의 DB 소스 타입
|
||||
flowDbSourceType?: "internal" | "external" | "restapi" | "multi_restapi"; // 플로우의 DB 소스 타입
|
||||
flowDbConnectionId?: number; // 플로우의 외부 DB 연결 ID
|
||||
flowRestApiConnectionId?: number; // 플로우의 REST API 연결 ID (단일)
|
||||
flowRestApiEndpoint?: string; // REST API 엔드포인트 (단일)
|
||||
flowRestApiJsonPath?: string; // REST API JSON 경로 (단일)
|
||||
flowRestApiConnections?: RestApiConnectionConfig[]; // 다중 REST API 설정
|
||||
onClose: () => void;
|
||||
onUpdate: () => void;
|
||||
}
|
||||
@@ -46,6 +59,10 @@ export function FlowStepPanel({
|
||||
flowTableName,
|
||||
flowDbSourceType = "internal",
|
||||
flowDbConnectionId,
|
||||
flowRestApiConnectionId,
|
||||
flowRestApiEndpoint,
|
||||
flowRestApiJsonPath,
|
||||
flowRestApiConnections,
|
||||
onClose,
|
||||
onUpdate,
|
||||
}: FlowStepPanelProps) {
|
||||
@@ -56,6 +73,9 @@ export function FlowStepPanel({
|
||||
flowTableName,
|
||||
flowDbSourceType,
|
||||
flowDbConnectionId,
|
||||
flowRestApiConnectionId,
|
||||
flowRestApiEndpoint,
|
||||
flowRestApiJsonPath,
|
||||
final: step.tableName || flowTableName || "",
|
||||
});
|
||||
|
||||
@@ -315,10 +335,11 @@ export function FlowStepPanel({
|
||||
setFormData(newFormData);
|
||||
}, [step.id, flowTableName]); // flowTableName도 의존성 추가
|
||||
|
||||
// 테이블 선택 시 컬럼 로드 - 내부/외부 DB 모두 지원
|
||||
// 테이블 선택 시 컬럼 로드 - 내부/외부 DB 및 REST API 모두 지원
|
||||
useEffect(() => {
|
||||
const loadColumns = async () => {
|
||||
if (!formData.tableName) {
|
||||
// 다중 REST API인 경우 tableName 없이도 컬럼 로드 가능
|
||||
if (!formData.tableName && flowDbSourceType !== "multi_restapi") {
|
||||
setColumns([]);
|
||||
return;
|
||||
}
|
||||
@@ -329,8 +350,74 @@ export function FlowStepPanel({
|
||||
tableName: formData.tableName,
|
||||
flowDbSourceType,
|
||||
flowDbConnectionId,
|
||||
flowRestApiConnectionId,
|
||||
flowRestApiConnections,
|
||||
});
|
||||
|
||||
// 다중 REST API인 경우
|
||||
if (flowDbSourceType === "multi_restapi" && flowRestApiConnections && flowRestApiConnections.length > 0) {
|
||||
console.log("🌐 다중 REST API 컬럼 로드 시작");
|
||||
const { ExternalRestApiConnectionAPI } = await import("@/lib/api/externalRestApiConnection");
|
||||
|
||||
const allColumns: any[] = [];
|
||||
|
||||
for (const config of flowRestApiConnections) {
|
||||
try {
|
||||
const effectiveJsonPath = (!config.jsonPath || config.jsonPath === "data") ? "response" : config.jsonPath;
|
||||
|
||||
const restApiData = await ExternalRestApiConnectionAPI.fetchData(
|
||||
config.connectionId,
|
||||
config.endpoint,
|
||||
effectiveJsonPath,
|
||||
);
|
||||
|
||||
if (restApiData.columns && restApiData.columns.length > 0) {
|
||||
const prefixedColumns = restApiData.columns.map((col) => ({
|
||||
column_name: config.alias ? `${config.alias}${col.columnName}` : col.columnName,
|
||||
data_type: col.dataType || "varchar",
|
||||
displayName: `${col.columnLabel || col.columnName} (${config.connectionName})`,
|
||||
}));
|
||||
allColumns.push(...prefixedColumns);
|
||||
}
|
||||
} catch (apiError) {
|
||||
console.warn(`API ${config.connectionId} 컬럼 로드 실패:`, apiError);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("✅ 다중 REST API 컬럼 로드 완료:", allColumns.length, "items");
|
||||
setColumns(allColumns);
|
||||
return;
|
||||
}
|
||||
|
||||
// 단일 REST API인 경우
|
||||
const isRestApi = flowDbSourceType === "restapi" || formData.tableName?.startsWith("_restapi_");
|
||||
|
||||
if (isRestApi && flowRestApiConnectionId) {
|
||||
console.log("🌐 단일 REST API 컬럼 로드 시작");
|
||||
const { ExternalRestApiConnectionAPI } = await import("@/lib/api/externalRestApiConnection");
|
||||
|
||||
const effectiveJsonPath = (!flowRestApiJsonPath || flowRestApiJsonPath === "data") ? "response" : flowRestApiJsonPath;
|
||||
|
||||
const restApiData = await ExternalRestApiConnectionAPI.fetchData(
|
||||
flowRestApiConnectionId,
|
||||
flowRestApiEndpoint,
|
||||
effectiveJsonPath,
|
||||
);
|
||||
|
||||
if (restApiData.columns && restApiData.columns.length > 0) {
|
||||
const columnList = restApiData.columns.map((col) => ({
|
||||
column_name: col.columnName,
|
||||
data_type: col.dataType || "varchar",
|
||||
displayName: col.columnLabel || col.columnName,
|
||||
}));
|
||||
console.log("✅ REST API 컬럼 로드 완료:", columnList.length, "items");
|
||||
setColumns(columnList);
|
||||
} else {
|
||||
setColumns([]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 외부 DB인 경우
|
||||
if (flowDbSourceType === "external" && flowDbConnectionId) {
|
||||
const token = localStorage.getItem("authToken");
|
||||
@@ -399,7 +486,7 @@ export function FlowStepPanel({
|
||||
};
|
||||
|
||||
loadColumns();
|
||||
}, [formData.tableName, flowDbSourceType, flowDbConnectionId]);
|
||||
}, [formData.tableName, flowDbSourceType, flowDbConnectionId, flowRestApiConnectionId, flowRestApiEndpoint, flowRestApiJsonPath, flowRestApiConnections]);
|
||||
|
||||
// formData의 최신 값을 항상 참조하기 위한 ref
|
||||
const formDataRef = useRef(formData);
|
||||
@@ -661,6 +748,10 @@ export function FlowStepPanel({
|
||||
tableName={formData.tableName}
|
||||
dbSourceType={flowDbSourceType}
|
||||
dbConnectionId={flowDbConnectionId}
|
||||
restApiConnectionId={flowRestApiConnectionId}
|
||||
restApiEndpoint={flowRestApiEndpoint}
|
||||
restApiJsonPath={flowRestApiJsonPath}
|
||||
restApiConnections={flowRestApiConnections}
|
||||
condition={formData.conditionJson}
|
||||
onChange={(condition) => setFormData({ ...formData, conditionJson: condition })}
|
||||
/>
|
||||
@@ -852,7 +943,7 @@ export function FlowStepPanel({
|
||||
<SelectItem
|
||||
key={opt.value}
|
||||
value={opt.value}
|
||||
disabled={opt.value !== "internal" && opt.value !== "external_db"}
|
||||
disabled={opt.value !== "internal" && opt.value !== "external_db" && opt.value !== "rest_api"}
|
||||
>
|
||||
{opt.label}
|
||||
</SelectItem>
|
||||
@@ -1044,6 +1135,132 @@ export function FlowStepPanel({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* REST API 연동 설정 */}
|
||||
{formData.integrationType === "rest_api" && (
|
||||
<div className="space-y-4 rounded-lg border p-4">
|
||||
<div>
|
||||
<Label>REST API 연결</Label>
|
||||
<Select
|
||||
value={formData.integrationConfig?.connectionId?.toString() || ""}
|
||||
onValueChange={(value) => {
|
||||
const connectionId = parseInt(value);
|
||||
setFormData({
|
||||
...formData,
|
||||
integrationConfig: {
|
||||
type: "rest_api",
|
||||
connectionId,
|
||||
operation: "update",
|
||||
endpoint: "",
|
||||
method: "POST",
|
||||
bodyTemplate: "{}",
|
||||
} as any,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="REST API 연결 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{flowRestApiConnections && flowRestApiConnections.length > 0 ? (
|
||||
flowRestApiConnections.map((api) => (
|
||||
<SelectItem key={api.connectionId} value={api.connectionId.toString()}>
|
||||
{api.connectionName}
|
||||
</SelectItem>
|
||||
))
|
||||
) : flowRestApiConnectionId ? (
|
||||
<SelectItem value={flowRestApiConnectionId.toString()}>
|
||||
기본 REST API 연결
|
||||
</SelectItem>
|
||||
) : (
|
||||
<SelectItem value="" disabled>
|
||||
연결된 REST API가 없습니다
|
||||
</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{formData.integrationConfig?.connectionId && (
|
||||
<>
|
||||
<div>
|
||||
<Label>HTTP 메서드</Label>
|
||||
<Select
|
||||
value={(formData.integrationConfig as any).method || "POST"}
|
||||
onValueChange={(value) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
integrationConfig: {
|
||||
...formData.integrationConfig!,
|
||||
method: value,
|
||||
} as any,
|
||||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="GET">GET</SelectItem>
|
||||
<SelectItem value="POST">POST</SelectItem>
|
||||
<SelectItem value="PUT">PUT</SelectItem>
|
||||
<SelectItem value="PATCH">PATCH</SelectItem>
|
||||
<SelectItem value="DELETE">DELETE</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>엔드포인트</Label>
|
||||
<Input
|
||||
value={(formData.integrationConfig as any).endpoint || ""}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
integrationConfig: {
|
||||
...formData.integrationConfig!,
|
||||
endpoint: e.target.value,
|
||||
} as any,
|
||||
})
|
||||
}
|
||||
placeholder="/api/update"
|
||||
/>
|
||||
<p className="text-muted-foreground mt-1 text-xs">
|
||||
데이터 이동 시 호출할 API 엔드포인트
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>요청 바디 (JSON)</Label>
|
||||
<Textarea
|
||||
value={(formData.integrationConfig as any).bodyTemplate || "{}"}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
integrationConfig: {
|
||||
...formData.integrationConfig!,
|
||||
bodyTemplate: e.target.value,
|
||||
} as any,
|
||||
})
|
||||
}
|
||||
placeholder='{"id": "{{dataId}}", "status": "approved"}'
|
||||
rows={4}
|
||||
className="font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="rounded-md bg-blue-50 p-3">
|
||||
<p className="text-sm text-blue-900">
|
||||
💡 템플릿 변수를 사용하여 동적 값을 삽입할 수 있습니다:
|
||||
<br />• {`{{dataId}}`} - 이동하는 데이터의 ID
|
||||
<br />• {`{{currentUser}}`} - 현재 사용자
|
||||
<br />• {`{{currentTimestamp}}`} - 현재 시간
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user