feat(gtm-guardian): Reorganize skill with dual-platform structure
- 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>
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
# 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으로 직접 삽입.
|
||||
Reference in New Issue
Block a user