제어 관리 저장 액션에 논리연산자 추가

This commit is contained in:
hyeonsu 2025-09-19 16:42:33 +09:00
parent 61aac5c5c3
commit 81d760532b
4 changed files with 158 additions and 83 deletions

View File

@ -19,6 +19,7 @@ export interface ControlAction {
id: string;
name: string;
actionType: "insert" | "update" | "delete";
logicalOperator?: "AND" | "OR"; // 액션 간 논리 연산자 (첫 번째 액션 제외)
conditions: ControlCondition[];
fieldMappings: {
sourceField?: string;
@ -136,17 +137,41 @@ export class DataflowControlService {
};
}
// 액션 실행
// 액션 실행 (논리 연산자 지원)
const executedActions = [];
const errors = [];
let previousActionSuccess = false;
let shouldSkipRemainingActions = false;
for (let i = 0; i < targetPlan.actions.length; i++) {
const action = targetPlan.actions[i];
for (const action of targetPlan.actions) {
try {
// 논리 연산자에 따른 실행 여부 결정
if (
i > 0 &&
action.logicalOperator === "OR" &&
previousActionSuccess
) {
console.log(
`⏭️ OR 조건으로 인해 액션 건너뛰기: ${action.name} (이전 액션 성공)`
);
continue;
}
if (shouldSkipRemainingActions && action.logicalOperator === "AND") {
console.log(
`⏭️ 이전 액션 실패로 인해 AND 체인 액션 건너뛰기: ${action.name}`
);
continue;
}
console.log(`⚡ 액션 실행: ${action.name} (${action.actionType})`);
console.log(`📋 액션 상세 정보:`, {
actionId: action.id,
actionName: action.name,
actionType: action.actionType,
logicalOperator: action.logicalOperator,
conditions: action.conditions,
fieldMappings: action.fieldMappings,
});
@ -163,6 +188,10 @@ export class DataflowControlService {
console.log(
`⚠️ 액션 조건 미충족: ${actionConditionResult.reason}`
);
previousActionSuccess = false;
if (action.logicalOperator === "AND") {
shouldSkipRemainingActions = true;
}
continue;
}
}
@ -173,11 +202,19 @@ export class DataflowControlService {
actionName: action.name,
result: actionResult,
});
previousActionSuccess = true;
shouldSkipRemainingActions = false; // 성공했으므로 다시 실행 가능
} catch (error) {
console.error(`❌ 액션 실행 오류: ${action.name}`, error);
const errorMessage =
error instanceof Error ? error.message : String(error);
errors.push(`액션 '${action.name}' 실행 오류: ${errorMessage}`);
previousActionSuccess = false;
if (action.logicalOperator === "AND") {
shouldSkipRemainingActions = true;
}
}
}

View File

@ -669,6 +669,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
id: action.id as string,
name: action.name as string,
actionType: action.actionType as "insert" | "update" | "delete" | "upsert",
logicalOperator: action.logicalOperator as "AND" | "OR" | undefined, // 논리 연산자 추가
fieldMappings: ((action.fieldMappings as Record<string, unknown>[]) || []).map(
(mapping: Record<string, unknown>) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars

View File

@ -38,6 +38,8 @@ export const DataSaveSettings: React.FC<DataSaveSettingsProps> = ({
id: `action_${settings.actions.length + 1}`,
name: `액션 ${settings.actions.length + 1}`,
actionType: "insert" as const,
// 첫 번째 액션이 아니면 기본적으로 AND 연산자 추가
...(settings.actions.length > 0 && { logicalOperator: "AND" as const }),
fieldMappings: [],
conditions: [],
splitConfig: {
@ -60,6 +62,12 @@ export const DataSaveSettings: React.FC<DataSaveSettingsProps> = ({
const removeAction = (actionIndex: number) => {
const newActions = settings.actions.filter((_, i) => i !== actionIndex);
// 첫 번째 액션을 삭제했다면, 새로운 첫 번째 액션의 logicalOperator 제거
if (actionIndex === 0 && newActions.length > 0) {
delete newActions[0].logicalOperator;
}
onSettingsChange({ ...settings, actions: newActions });
};
@ -87,104 +95,132 @@ export const DataSaveSettings: React.FC<DataSaveSettingsProps> = ({
) : (
<div className="space-y-3">
{settings.actions.map((action, actionIndex) => (
<div key={action.id} className="rounded border bg-white p-3">
<div className="mb-3 flex items-center justify-between">
<Input
value={action.name}
onChange={(e) => updateAction(actionIndex, "name", e.target.value)}
className="h-7 flex-1 text-xs font-medium"
placeholder="액션 이름"
/>
<Button size="sm" variant="ghost" onClick={() => removeAction(actionIndex)} className="h-7 w-7 p-0">
<Trash2 className="h-3 w-3" />
</Button>
</div>
<div className="grid grid-cols-1 gap-3">
{/* 액션 타입 */}
<div>
<Label className="text-xs"> </Label>
<Select
value={action.actionType}
onValueChange={(value: "insert" | "update" | "delete" | "upsert") =>
updateAction(actionIndex, "actionType", value)
}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="insert">INSERT</SelectItem>
<SelectItem value="update">UPDATE</SelectItem>
<SelectItem value="delete">DELETE</SelectItem>
<SelectItem value="upsert">UPSERT</SelectItem>
</SelectContent>
</Select>
<div key={action.id}>
{/* 첫 번째 액션이 아닌 경우 논리 연산자 표시 */}
{actionIndex > 0 && (
<div className="mb-2 flex items-center justify-center">
<div className="flex items-center gap-2 rounded-lg bg-gray-100 px-3 py-1">
<span className="text-xs text-gray-600"> :</span>
<Select
value={action.logicalOperator || "AND"}
onValueChange={(value: "AND" | "OR") => updateAction(actionIndex, "logicalOperator", value)}
>
<SelectTrigger className="h-8 w-20 text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="AND">AND</SelectItem>
<SelectItem value="OR">OR</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
{/* 액션별 개별 실행 조건 */}
<ActionConditionsSection
action={action}
actionIndex={actionIndex}
settings={settings}
onSettingsChange={onSettingsChange}
fromTableColumns={fromTableColumns}
toTableColumns={toTableColumns}
fromTableName={fromTableName}
toTableName={toTableName}
/>
{/* 데이터 분할 설정 - DELETE 액션은 제외 */}
{action.actionType !== "delete" && (
<ActionSplitConfig
action={action}
actionIndex={actionIndex}
settings={settings}
onSettingsChange={onSettingsChange}
fromTableColumns={fromTableColumns}
toTableColumns={toTableColumns}
/>
)}
{/* 필드 매핑 - DELETE 액션은 제외 */}
{action.actionType !== "delete" && (
<ActionFieldMappings
<div className="rounded border bg-white p-3">
<div className="mb-3 flex items-center justify-between">
<Input
value={action.name}
onChange={(e) => updateAction(actionIndex, "name", e.target.value)}
className="h-7 flex-1 text-xs font-medium"
placeholder="액션 이름"
/>
<Button
size="sm"
variant="ghost"
onClick={() => removeAction(actionIndex)}
className="h-7 w-7 p-0"
>
<Trash2 className="h-3 w-3" />
</Button>
</div>
<div className="grid grid-cols-1 gap-3">
{/* 액션 타입 */}
<div>
<Label className="text-xs"> </Label>
<Select
value={action.actionType}
onValueChange={(value: "insert" | "update" | "delete" | "upsert") =>
updateAction(actionIndex, "actionType", value)
}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="insert">INSERT</SelectItem>
<SelectItem value="update">UPDATE</SelectItem>
<SelectItem value="delete">DELETE</SelectItem>
<SelectItem value="upsert">UPSERT</SelectItem>
</SelectContent>
</Select>
</div>
</div>
{/* 액션별 개별 실행 조건 */}
<ActionConditionsSection
action={action}
actionIndex={actionIndex}
settings={settings}
onSettingsChange={onSettingsChange}
availableTables={availableTables}
tableColumnsCache={tableColumnsCache}
fromTableColumns={fromTableColumns}
toTableColumns={toTableColumns}
fromTableName={fromTableName}
toTableName={toTableName}
/>
)}
{/* DELETE 액션일 때 안내 메시지 */}
{action.actionType === "delete" && (
<div className="mt-3">
<div className="rounded border border-blue-200 bg-blue-50 p-3 text-xs text-blue-700">
<div className="flex items-start gap-2">
<span></span>
<div>
<div className="font-medium">DELETE </div>
<div className="mt-1">
DELETE <strong></strong> .
<br />
설정: 불필요 ( )
<br />
매핑: 불필요 ( )
<br />
.
{/* 데이터 분할 설정 - DELETE 액션은 제외 */}
{action.actionType !== "delete" && (
<ActionSplitConfig
action={action}
actionIndex={actionIndex}
settings={settings}
onSettingsChange={onSettingsChange}
fromTableColumns={fromTableColumns}
toTableColumns={toTableColumns}
/>
)}
{/* 필드 매핑 - DELETE 액션은 제외 */}
{action.actionType !== "delete" && (
<ActionFieldMappings
action={action}
actionIndex={actionIndex}
settings={settings}
onSettingsChange={onSettingsChange}
availableTables={availableTables}
tableColumnsCache={tableColumnsCache}
fromTableColumns={fromTableColumns}
toTableColumns={toTableColumns}
fromTableName={fromTableName}
toTableName={toTableName}
/>
)}
{/* DELETE 액션일 때 안내 메시지 */}
{action.actionType === "delete" && (
<div className="mt-3">
<div className="rounded border border-blue-200 bg-blue-50 p-3 text-xs text-blue-700">
<div className="flex items-start gap-2">
<span></span>
<div>
<div className="font-medium">DELETE </div>
<div className="mt-1">
DELETE <strong></strong> .
<br />
설정: 불필요 ( )
<br />
매핑: 불필요 ( )
<br />
.
</div>
</div>
</div>
</div>
</div>
</div>
)}
)}
</div>
</div>
))}
</div>

View File

@ -48,6 +48,7 @@ export interface DataSaveSettings {
id: string;
name: string;
actionType: "insert" | "update" | "delete" | "upsert";
logicalOperator?: "AND" | "OR"; // 액션 간 논리 연산자 (첫 번째 액션 제외)
conditions?: ConditionNode[];
fieldMappings: Array<{
sourceTable?: string;