실행 조건 구현

This commit is contained in:
hyeonsu 2025-09-12 11:33:54 +09:00
parent f50dd520ae
commit 3344a5785c
5 changed files with 79 additions and 177 deletions

View File

@ -16,7 +16,7 @@ export async function testConditionalConnection(
const { diagramId } = req.params;
const { testData } = req.body;
const companyCode = req.user?.company_code;
const companyCode = req.user?.companyCode;
if (!companyCode) {
const response: ApiResponse<null> = {
@ -86,7 +86,7 @@ export async function executeConditionalActions(
const { diagramId } = req.params;
const { triggerType, tableName, data } = req.body;
const companyCode = req.user?.company_code;
const companyCode = req.user?.companyCode;
if (!companyCode) {
const response: ApiResponse<null> = {

View File

@ -1,5 +1,5 @@
import { PrismaClient } from "@prisma/client";
import { logger } from "../config/logger.js";
import { logger } from "../utils/logger";
const prisma = new PrismaClient();
@ -9,24 +9,7 @@ interface ConditionNode {
operator?: "AND" | "OR";
children?: ConditionNode[];
field?: string;
operator_type?:
| "="
| "!="
| ">"
| "<"
| ">="
| "<="
| "LIKE"
| "NOT_LIKE"
| "CONTAINS"
| "STARTS_WITH"
| "ENDS_WITH"
| "IN"
| "NOT_IN"
| "IS_NULL"
| "IS_NOT_NULL"
| "BETWEEN"
| "NOT_BETWEEN";
operator_type?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE";
value?: any;
dataType?: string;
}
@ -164,9 +147,9 @@ export class EventTriggerService {
const errors: string[] = [];
try {
const control = diagram.control as ConditionControl;
const category = diagram.category as ConnectionCategory;
const plan = diagram.plan as ExecutionPlan;
const control = diagram.control as unknown as ConditionControl;
const category = diagram.category as unknown as ConnectionCategory;
const plan = diagram.plan as unknown as ExecutionPlan;
logger.info(
`Executing diagram ${diagram.diagram_id} (${diagram.diagram_name})`
@ -302,38 +285,6 @@ export class EventTriggerService {
return Number(fieldValue) <= Number(value);
case "LIKE":
return String(fieldValue).includes(String(value));
case "NOT_LIKE":
return !String(fieldValue).includes(String(value));
case "CONTAINS":
return String(fieldValue)
.toLowerCase()
.includes(String(value).toLowerCase());
case "STARTS_WITH":
return String(fieldValue).startsWith(String(value));
case "ENDS_WITH":
return String(fieldValue).endsWith(String(value));
case "IN":
return Array.isArray(value) && value.includes(fieldValue);
case "NOT_IN":
return Array.isArray(value) && !value.includes(fieldValue);
case "IS_NULL":
return fieldValue == null || fieldValue === undefined;
case "IS_NOT_NULL":
return fieldValue != null && fieldValue !== undefined;
case "BETWEEN":
if (Array.isArray(value) && value.length === 2) {
const numValue = Number(fieldValue);
return numValue >= Number(value[0]) && numValue <= Number(value[1]);
}
return false;
case "NOT_BETWEEN":
if (Array.isArray(value) && value.length === 2) {
const numValue = Number(fieldValue);
return !(
numValue >= Number(value[0]) && numValue <= Number(value[1])
);
}
return false;
default:
return false;
}
@ -553,7 +504,7 @@ export class EventTriggerService {
throw new Error(`Diagram ${diagramId} not found`);
}
const control = diagram.control as ConditionControl;
const control = diagram.control as unknown as ConditionControl;
// 조건 평가만 수행
const conditionMet = control.conditionTree

View File

@ -2,13 +2,13 @@
## 📋 프로젝트 개요
현재 DataFlow 시스템에서 3가지 연결 종류를 지원하고 있으며, 이 중 **데이터 저장**과 **외부 호출** 기능에 조건부 실행 로직을 추가해야 합니다.
현재 DataFlow 시스템에서 3가지 연결 종류를 지원하고 있으며, 이 중 **데이터 저장**과 **외부 호출** 기능에 실행 조건 로직을 추가해야 합니다.
### 현재 연결 종류
1. **단순 키값 연결** - 조건 설정 불필요 (기존 방식 유지)
2. **데이터 저장** - 조건부 실행 필요 ✨
3. **외부 호출** - 조건부 실행 필요 ✨
2. **데이터 저장** - 실행 조건 설정 필요 ✨
3. **외부 호출** - 실행 조건 설정 필요 ✨
## 🎯 기능 요구사항
@ -78,9 +78,7 @@ CREATE INDEX idx_dataflow_category_type ON dataflow_diagrams USING GIN ((categor
```json
{
"type": "data-save", // "simple-key" | "data-save" | "external-call"
"rollbackOnError": true,
"enableLogging": true
"type": "data-save" // "simple-key" | "data-save" | "external-call"
}
```
@ -265,7 +263,6 @@ router.post("/diagrams/:id/execute-actions", async (req, res) => {
- 트랜잭션 롤백 지원
- 부분 실패 시 복구 메커니즘
- 상세한 실행 로그 저장
### 3. 보안

View File

@ -126,10 +126,7 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
const [selectedToColumns, setSelectedToColumns] = useState<string[]>([]);
// 조건부 연결을 위한 새로운 상태들
const [triggerType, setTriggerType] = useState<"insert" | "update" | "delete" | "insert_update">("insert");
const [conditions, setConditions] = useState<ConditionNode[]>([]);
const [rollbackOnError, setRollbackOnError] = useState(true);
const [enableLogging, setEnableLogging] = useState(true);
// 테이블 목록 로드
useEffect(() => {
@ -291,7 +288,7 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
const conditionalSettings = isConditionalConnection()
? {
control: {
triggerType,
triggerType: "insert",
conditionTree:
conditions.length > 0
? {
@ -303,8 +300,6 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
},
category: {
type: config.connectionType,
rollbackOnError,
enableLogging,
},
plan: {
sourceTable: fromTableName,
@ -378,6 +373,7 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
operator_type: "=",
value: "",
dataType: "string",
operator: "AND", // 기본값으로 AND 설정
};
setConditions([...conditions, newCondition]);
};
@ -399,28 +395,7 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
<div className="rounded-lg border border-l-4 border-l-purple-500 bg-purple-50/30 p-4">
<div className="mb-4 flex items-center gap-2">
<Zap className="h-4 w-4 text-purple-500" />
<span className="text-sm font-medium"> </span>
</div>
{/* 트리거 타입 선택 */}
<div className="mb-4">
<Label htmlFor="triggerType" className="mb-2 block text-sm font-medium">
</Label>
<Select
value={triggerType}
onValueChange={(value: "insert" | "update" | "delete" | "insert_update") => setTriggerType(value)}
>
<SelectTrigger className="text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="insert"> (INSERT)</SelectItem>
<SelectItem value="update"> (UPDATE)</SelectItem>
<SelectItem value="delete"> (DELETE)</SelectItem>
<SelectItem value="insert_update">/ (INSERT/UPDATE)</SelectItem>
</SelectContent>
</Select>
<span className="text-sm font-medium"> </span>
</div>
{/* 실행 조건 설정 */}
@ -443,81 +418,77 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
</div>
) : (
conditions.map((condition, index) => (
<div key={index} className="flex items-center gap-2 rounded border bg-white p-2">
<Select
value={condition.field || ""}
onValueChange={(value) => updateCondition(index, "field", value)}
>
<SelectTrigger className="h-8 flex-1 text-xs">
<SelectValue placeholder="필드 선택" />
</SelectTrigger>
<SelectContent>
{fromTableColumns.map((column) => (
<SelectItem key={column.columnName} value={column.columnName}>
{column.columnName} ({column.dataType})
</SelectItem>
))}
</SelectContent>
</Select>
<div key={index}>
{/* 첫 번째 조건이 아닐 때 AND/OR 연산자를 위에 표시 */}
{index > 0 && (
<div className="flex justify-center py-1">
<Select
value={conditions[index - 1]?.operator || "AND"}
onValueChange={(value: "AND" | "OR") => updateCondition(index - 1, "operator", value)}
>
<SelectTrigger className="h-8 w-20 border-blue-200 bg-blue-50 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="AND">AND</SelectItem>
<SelectItem value="OR">OR</SelectItem>
</SelectContent>
</Select>
</div>
)}
<Select
value={condition.operator_type || "="}
onValueChange={(value) => updateCondition(index, "operator_type", value)}
>
<SelectTrigger className="h-8 w-20 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="=">=</SelectItem>
<SelectItem value="!=">!=</SelectItem>
<SelectItem value=">">&gt;</SelectItem>
<SelectItem value="<">&lt;</SelectItem>
<SelectItem value=">=">&gt;=</SelectItem>
<SelectItem value="<=">&lt;=</SelectItem>
<SelectItem value="LIKE">LIKE</SelectItem>
<SelectItem value="IN">IN</SelectItem>
<SelectItem value="IS_NULL">IS NULL</SelectItem>
</SelectContent>
</Select>
{/* 조건 행 */}
<div className="flex items-center gap-2 rounded border bg-white p-2">
<Select
value={condition.field || ""}
onValueChange={(value) => updateCondition(index, "field", value)}
>
<SelectTrigger className="h-8 flex-1 text-xs">
<SelectValue placeholder="필드 선택" />
</SelectTrigger>
<SelectContent>
{fromTableColumns.map((column) => (
<SelectItem key={column.columnName} value={column.columnName}>
{column.columnName} ({column.dataType})
</SelectItem>
))}
</SelectContent>
</Select>
<Input
placeholder="값"
value={condition.value || ""}
onChange={(e) => updateCondition(index, "value", e.target.value)}
className="h-8 flex-1 text-xs"
/>
<Select
value={condition.operator_type || "="}
onValueChange={(value) => updateCondition(index, "operator_type", value)}
>
<SelectTrigger className="h-8 w-20 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="=">=</SelectItem>
<SelectItem value="!=">!=</SelectItem>
<SelectItem value=">">&gt;</SelectItem>
<SelectItem value="<">&lt;</SelectItem>
<SelectItem value=">=">&gt;=</SelectItem>
<SelectItem value="<=">&lt;=</SelectItem>
<SelectItem value="LIKE">LIKE</SelectItem>
</SelectContent>
</Select>
<Button size="sm" variant="ghost" onClick={() => removeCondition(index)} className="h-8 w-8 p-0">
<Trash2 className="h-3 w-3" />
</Button>
<Input
placeholder="값"
value={condition.value || ""}
onChange={(e) => updateCondition(index, "value", e.target.value)}
className="h-8 flex-1 text-xs"
/>
<Button size="sm" variant="ghost" onClick={() => removeCondition(index)} className="h-8 w-8 p-0">
<Trash2 className="h-3 w-3" />
</Button>
</div>
</div>
))
)}
</div>
</div>
{/* 추가 옵션 */}
<div className="grid grid-cols-2 gap-4 border-t pt-2">
<label className="flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={rollbackOnError}
onChange={(e) => setRollbackOnError(e.target.checked)}
className="rounded"
/>
</label>
<label className="flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={enableLogging}
onChange={(e) => setEnableLogging(e.target.checked)}
className="rounded"
/>
</label>
</div>
</div>
);
};

View File

@ -13,24 +13,7 @@ export interface ConditionNode {
operator?: "AND" | "OR";
children?: ConditionNode[];
field?: string;
operator_type?:
| "="
| "!="
| ">"
| "<"
| ">="
| "<="
| "LIKE"
| "NOT_LIKE"
| "CONTAINS"
| "STARTS_WITH"
| "ENDS_WITH"
| "IN"
| "NOT_IN"
| "IS_NULL"
| "IS_NOT_NULL"
| "BETWEEN"
| "NOT_BETWEEN";
operator_type?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE";
value?: any;
dataType?: string;
}