이 패턴을 사용하여 데이터 웨어하우스(예: Snowflake, BigQuery, PostgreSQL)의 제품 데이터와 Twenty를 동기화 상태로 유지하세요.
워크플로우 구조
- 트리거: 일정에 따라
- 코드: 데이터 웨어하우스에 질의
- 코드(선택): 데이터를 배열로 형식 지정
- Iterator: 각 제품을 순회
- 레코드 업서트: Twenty에서 생성 또는 업데이트
단계 1: 트리거 일정 설정
데이터 최신성 요구에 맞는 빈도로 워크플로우가 실행되도록 설정하세요:
- 거의 실시간 동기화를 위해 5분마다
- 중요도가 낮은 데이터의 경우 매시간
- 배치 업데이트의 경우 매일
단계 2: 데이터 웨어하우스 질의
최근 데이터를 가져오기 위해 코드 액션을 추가합니다:
export const main = async () => {
const intervalMinutes = 10; // Match your schedule frequency
const cutoffTime = new Date(Date.now() - intervalMinutes * 60 * 1000).toISOString();
// Replace with your actual data warehouse connection
const response = await fetch("https://your-warehouse-api.com/query", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
query: `
SELECT id, name, sku, price, stock_quantity, updated_at
FROM products
WHERE updated_at >= '${cutoffTime}'
`
})
});
const data = await response.json();
return { products: data.results };
};
updated_at >= last X minutes로 필터링하여 최근에 변경된 레코드만 가져옵니다. 동기화를 효율적으로 유지할 수 있습니다.
단계 3: 데이터 형식 지정(선택)
웨어하우스가 변환이 필요한 형식으로 데이터를 반환하는 경우, 코드 액션을 하나 더 추가하세요. 일반적인 변환에는 타입 변환, 필드 이름 변경, 데이터 정리가 포함됩니다.
예시: 불리언 및 상태 필드를 포함한 사용자 데이터
export const main = async (params: {
users: any;
}): Promise<object> => {
const { users } = params;
const usersFormatted = typeof users === "string" ? JSON.parse(users) : users;
// Convert string "true"/"false" to actual booleans
const toBool = (v: any) => v === true || v === "true";
return {
users: usersFormatted.map((user) => ({
...user,
activityStatus: String(user.activityStatus).toUpperCase(),
isActiveLast30d: toBool(user.isActiveLast30d),
isActiveLast7d: toBool(user.isActiveLast7d),
isActiveLast24h: toBool(user.isActiveLast24h),
isTwenty: toBool(user.isTwenty),
})),
};
};
예시: 타입 변환이 포함된 제품 데이터
export const main = async (params: { products: any }) => {
const products = typeof params.products === "string"
? JSON.parse(params.products)
: params.products;
return {
products: products.map(product => ({
externalId: product.id,
name: product.name,
sku: product.sku,
price: parseFloat(product.price), // String → Number
stockQuantity: parseInt(product.stock_quantity),
isActive: product.status === "active" // String → Boolean
}))
};
};
예시: 날짜 및 통화 형식 지정
export const main = async (params: { deals: any }) => {
const deals = typeof params.deals === "string"
? JSON.parse(params.deals)
: params.deals;
return {
deals: deals.map(deal => ({
...deal,
// Convert Unix timestamp to ISO date
closedAt: deal.closed_timestamp
? new Date(deal.closed_timestamp * 1000).toISOString()
: null,
// Ensure amount is a number (remove currency symbols)
amount: parseFloat(String(deal.amount).replace(/[^0-9.-]/g, "")),
// Normalize stage names
stage: deal.stage?.toLowerCase().replace(/_/g, " ")
}))
};
};
일반적인 변환
| 소스 형식 | 대상 형식 | 코드 |
|---|
"true" / "false" | true / false | v === true || v === "true" |
"123.45" | 123.45 | parseFloat(value) |
"active" | "ACTIVE" | value.toUpperCase() |
1704067200 (Unix) | ISO 날짜 | new Date(v * 1000).toISOString() |
"$1,234.56" | 1234.56 | parseFloat(v.replace(/[^0-9.-]/g, "")) |
null / undefined | "" | value || "" |
단계 4: 제품 순회
Iterator 액션을 추가합니다:
배열의 각 제품을 순회합니다.
5단계: 각 레코드를 업서트하기
이터레이터 내부에 레코드 업서트 액션을 추가합니다:
| 설정 | 값 |
|---|
| 객체 | 사용자 지정 제품 객체 |
| 일치 기준 | External ID 또는 SKU(고유 식별자) |
| Name | {{iterator.item.name}} |
| SKU | {{iterator.item.sku}} |
| Price | {{iterator.item.price}} |
생성과 업데이트를 위한 분기 흐름을 따로 만들기보다 Upsert(업데이트 또는 생성)를 사용하세요. 구현이 더 빠르고 디버깅이 더 쉽습니다.
사용 예시
| 소스 | 데이터 |
|---|
| ERP 시스템 | 제품 카탈로그, 가격, 재고 |
| 전자상거래 플랫폼 | 주문, 고객, 제품 업데이트 |
| 데이터 웨어하우스 | 집계 지표, 보강된 데이터 |
| 재고 시스템 | 재고 수준, 재주문 알림 |
관련 항목