본문 바로가기

Data & MarTech/Google Marketing Platform

[GA4] Rechart를 활용한 GA4 데이터 시각화 구현하기

반응형

 

Rechart를 활용한 GA4 데이터 시각화 구현하기

루커스튜디오(Looker Studio)태블로(Tableau)등에서 구글 애널리틱스 데이터를 연동하여 시각화 대시보드를 구현하는 것은 생산성 측면에서 가장 용이한 방법이라고 생각합니다. 그런 점에서 시각화 대시보드를 개발할 경우 목적과 활용(사용자)방법을 고려하는 것이 중요합니다. 특히 개발을 통해 시각화 대시보드를 구현할 경우 개발에 소요되는 리소스, 시간, 주사용자 등의 요소를 감안하여 공통적인 거시지표전달을 목적으로 사용하는 것을 권장드립니다. 

 

개발환경 설정

저는 yarn을 패키지 관리나 도구로 사용하여 프로젝트를 생성하였습니다. yarn을 사용하지 않는 경우 npm을 그대로 사용하셔도 됩니다.

프로젝트 생성

yarn create react-app <your-app-name>

리액트 최신 문서의 프로젝트 생성 방법이 다를 수 있으니, 이 부분 감안하여 테스트에 참고하시기 바랍니다.

 

Start a New React Project – React

The library for web and native user interfaces

react.dev

 

GCP 환경 설정

OAuth 동의 화면: API 및 서비스 > OAuth 동의화면을 선택한 다음 OAuth 동의 화면 설정에 필요한 값을 등록합니다. 

OAuth 동의 화면

 

앱 도메인에는 테스트를 위해 아래와 같이 모두 로컬호스트 주소를 입력하였습니다.

앱 도메인 세팅

사용자 인증 정보에도 설정을 진행합니다.

사용자 인증 정보

사용자 인증 정보에서 생성된 클라이언트 ID는 GA4 연동 시 필요한 정보입니다.

관련 모듈 설치

Rechart 설치
yarn add rechart
gapi-script 설치
yarn add gapi-script

 

React 컴포넌트 개발

컴포넌트에 Google Login / Logout 기능과 구글 애널리틱스 데이터를 연동하는 기능을 구현합니다.

모듈 및 구글 연동에 필요한 ID 정보 선언

  • CLIENT_ID: GCP 사용자 인증정보에서 확인 (위 내용 중 GCP 환경 설정 참고)
  • DISCOVERY_DOCS 및 SCOPES: 'API 사용해보기' 설정 참고
  • PROPERTY_ID: 구글 애널리틱스 > Admin > Property > Property Settings에서 복사
import React, { useState, useEffect } from 'react';
import { LineChart, AreaChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Area, Line, ResponsiveContainer } from 'recharts';
import { gapi } from 'gapi-script';

const CLIENT_ID = '<your client id>';
const DISCOVERY_DOCS = ['https://analyticsdata.googleapis.com/$discovery/rest?version=v1beta'];
const SCOPES = 'https://www.googleapis.com/auth/analytics https://www.googleapis.com/auth/analytics.readonly';
const PROPERTY_ID='<your GA4 property id>';

//--- 이하 생략 ---

Property ID 확인하기

구글 OAuth 로그인 및 로그아웃 추가

Chart란 이름으로 함수컴포넌트를 만들고 구글 로그인기능을 추가하였습니다. 로그인 상태를 관리하도록 useState에 loggedIn 플래그를 추가하였습니다. useEffect를 선언하여 Chart 컴포넌트 마운트 시 구글 로그인 상태 체크 및 로그인 실행이 되도록 적용하였습니다. 

// 선언부 코드 생략

const Chart = () => {
    const [data, setData] = useState([]);
    const [loggedIn, setLoggedIn] = useState(false);

	// 마운트 될 때 로그인 처리하도록 선언
    useEffect(() => {
        gapi.load('client:auth2', initializeGapi);
    }, []);

    const initializeGapi = () => {
        gapi.client.init({
            clientId: CLIENT_ID,
            discoveryDocs: DISCOVERY_DOCS,
            scope: SCOPES,
        })
        .then(() => {
            setLoggedIn(gapi.auth2.getAuthInstance().isSignedIn.get());
        })
        .catch((error) => {
            console.error('gapi client 초기화 실패:', error);
        });
    };

    // login
    const handleLogin = () => {
        gapi.auth2.getAuthInstance().signIn().then(() => {
            setLoggedIn(true);
        })
        .catch((error) => {
            console.error('Google 인증 실패: ', error);
        })
    }

    // logout
    const handleLogout = () => {
        gapi.auth2.getAuthInstance().signOut()
        .then(() => {
            setLoggedIn(false);
            setData([]);
        })
        .catch((error) => {
            console.error('로그아웃 중 에러 발생: ', error);
        });
    }

	// GA4 데이터 가져오기
    const handleFetchData = () => {
        // 생략
    }

    return (
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
            <h1>Google Analytics Data API Example</h1>
            <div>
                {!loggedIn && (
                    <button onClick={handleLogin}>Login with Google</button>
                )}
                {loggedIn && (
                    <div>
                        <button style={{ marginTop: '10px', marginRight: '10px' }} onClick={handleFetchData}>Fetch Data</button>
                        <button onClick={handleLogout}>Logout</button>
                    </div>
                )}
            </div>
            ... 생략 ...
        </div>
    );
};

export default Chart;

GA4 데이터 연동

batchRunReports를 선언합니다. batchRunReports는 GA4 리포트 조회용 함수입니다. 리포트 종류 및 관련 속성에 대해 자세한 정보는 구글 애널리틱스 개발 문서(Google Analytics Data API)에서 확인할 수 있습니다.

  • property: GA4 속성(Property)에서 읽어온 property 값
  • dimensions: GA4 dimension. 아래 예시에서는 date dimension을 선언했습니다.
  • metrics: GA4 metrics. 아래 예시에서는 activeUsers, sessions를 선언했습니다.
  • dateRanges: 조회기간. startDate, endDate에는 아래와 같이 동적으로 최근 일주일 또는 지정된 날짜를 지정할 수 있습니다.
const handleFetchData = () => {
    gapi.client.analyticsdata.properties.batchRunReports({
        property: `properties/${PROPERTY_ID}`,
        resource: {
            requests: [
                {
                    dimensions: [
                        {
                            name: 'date'
                        }
                    ],
                    metrics: [
                        {
                            name: 'activeUsers'
                        },
                        {
                            name: 'sessions'
                        }
                    ],
                    dateRanges: [
                        {
                            startDate: '7daysAgo',
                            endDate: 'yesterday'
                        }
                    ]
                }
            ]
        }
    }).then(response => {
        const { rows } = response.result.reports[0];
        const transformedData = rows.map(row => ({
            date: row.dimensionValues[0].value,
            activeUsers: parseInt(row.metricValues[0].value, 10),
            sessions: parseInt(row.metricValues[1].value, 10)
        }));
        console.log(transformedData);
        setData(transformedData);
    }).catch((error) => {
        console.error('fetchData 에러 발생: ', error);
    })
}

transformedData: GA4 데이터를 받아 전처리한 데이터. Rechart에 조회시 사용합니다.

{date: '20230612', activeUsers: 29, sessions: 37}

Rechart 추가

  • XAxis: date(GA dimension) 선언
  • YAxis: metrics value
  • Area: activeUsers, sessions
const Chart = () => {
    // 코드 생략

    return (
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
            // 코드 생략
            {data.length > 0 && (
                <div style={{ width: '800px', height: '500px' }}>
                <AreaChart width={800} height={500} data={data} margin={{
                    top: 20, right: 20, bottom: 20, left: 20,
                }}>
                    <defs>
                        <linearGradient id="colorSessions" x1="0" y1="0" x2="0" y2="1">
                            <stop offset="5%" stopColor="#8884d8" stopOpacity={0.8}/>
                            <stop offset="95%" stopColor="#8884d8" stopOpacity={0}/>
                        </linearGradient>
                        <linearGradient id="colorActiveUsers" x1="0" y1="0" x2="0" y2="1">
                            <stop offset="5%" stopColor="#82ca9d" stopOpacity={0.8}/>
                            <stop offset="95%" stopColor="#82ca9d" stopOpacity={0}/>
                        </linearGradient>
                    </defs>
                    <CartesianGrid strokeDasharray="3 3" />
                    <XAxis dataKey="date" />
                    <YAxis />
                    <Tooltip />
                    <Legend />
                    <Area type="monotone" dataKey="activeUsers" stroke="#82ca9d" fillOpacity={1} fill="url(#colorActiveUsers)" />
                    <Area type="monotone" dataKey="sessions" stroke="#8884d8" fillOpacity={1} fill="url(#colorSessions)" />
                </AreaChart>
                </div>
            )}
        </div>
    );
};

export default Chart;

 

전체 소스

const Chart = () => {
    const [data, setData] = useState([]);
    const [loggedIn, setLoggedIn] = useState(false);

	// 마운트 될 때 로그인 처리하도록 선언
    useEffect(() => {
        gapi.load('client:auth2', initializeGapi);
    }, []);

    const initializeGapi = () => {
        gapi.client.init({
            clientId: CLIENT_ID,
            discoveryDocs: DISCOVERY_DOCS,
            scope: SCOPES,
        })
        .then(() => {
            setLoggedIn(gapi.auth2.getAuthInstance().isSignedIn.get());
        })
        .catch((error) => {
            console.error('gapi client 초기화 실패:', error);
        });
    };

    // login
    const handleLogin = () => {
        gapi.auth2.getAuthInstance().signIn().then(() => {
            setLoggedIn(true);
        })
        .catch((error) => {
            console.error('Google 인증 실패: ', error);
        })
    }

    // logout
    const handleLogout = () => {
        gapi.auth2.getAuthInstance().signOut()
        .then(() => {
            setLoggedIn(false);
            setData([]);
        })
        .catch((error) => {
            console.error('로그아웃 중 에러 발생: ', error);
        });
    }

	// GA4 데이터 가져오기
    const handleFetchData = () => {
        gapi.client.analyticsdata.properties.batchRunReports({
            property: `properties/${PROPERTY_ID}`,
            resource: {
                requests: [
                    {
                        dimensions: [
                            {
                                name: 'date'
                            }
                        ],
                        metrics: [
                            {
                                name: 'activeUsers'
                            },
                            {
                                name: 'sessions'
                            }
                        ],
                        dateRanges: [
                            {
                                startDate: '7daysAgo',
                                endDate: 'yesterday'
                            }
					    ]
                    }
                ]
            }
        }).then(response => {
            const { rows } = response.result.reports[0];
            const transformedData = rows.map(row => ({
                date: row.dimensionValues[0].value,
                activeUsers: parseInt(row.metricValues[0].value, 10),
                sessions: parseInt(row.metricValues[1].value, 10)
            }));
            console.log(transformedData);
            setData(transformedData);
        }).catch((error) => {
            console.error('Error querying data: ', error);
        })
    }

    return (
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
            <h1>Google Analytics Data API</h1>
            <div>
                {!loggedIn && (
                    <button onClick={handleLogin}>Login with Google</button>
                )}
                {loggedIn && (
                    <div>
                        <button style={{ marginTop: '10px', marginRight: '10px' }} onClick={handleFetchData}>Fetch Data</button>
                        <button onClick={handleLogout}>Logout</button>
                    </div>
                )}
            </div>
            {data.length > 0 && (
                <div style={{ width: '800px', height: '500px' }}>
                <AreaChart width={800} height={500} data={data} margin={{
                    top: 20, right: 20, bottom: 20, left: 20,
                }}>
                    <defs>
                        <linearGradient id="colorSessions" x1="0" y1="0" x2="0" y2="1">
                            <stop offset="5%" stopColor="#8884d8" stopOpacity={0.8}/>
                            <stop offset="95%" stopColor="#8884d8" stopOpacity={0}/>
                        </linearGradient>
                        <linearGradient id="colorActiveUsers" x1="0" y1="0" x2="0" y2="1">
                            <stop offset="5%" stopColor="#82ca9d" stopOpacity={0.8}/>
                            <stop offset="95%" stopColor="#82ca9d" stopOpacity={0}/>
                        </linearGradient>
                    </defs>
                    <CartesianGrid strokeDasharray="3 3" />
                    <XAxis dataKey="date" />
                    <YAxis />
                    <Tooltip />
                    <Legend />
                    <Area type="monotone" dataKey="activeUsers" stroke="#82ca9d" fillOpacity={1} fill="url(#colorActiveUsers)" />
                    <Area type="monotone" dataKey="sessions" stroke="#8884d8" fillOpacity={1} fill="url(#colorSessions)" />
                </AreaChart>
                </div>
            )}
        </div>
    );
};

export default Chart;

 

App.js

Chart 컴포넌트 추가

import React, { useEffect, useState } from 'react';
import { Route, Link, Routes } from 'react-router-dom';

import logo from './logo.svg';
import singularLogo from './img/singular-labs-logo.png'
import './App.css';
import Navbar from './Navbar';
import Events from './Events';
import Chart from './Chart';


function App() {

  return (
    <div className="App">
      <Navbar />
      <img src={singularLogo} alt='' style={{height: '20%',width:'20%'}} />
      <Routes>
        <Route path='/' element={<Events />}></Route>
        <Route path='/ga' element={<Chart />}></Route>
      </Routes>
    </div>
  );
}

export default App;

 

실행해보기

yarn start

 

실행결과

로그인 완료 후 Fetch Data 실행시 Rechart가 실행되도록 처리하였습니다.

 

참고자료

Rechart | npmjs
 

recharts

React charts. Latest version: 2.6.2, last published: a month ago. Start using recharts in your project by running `npm i recharts`. There are 1202 other projects in the npm registry using recharts.

www.npmjs.com

 

Google Analytics Data API(GA4) 연동하기
 

Google Analytics Data API(GA4) 연동하기 - 1. Overview

Google Analytics Data API(GA4) 연동하기 - 1. Overview 분석툴을 쓰다보면 '수집된 데이터를 내부 데이터와 블렌딩하여 분석을 하거나 새로운 마케팅 활동에 응용해보고 싶다'는 니즈가 있는 경우가 종종

neep305.tistory.com

 

반응형