제어 관리 저장 액션에 논리연산자 추가
This commit is contained in:
parent
61aac5c5c3
commit
81d760532b
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue