실행 조건 구현
This commit is contained in:
parent
f50dd520ae
commit
3344a5785c
|
|
@ -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> = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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. 보안
|
||||
|
||||
|
|
|
|||
|
|
@ -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,7 +418,27 @@ 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">
|
||||
<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>
|
||||
)}
|
||||
|
||||
{/* 조건 행 */}
|
||||
<div className="flex items-center gap-2 rounded border bg-white p-2">
|
||||
<Select
|
||||
value={condition.field || ""}
|
||||
onValueChange={(value) => updateCondition(index, "field", value)}
|
||||
|
|
@ -475,8 +470,6 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
|
|||
<SelectItem value=">=">>=</SelectItem>
|
||||
<SelectItem value="<="><=</SelectItem>
|
||||
<SelectItem value="LIKE">LIKE</SelectItem>
|
||||
<SelectItem value="IN">IN</SelectItem>
|
||||
<SelectItem value="IS_NULL">IS NULL</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
|
|
@ -491,33 +484,11 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
|
|||
<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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue