일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- identifierForVender
- typescript
- web track
- Redux
- Completer
- code editor
- Babel standalone
- webrtc
- Three-fiber
- Three js
- uint8array
- androidId
- Flutter
- node
- jszip
- swagger-typescript-api
- FirebaseAnalytics
- userevent_tracker
- Excel
- Prism.js
- REST API
- Raycasting
- Image Resize typescript
- babel
- Game js
- KakaoMap
- uint16array
- react
- methodChannel
- RouteObserver
- Today
- Total
Never give up
Node - Google analytics (feat. Chart.js, Excel) 본문
이번에는 google analytics에서 축적된(?) 데이터를
chart js로 시각화 그리고 excel파일로 만들어서 다운받는 예제를 만들어봤습니다
먼저 google cloud platform에서 api활성화, 그리고 계정 생성, domain 설정(필자는 localhost:8080), 환경변수 등록이 필요합니다
자세한 내용은 링크를 참고해주세요
위 링크대로 api를 활성화 하고, credentials.json파일을 적당한곳(필자는 프로젝트 폴더)에 넣어줍니다
그 후 환경변수에 다음과 같이 등록해줍니다
(윈도우는 링크 참고 : https://cloud.google.com/docs/authentication/production)
server.js
require('dotenv').config()
const express = require('express')
const app = express()
const port = process.env.PORT || 8080
const show = require('./show')
const report = require('./report')
// http://localhost:8080/show/analytics.html <- show chart page
app.use('/show', show)
// http://localhost:8080/report/0 <- get report by date
// http://localhost:8080/report/1 <- get report by screen
// http://localhost:8080/report/file/excel <- make and download excel file
app.use('/report', report)
app.listen(port, () => {
console.log(`start! express server on port ${port}`)
})
일단 기본적인 셋팅 + route를 조금 셋팅 해줬는데
간단하게 정리해보자면
- 날짜별 report 가져오기 : http://localhost:8080/report/0
- 화면별 report 가져오기 : http://localhost:8080/report/1
- 차트 보여주기 : http://localhost:8080/show/analytics.html
- 엑셀파일 다운로드 : http://localhost:8080/report/file/excel
정도로 만들어봤습니다
report.js를 먼저 보자면
const express = require("express")
const router = express.Router()
const excel = require('./excel')
const { BetaAnalyticsDataClient } = require('@google-analytics/data');
async function runReport(name) {
const propertyId = process.env.PROPERTY_ID;
const analyticsDataClient = new BetaAnalyticsDataClient();
let reportData = []
const [response] = await analyticsDataClient.runReport({
property: `properties/${propertyId}`,
dateRanges: [
{
startDate: '7daysAgo',
endDate: 'yesterday',
},
],
dimensions: [
{
name: name,
}
],
metrics: [
{
name: 'activeUsers'
},
{
name: 'active1DayUsers'
},
{
name: 'active7DayUsers'
},
{
name: 'averageSessionDuration'
},
{
name: 'dauPerWau'
},
{
name: 'eventCountPerUser'
},
{
name: 'screenPageViewsPerSession'
},
{
name: 'totalUsers'
}
],
});
console.log('Report result:', response.rows.length);
response.rows.forEach((row, rowIndex) => {
// console.log({ rowIndex, row })
let data = { div: row.dimensionValues[0].value, list: [] }
row.metricValues.forEach((e, i) => {
switch (i) {
case 0:
data.list = [{ title: '앱 사용자 수', value: e.value }]
break
case 1:
data.list = [...data.list, { title: '1일 사용자 수', value: e.value }]
break
case 2:
data.list = [...data.list, { title: '1주 사용자 수', value: e.value }]
break
case 3:
data.list = [...data.list, { title: '앱 평균 사용시간(세션 유지 시간)', value: Math.round(e.value) }]
break
case 4:
data.list = [...data.list, { title: '1주 순 이용자수(dau)', value: Math.round(e.value) }]
break
case 5:
data.list = [...data.list, { title: '1인 당 이벤트(클릭 등) 횟수', value: Math.round(e.value) }]
break
case 6:
data.list = [...data.list, { title: '1인 당 페이지 뷰 수', value: Math.round(e.value) }]
break
case 7:
data.list = [...data.list, { title: '전체 유저 수', value: e.value }]
break
default:
console.log('??', e.value)
}
})
reportData = [...reportData, data]
});
reportData.sort((a, b) => a.div - b.div)
// report data = [{date: 날짜, list: [{title: metric, value: value}]}]
// console.log(JSON.stringify(reportData))
return reportData
}
// report 데이터 가져오기
// 0 : date, 1 : unifiedScreenName
router.get('/:no', async (req, res) => {
const no = req.params.no
let data
let code
try {
data = await runReport(no == 0 ? 'date' : 'unifiedScreenName')
code = 1
console.log({ data })
} catch (e) {
data = e
code = -1
console.log({ e })
}
res.send({ data, code })
})
// 현재 디렉토리에 analytics.xls 추가
router.get('/file/excel', async (req, res) => {
let data
let code
try {
data = {}
data.date = await runReport('date')
data.screen = await runReport('unifiedScreenName')
data.finished = () => {
console.log('Downloading is started')
res.download('./analytics.xls')
console.log('Downloading is finished')
}
excel.makeExcel(data)
code = 1
} catch (e) {
data = e
code = -1
res.send({ data, code })
}
})
module.exports = router
필자는 구글 report api콜을 위해 npm에서 @google-analytics/data를 이용했습니다
(링크 : https://www.npmjs.com/package/@google-analytics/data)
run report()함수에서 name에 따라 dimension값을 설정해주고,
각각 파라미터(metrics)로
- 앱 사용자 수
- 1일 사용자 수
- 1주 사용자 수
- 앱 평균 사용시간(세션 유지 시간)
- 1주 순 이용자수
- 1인 당 사용 이벤트 발생 수(클릭 등)
- 1인 당 페이지 뷰 수
- 전체 유저 수
를 받고 있습니다(참고로 필자는 한 주정도의 데이터를 확인중입니다)
그리고 제가 사용하기 좋은 형태의 데이터로 만들기 위해 switch문으로 적당히(?) 저장해줍니다
최종 데이터 타입은 [{div: 날짜, list: [{tittle: 항목, value: 트래킹된 값]}]…}로
Array와 Object가 중첩된 형태입니다 말로 풀어쓰자면..
Array안에 Object(날짜, 항목별 Array) 항목별 Array 내부에 Object는 항목 그리고 값이 들어갑니다
(플러터로 치자면.. List<Map<String, List<Map<String, dynamic>>>> )
그리고 router부분을 보면
1. /:no : no값에 따라서 date혹은 screen name을 보여줍니다
2. /file/excel : excel파일을 만들고 download를 해줍니다
excel로 만드는부분을 보면
excel.js
const reader = require('xlsx')
exports.makeExcel = (data) => {
const workBook = handler.getWorkBook()
// 날짜별 통계값 엑셀 형태로
const dateSheet = handler.getWorksheet(data.date)
// 페이지별 통계값 엑셀 형태로
const screenSheet = handler.getWorksheet(data.screen)
// sheet에 추가
reader.utils.book_append_sheet(workBook, dateSheet, handler.getSheetName('날짜'))
reader.utils.book_append_sheet(workBook, screenSheet, handler.getSheetName('페이지'))
// excel 파일 생성
// reader.writeFile(workBook, handler.getExcelFileName())
reader.writeFileAsync(handler.getExcelFileName(), workBook, null, () => {
console.log('Finished writing')
data.finished()
})
}
const handler = {
getWorkBook: () => {
return reader.utils.book_new()
},
getExcelFileName: () => {
return `analytics.xls`;
},
getSheetName: (name) => {
// 공백이면 순서대로
// ex: sheet1, sheet2
return name;
},
getExcelData: (data) => {
let sheetData = [['']]
data.forEach((row, rowIndex) => {
console.log({ row, rowIndex })
sheetData[0] = [...sheetData[0], row.div]
row.list.forEach((e, i) => {
console.log({ e, i })
if (rowIndex === 0) {
sheetData[i + 1] = [e.title, e.value]
} else {
sheetData[i + 1] = [...sheetData[i + 1], e.value]
}
})
})
return sheetData;
},
getWorksheet: (data) => {
console.log('getWorksheet : ', data.length)
return reader.utils.aoa_to_sheet(handler.getExcelData(data));
}
}
엑셀 관련 라이브러리를 찾던중 xlsx 라이브러리 이용자수가 가장 많아서 사용해봤습니다
(링크 : https://www.npmjs.com/package/xlsx)
위에서 만들어놓은 api로 가져온 데이터를 이용해서 보기 좋은 형태로 만들어서
(handler의 getExcelData 부분 참고)
book_append_sheet 함수를 이용해서 sheet에 추가해주고 writeFileAsync함수로 엑셀파일을 만들어줍니다
(그냥 writeFile은 생성완료 콜백이 따로 없어서 해당 함수를 사용했습니다)
그리고 생성이 완료되면 콜백을 이용해서 res.download(엑셀파일 경로)로 클라이언트 pc에 저장을 해줍니다
(데이터는 민감한(?)정보여서 일단 가려놨습니다)
show.js
const express = require("express")
const router = express.Router()
const fs = require('fs')
router.get('/analytics.html', (_,res) => {
fs.readFile('./analytics.html', (err, data) => {
if(err){
throw err
}
res.end(data)
})
})
module.exports = router
폴더 내에 analytics.html를 찾아서 보여주는 역할을 합니다
analytics.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>G9bon analytics</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.min.js"></script>
</head>
<body>
<script>
// 0: date, 1: screen
fetch('/report/0')
.then((res) => res.json())
.then((res) => {
if (res) {
console.log('done')
makeChart(res.data,'line')
}
})
.catch((e) => {
alert(e)
})
setTimeout(() => {
fetch('/report/1')
.then((res) => res.json())
.then((res) => {
if (res) {
console.log('done')
makeChart(res.data, 'bar')
}
})
.catch((e) => {
alert(e)
})
}, 3000);
const makeChart = (report, type) => {
let labels = []
let datasets = Array.from({ length: report[0].list.length }, (_) => {
return {
label: '', backgroundColor: type != 'line' ? [
'rgba(255, 99, 132, 0.8)',
'rgba(255, 159, 64, 0.8)',
'rgba(255, 205, 86, 0.8)',
'rgba(75, 192, 192, 0.8)',
'rgba(54, 162, 235, 0.8)',
'rgba(153, 102, 255, 0.8)',
'rgba(201, 203, 207, 0.8)',
'rgba(255, 99, 132, 0.5)',
'rgba(255, 159, 64, 0.5)',
'rgba(255, 205, 86, 0.5)',
'rgba(75, 192, 192, 0.5)',
'rgba(54, 162, 235, 0.5)',
'rgba(153, 102, 255, 0.5)',
'rgba(201, 203, 207, 0.5)',
'rgba(255, 99, 132, 0.2)',
'rgba(255, 159, 64, 0.2)',
'rgba(255, 205, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(201, 203, 207, 0.2)'
] : 'rgb(255, 99, 132)', borderColor: type != 'line' ? 'rgb(255,255,255)' : 'rgb(255, 99, 132)', data: []
}
})
report.forEach((reportList, rIndex) => {
labels = [...labels, reportList.div]
reportList.list.forEach((e, i) => {
console.log(i, e.title, datasets[i]?.data ?? [], datasets.length)
datasets[i].label = e.title
datasets[i].data = [...datasets[i]?.data ?? [], e.value]
})
console.log({ rIndex })
})
datasets.forEach((e, i) => {
const data = {
labels,
datasets: [e]
}
const config = {
type: type,
data: data,
options: {}
}
const div = document.createElement('div')
document.body.appendChild(div)
const canvas = document.createElement('canvas')
div.appendChild(canvas)
const myChart = new Chart(canvas, config)
})
}
</script>
</body>
</html>
cdn으로 chart js를 설치해주고, fetch를 통해 api콜을 한 이후에
결과값을 이용해서 makeChart()함수를 이용해 차트를 그려주는데
위에서 만든 데이터를 가지고 canvas에 chart js형식에 맞춰서 적당히(?) 넣어주고
append child로 하나씩 더해주는 형태로 만들어봤습니다
(마찬가지로 데이터는 민감한(?) 정보라 왼쪽은 최대한 가렸습니다)
하단에 더 많은 그래프가 있는데, 일단 잘 나온다는것만 보여주기용으로 하나만 첨부해봤습니다
(전체 소스 코드 링크 : https://github.com/devmemory/analytics)
작업하면서 데이터 타입이 지저분하다보니(?) 사용하기 좋은 형태로 가공하는데 시간을 조금 많이 쓴것 같습니다
(필자가 머리가 안좋아서..)
'WEB' 카테고리의 다른 글
Node - XLSX example (feat. scraping) (0) | 2022.05.11 |
---|---|
Node - Nodemailer example (2) | 2022.04.26 |
React, Node - Image upload example (0) | 2022.02.19 |
React - Kakaomap example (0) | 2022.02.19 |
React - html, css album page clone (0) | 2022.01.08 |