워크플로우 restapi도 연결가능하고여러개 가능하게 구현시켜놓음

This commit is contained in:
leeheejin
2025-12-02 14:24:43 +09:00
parent 30e6595bf3
commit 9078873240
11 changed files with 989 additions and 50 deletions

View File

@@ -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>