- Add desktop/ directory for Claude Desktop (Phase 1-5: analysis, design, docs) - Add code/ directory for Claude Code (Phase 6-7: automation, audit) - Create SKILL.md with YAML frontmatter for Desktop compatibility - Create CLAUDE.md for Code automation workflows - Organize references by platform scope with shared files duplicated - Add templates for tagging plan and event taxonomy - Include README.md with overview and usage guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
284 lines
7.4 KiB
Markdown
284 lines
7.4 KiB
Markdown
# Phase 7: Event Lookup & Parameter Library Web App
|
|
|
|
Google Apps Script를 활용한 Event Taxonomy Sheet 기반 업무 지원용 앱 배포.
|
|
|
|
## Objectives
|
|
|
|
1. Event Taxonomy 데이터 조회 앱
|
|
2. 실시간 파라미터 참조 도구
|
|
3. 비개발자용 Self-service 도구
|
|
4. 팀 간 일관된 이벤트 정보 공유
|
|
|
|
## App Architecture
|
|
|
|
```
|
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
│ Google Sheets │────▶│ Apps Script │────▶│ Web App │
|
|
│ (Taxonomy DB) │ │ (Backend) │ │ (Frontend) │
|
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
```
|
|
|
|
## Google Sheets Data Source
|
|
|
|
### Required Sheets
|
|
- Events Master
|
|
- Parameters Reference
|
|
- Custom Definitions
|
|
- Platform Mapping
|
|
|
|
### Column Requirements
|
|
→ Phase 4 참조: [phase4-taxonomy.md](phase4-taxonomy.md)
|
|
|
|
## Apps Script Setup
|
|
|
|
### 1. Project Creation
|
|
```
|
|
1. Google Sheets 열기
|
|
2. Extensions > Apps Script
|
|
3. 프로젝트 이름 설정
|
|
```
|
|
|
|
### 2. Core Functions
|
|
|
|
#### Data Retrieval
|
|
```javascript
|
|
function getEventsData() {
|
|
const sheet = SpreadsheetApp.getActiveSpreadsheet()
|
|
.getSheetByName('Events Master');
|
|
const data = sheet.getDataRange().getValues();
|
|
const headers = data[0];
|
|
|
|
return data.slice(1).map(row => {
|
|
const obj = {};
|
|
headers.forEach((header, i) => {
|
|
obj[header] = row[i];
|
|
});
|
|
return obj;
|
|
});
|
|
}
|
|
|
|
function getParametersData() {
|
|
const sheet = SpreadsheetApp.getActiveSpreadsheet()
|
|
.getSheetByName('Parameters Reference');
|
|
const data = sheet.getDataRange().getValues();
|
|
const headers = data[0];
|
|
|
|
return data.slice(1).map(row => {
|
|
const obj = {};
|
|
headers.forEach((header, i) => {
|
|
obj[header] = row[i];
|
|
});
|
|
return obj;
|
|
});
|
|
}
|
|
```
|
|
|
|
#### Search Function
|
|
```javascript
|
|
function searchEvents(query) {
|
|
const events = getEventsData();
|
|
const searchTerm = query.toLowerCase();
|
|
|
|
return events.filter(event =>
|
|
event.event_name.toLowerCase().includes(searchTerm) ||
|
|
event.event_category.toLowerCase().includes(searchTerm) ||
|
|
event.notes.toLowerCase().includes(searchTerm)
|
|
);
|
|
}
|
|
|
|
function getEventDetails(eventName) {
|
|
const events = getEventsData();
|
|
const params = getParametersData();
|
|
|
|
const event = events.find(e => e.event_name === eventName);
|
|
if (!event) return null;
|
|
|
|
const eventParams = event.parameters.split(',').map(p => p.trim());
|
|
const paramDetails = params.filter(p =>
|
|
eventParams.includes(p.parameter_name)
|
|
);
|
|
|
|
return {
|
|
event: event,
|
|
parameters: paramDetails
|
|
};
|
|
}
|
|
```
|
|
|
|
### 3. Web App Handler
|
|
|
|
```javascript
|
|
function doGet(e) {
|
|
return HtmlService.createHtmlOutputFromFile('index')
|
|
.setTitle('GTM Event Lookup')
|
|
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
|
|
}
|
|
|
|
function doPost(e) {
|
|
const action = e.parameter.action;
|
|
|
|
switch(action) {
|
|
case 'search':
|
|
return ContentService.createTextOutput(
|
|
JSON.stringify(searchEvents(e.parameter.query))
|
|
).setMimeType(ContentService.MimeType.JSON);
|
|
|
|
case 'details':
|
|
return ContentService.createTextOutput(
|
|
JSON.stringify(getEventDetails(e.parameter.eventName))
|
|
).setMimeType(ContentService.MimeType.JSON);
|
|
|
|
default:
|
|
return ContentService.createTextOutput(
|
|
JSON.stringify({error: 'Invalid action'})
|
|
).setMimeType(ContentService.MimeType.JSON);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. HTML Frontend (index.html)
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<base target="_top">
|
|
<style>
|
|
body { font-family: 'Roboto', sans-serif; padding: 20px; max-width: 800px; margin: 0 auto; }
|
|
.search-box { width: 100%; padding: 12px; font-size: 16px; margin-bottom: 20px; }
|
|
.event-card { border: 1px solid #ddd; padding: 16px; margin: 10px 0; border-radius: 8px; }
|
|
.event-name { font-size: 18px; font-weight: bold; color: #1a73e8; }
|
|
.param-badge { background: #e8f0fe; color: #1967d2; padding: 4px 8px; border-radius: 4px; margin: 2px; display: inline-block; }
|
|
.category { color: #5f6368; font-size: 14px; }
|
|
.priority-p1 { border-left: 4px solid #ea4335; }
|
|
.priority-p2 { border-left: 4px solid #fbbc04; }
|
|
.priority-p3 { border-left: 4px solid #34a853; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>🏷️ GTM Event Lookup</h1>
|
|
<input type="text" class="search-box" id="searchInput" placeholder="이벤트명 또는 카테고리 검색...">
|
|
<div id="results"></div>
|
|
|
|
<script>
|
|
const searchInput = document.getElementById('searchInput');
|
|
const results = document.getElementById('results');
|
|
|
|
searchInput.addEventListener('input', debounce(function() {
|
|
const query = this.value;
|
|
if (query.length < 2) {
|
|
results.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
google.script.run
|
|
.withSuccessHandler(displayResults)
|
|
.searchEvents(query);
|
|
}, 300));
|
|
|
|
function displayResults(events) {
|
|
results.innerHTML = events.map(event => `
|
|
<div class="event-card priority-${event.priority.toLowerCase()}">
|
|
<div class="event-name">${event.event_name}</div>
|
|
<div class="category">${event.event_category} | ${event.priority}</div>
|
|
<div style="margin-top: 10px;">
|
|
${event.parameters.split(',').map(p =>
|
|
`<span class="param-badge">${p.trim()}</span>`
|
|
).join('')}
|
|
</div>
|
|
<div style="margin-top: 10px; color: #5f6368;">${event.notes}</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function debounce(func, wait) {
|
|
let timeout;
|
|
return function(...args) {
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(() => func.apply(this, args), wait);
|
|
};
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
## Deployment
|
|
|
|
### 1. Deploy as Web App
|
|
```
|
|
1. Deploy > New deployment
|
|
2. Type: Web app
|
|
3. Execute as: Me
|
|
4. Access: Anyone within organization (또는 Anyone)
|
|
5. Deploy
|
|
```
|
|
|
|
### 2. Get Web App URL
|
|
```
|
|
배포 후 제공되는 URL 복사
|
|
https://script.google.com/macros/s/[ID]/exec
|
|
```
|
|
|
|
### 3. Share with Team
|
|
- URL을 Notion/Slack에 공유
|
|
- 북마크 권장
|
|
|
|
## Features Roadmap
|
|
|
|
### Phase 1 (MVP)
|
|
- [x] 이벤트 검색
|
|
- [x] 파라미터 조회
|
|
- [x] 우선순위 필터
|
|
|
|
### Phase 2
|
|
- [ ] DataLayer 코드 스니펫 복사
|
|
- [ ] 플랫폼별 코드 예시
|
|
- [ ] 최근 조회 히스토리
|
|
|
|
### Phase 3
|
|
- [ ] 이벤트 추가/수정 폼
|
|
- [ ] 변경 이력 추적
|
|
- [ ] Slack 알림 연동
|
|
|
|
## Maintenance
|
|
|
|
### Data Update
|
|
1. Google Sheets 원본 데이터 수정
|
|
2. Web App 자동 반영 (실시간)
|
|
|
|
### Code Update
|
|
1. Apps Script 수정
|
|
2. Deploy > Manage deployments
|
|
3. New version 배포
|
|
|
|
### Backup
|
|
- Google Sheets 자동 버전 히스토리
|
|
- 주기적 수동 백업 권장
|
|
|
|
## Access Control
|
|
|
|
| Role | Access Level |
|
|
|------|--------------|
|
|
| Admin | 시트 편집 + 스크립트 수정 |
|
|
| Editor | 시트 편집 |
|
|
| Viewer | Web App 조회만 |
|
|
|
|
## Troubleshooting
|
|
|
|
| Issue | Solution |
|
|
|-------|----------|
|
|
| 앱 로딩 느림 | 데이터 캐싱 구현 |
|
|
| 검색 결과 없음 | 검색어 확인, 데이터 존재 확인 |
|
|
| 권한 오류 | 배포 설정에서 접근 권한 확인 |
|
|
| 데이터 미반영 | 시트 새로고침, 캐시 클리어 |
|
|
|
|
## Integration with Notion
|
|
|
|
Web App URL을 Notion에 embed:
|
|
```
|
|
/embed [Web App URL]
|
|
```
|
|
|
|
또는 iframe으로 직접 삽입.
|