UI tweaks for match columns step + auto hide empty columns
This commit is contained in:
2538
inventory-server/dashboard/google-server/package-lock.json
generated
2538
inventory-server/dashboard/google-server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "google-analytics-server",
|
||||
"version": "1.0.0",
|
||||
"description": "Google Analytics server for dashboard",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google-analytics/data": "^4.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"redis": "^4.6.11",
|
||||
"winston": "^3.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.2"
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
const express = require('express');
|
||||
const { BetaAnalyticsDataClient } = require('@google-analytics/data');
|
||||
const router = express.Router();
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
// Initialize GA4 client
|
||||
const analyticsClient = new BetaAnalyticsDataClient({
|
||||
credentials: JSON.parse(process.env.GOOGLE_APPLICATION_CREDENTIALS_JSON)
|
||||
});
|
||||
|
||||
const propertyId = process.env.GA_PROPERTY_ID;
|
||||
|
||||
// Cache durations
|
||||
const CACHE_DURATIONS = {
|
||||
REALTIME_BASIC: 60, // 1 minute
|
||||
REALTIME_DETAILED: 300, // 5 minutes
|
||||
BASIC_METRICS: 3600, // 1 hour
|
||||
USER_BEHAVIOR: 3600 // 1 hour
|
||||
};
|
||||
|
||||
// Basic metrics endpoint
|
||||
router.get('/metrics', async (req, res) => {
|
||||
try {
|
||||
const { startDate = '7daysAgo' } = req.query;
|
||||
const cacheKey = `analytics:basic_metrics:${startDate}`;
|
||||
|
||||
// Check Redis cache
|
||||
const cachedData = await req.redisClient.get(cacheKey);
|
||||
if (cachedData) {
|
||||
logger.info('Returning cached basic metrics data');
|
||||
return res.json({ success: true, data: JSON.parse(cachedData) });
|
||||
}
|
||||
|
||||
// Fetch from GA4
|
||||
const [response] = await analyticsClient.runReport({
|
||||
property: `properties/${propertyId}`,
|
||||
dateRanges: [{ startDate, endDate: 'today' }],
|
||||
dimensions: [{ name: 'date' }],
|
||||
metrics: [
|
||||
{ name: 'activeUsers' },
|
||||
{ name: 'newUsers' },
|
||||
{ name: 'averageSessionDuration' },
|
||||
{ name: 'screenPageViews' },
|
||||
{ name: 'bounceRate' },
|
||||
{ name: 'conversions' }
|
||||
],
|
||||
returnPropertyQuota: true
|
||||
});
|
||||
|
||||
// Cache the response
|
||||
await req.redisClient.set(cacheKey, JSON.stringify(response), {
|
||||
EX: CACHE_DURATIONS.BASIC_METRICS
|
||||
});
|
||||
|
||||
res.json({ success: true, data: response });
|
||||
} catch (error) {
|
||||
logger.error('Error fetching basic metrics:', error);
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Realtime basic data endpoint
|
||||
router.get('/realtime/basic', async (req, res) => {
|
||||
try {
|
||||
const cacheKey = 'analytics:realtime:basic';
|
||||
|
||||
// Check Redis cache
|
||||
const cachedData = await req.redisClient.get(cacheKey);
|
||||
if (cachedData) {
|
||||
logger.info('Returning cached realtime basic data');
|
||||
return res.json({ success: true, data: JSON.parse(cachedData) });
|
||||
}
|
||||
|
||||
// Fetch active users
|
||||
const [userResponse] = await analyticsClient.runRealtimeReport({
|
||||
property: `properties/${propertyId}`,
|
||||
metrics: [{ name: 'activeUsers' }],
|
||||
returnPropertyQuota: true
|
||||
});
|
||||
|
||||
// Fetch last 5 minutes
|
||||
const [fiveMinResponse] = await analyticsClient.runRealtimeReport({
|
||||
property: `properties/${propertyId}`,
|
||||
metrics: [{ name: 'activeUsers' }],
|
||||
minuteRanges: [{ startMinutesAgo: 5, endMinutesAgo: 0 }]
|
||||
});
|
||||
|
||||
// Fetch time series data
|
||||
const [timeSeriesResponse] = await analyticsClient.runRealtimeReport({
|
||||
property: `properties/${propertyId}`,
|
||||
dimensions: [{ name: 'minutesAgo' }],
|
||||
metrics: [{ name: 'activeUsers' }]
|
||||
});
|
||||
|
||||
const response = {
|
||||
userResponse,
|
||||
fiveMinResponse,
|
||||
timeSeriesResponse,
|
||||
quotaInfo: {
|
||||
projectHourly: userResponse.propertyQuota.tokensPerProjectPerHour,
|
||||
daily: userResponse.propertyQuota.tokensPerDay,
|
||||
serverErrors: userResponse.propertyQuota.serverErrorsPerProjectPerHour,
|
||||
thresholdedRequests: userResponse.propertyQuota.potentiallyThresholdedRequestsPerHour
|
||||
}
|
||||
};
|
||||
|
||||
// Cache the response
|
||||
await req.redisClient.set(cacheKey, JSON.stringify(response), {
|
||||
EX: CACHE_DURATIONS.REALTIME_BASIC
|
||||
});
|
||||
|
||||
res.json({ success: true, data: response });
|
||||
} catch (error) {
|
||||
logger.error('Error fetching realtime basic data:', error);
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Realtime detailed data endpoint
|
||||
router.get('/realtime/detailed', async (req, res) => {
|
||||
try {
|
||||
const cacheKey = 'analytics:realtime:detailed';
|
||||
|
||||
// Check Redis cache
|
||||
const cachedData = await req.redisClient.get(cacheKey);
|
||||
if (cachedData) {
|
||||
logger.info('Returning cached realtime detailed data');
|
||||
return res.json({ success: true, data: JSON.parse(cachedData) });
|
||||
}
|
||||
|
||||
// Fetch current pages
|
||||
const [pageResponse] = await analyticsClient.runRealtimeReport({
|
||||
property: `properties/${propertyId}`,
|
||||
dimensions: [{ name: 'unifiedScreenName' }],
|
||||
metrics: [{ name: 'screenPageViews' }],
|
||||
orderBy: [{ metric: { metricName: 'screenPageViews' }, desc: true }],
|
||||
limit: 25
|
||||
});
|
||||
|
||||
// Fetch events
|
||||
const [eventResponse] = await analyticsClient.runRealtimeReport({
|
||||
property: `properties/${propertyId}`,
|
||||
dimensions: [{ name: 'eventName' }],
|
||||
metrics: [{ name: 'eventCount' }],
|
||||
orderBy: [{ metric: { metricName: 'eventCount' }, desc: true }],
|
||||
limit: 25
|
||||
});
|
||||
|
||||
// Fetch device categories
|
||||
const [deviceResponse] = await analyticsClient.runRealtimeReport({
|
||||
property: `properties/${propertyId}`,
|
||||
dimensions: [{ name: 'deviceCategory' }],
|
||||
metrics: [{ name: 'activeUsers' }],
|
||||
orderBy: [{ metric: { metricName: 'activeUsers' }, desc: true }],
|
||||
limit: 10,
|
||||
returnPropertyQuota: true
|
||||
});
|
||||
|
||||
const response = {
|
||||
pageResponse,
|
||||
eventResponse,
|
||||
sourceResponse: deviceResponse
|
||||
};
|
||||
|
||||
// Cache the response
|
||||
await req.redisClient.set(cacheKey, JSON.stringify(response), {
|
||||
EX: CACHE_DURATIONS.REALTIME_DETAILED
|
||||
});
|
||||
|
||||
res.json({ success: true, data: response });
|
||||
} catch (error) {
|
||||
logger.error('Error fetching realtime detailed data:', error);
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// User behavior endpoint
|
||||
router.get('/user-behavior', async (req, res) => {
|
||||
try {
|
||||
const { timeRange = '30' } = req.query;
|
||||
const cacheKey = `analytics:user_behavior:${timeRange}`;
|
||||
|
||||
// Check Redis cache
|
||||
const cachedData = await req.redisClient.get(cacheKey);
|
||||
if (cachedData) {
|
||||
logger.info('Returning cached user behavior data');
|
||||
return res.json({ success: true, data: JSON.parse(cachedData) });
|
||||
}
|
||||
|
||||
// Fetch page data
|
||||
const [pageResponse] = await analyticsClient.runReport({
|
||||
property: `properties/${propertyId}`,
|
||||
dateRanges: [{ startDate: `${timeRange}daysAgo`, endDate: 'today' }],
|
||||
dimensions: [{ name: 'pagePath' }],
|
||||
metrics: [
|
||||
{ name: 'screenPageViews' },
|
||||
{ name: 'averageSessionDuration' },
|
||||
{ name: 'bounceRate' },
|
||||
{ name: 'sessions' }
|
||||
],
|
||||
orderBy: [{
|
||||
metric: { metricName: 'screenPageViews' },
|
||||
desc: true
|
||||
}],
|
||||
limit: 25
|
||||
});
|
||||
|
||||
// Fetch device data
|
||||
const [deviceResponse] = await analyticsClient.runReport({
|
||||
property: `properties/${propertyId}`,
|
||||
dateRanges: [{ startDate: `${timeRange}daysAgo`, endDate: 'today' }],
|
||||
dimensions: [{ name: 'deviceCategory' }],
|
||||
metrics: [
|
||||
{ name: 'screenPageViews' },
|
||||
{ name: 'sessions' }
|
||||
]
|
||||
});
|
||||
|
||||
// Fetch source data
|
||||
const [sourceResponse] = await analyticsClient.runReport({
|
||||
property: `properties/${propertyId}`,
|
||||
dateRanges: [{ startDate: `${timeRange}daysAgo`, endDate: 'today' }],
|
||||
dimensions: [{ name: 'sessionSource' }],
|
||||
metrics: [
|
||||
{ name: 'sessions' },
|
||||
{ name: 'conversions' }
|
||||
],
|
||||
orderBy: [{
|
||||
metric: { metricName: 'sessions' },
|
||||
desc: true
|
||||
}],
|
||||
limit: 25,
|
||||
returnPropertyQuota: true
|
||||
});
|
||||
|
||||
const response = {
|
||||
pageResponse,
|
||||
deviceResponse,
|
||||
sourceResponse
|
||||
};
|
||||
|
||||
// Cache the response
|
||||
await req.redisClient.set(cacheKey, JSON.stringify(response), {
|
||||
EX: CACHE_DURATIONS.USER_BEHAVIOR
|
||||
});
|
||||
|
||||
res.json({ success: true, data: response });
|
||||
} catch (error) {
|
||||
logger.error('Error fetching user behavior data:', error);
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,91 +0,0 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const analyticsService = require('../services/analytics.service');
|
||||
|
||||
// Basic metrics endpoint
|
||||
router.get('/metrics', async (req, res) => {
|
||||
try {
|
||||
const { startDate = '7daysAgo' } = req.query;
|
||||
console.log(`Fetching metrics with startDate: ${startDate}`);
|
||||
|
||||
const data = await analyticsService.getBasicMetrics(startDate);
|
||||
res.json({ success: true, data });
|
||||
} catch (error) {
|
||||
console.error('Metrics error:', {
|
||||
startDate: req.query.startDate,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to fetch metrics',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Realtime basic data endpoint
|
||||
router.get('/realtime/basic', async (req, res) => {
|
||||
try {
|
||||
console.log('Fetching realtime basic data');
|
||||
const data = await analyticsService.getRealTimeBasicData();
|
||||
res.json({ success: true, data });
|
||||
} catch (error) {
|
||||
console.error('Realtime basic error:', {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to fetch realtime basic data',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Realtime detailed data endpoint
|
||||
router.get('/realtime/detailed', async (req, res) => {
|
||||
try {
|
||||
console.log('Fetching realtime detailed data');
|
||||
const data = await analyticsService.getRealTimeDetailedData();
|
||||
res.json({ success: true, data });
|
||||
} catch (error) {
|
||||
console.error('Realtime detailed error:', {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to fetch realtime detailed data',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// User behavior endpoint
|
||||
router.get('/user-behavior', async (req, res) => {
|
||||
try {
|
||||
const { timeRange = '30' } = req.query;
|
||||
console.log(`Fetching user behavior with timeRange: ${timeRange}`);
|
||||
|
||||
const data = await analyticsService.getUserBehavior(timeRange);
|
||||
res.json({ success: true, data });
|
||||
} catch (error) {
|
||||
console.error('User behavior error:', {
|
||||
timeRange: req.query.timeRange,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to fetch user behavior data',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,65 +0,0 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const { createClient } = require('redis');
|
||||
const analyticsRoutes = require('./routes/analytics.routes');
|
||||
|
||||
const app = express();
|
||||
const port = process.env.GOOGLE_ANALYTICS_PORT || 3007;
|
||||
|
||||
// Redis client setup
|
||||
const redisClient = createClient({
|
||||
url: process.env.REDIS_URL || 'redis://localhost:6379'
|
||||
});
|
||||
|
||||
redisClient.on('error', (err) => console.error('Redis Client Error:', err));
|
||||
redisClient.on('connect', () => console.log('Redis Client Connected'));
|
||||
|
||||
// Connect to Redis
|
||||
(async () => {
|
||||
try {
|
||||
await redisClient.connect();
|
||||
} catch (err) {
|
||||
console.error('Redis connection error:', err);
|
||||
}
|
||||
})();
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Make Redis client available in requests
|
||||
app.use((req, res, next) => {
|
||||
req.redisClient = redisClient;
|
||||
next();
|
||||
});
|
||||
|
||||
// Routes
|
||||
app.use('/api/analytics', analyticsRoutes);
|
||||
|
||||
// Error handling middleware
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('Server error:', err);
|
||||
res.status(err.status || 500).json({
|
||||
success: false,
|
||||
message: err.message || 'Internal server error',
|
||||
error: process.env.NODE_ENV === 'production' ? err : {}
|
||||
});
|
||||
});
|
||||
|
||||
// Start server
|
||||
app.listen(port, () => {
|
||||
console.log(`Google Analytics server running on port ${port}`);
|
||||
});
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on('SIGTERM', async () => {
|
||||
console.log('SIGTERM received. Shutting down gracefully...');
|
||||
await redisClient.quit();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('SIGINT received. Shutting down gracefully...');
|
||||
await redisClient.quit();
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -1,283 +0,0 @@
|
||||
const { BetaAnalyticsDataClient } = require('@google-analytics/data');
|
||||
const { createClient } = require('redis');
|
||||
|
||||
class AnalyticsService {
|
||||
constructor() {
|
||||
// Initialize Redis client
|
||||
this.redis = createClient({
|
||||
url: process.env.REDIS_URL || 'redis://localhost:6379'
|
||||
});
|
||||
|
||||
this.redis.on('error', err => console.error('Redis Client Error:', err));
|
||||
this.redis.connect().catch(err => console.error('Redis connection error:', err));
|
||||
|
||||
try {
|
||||
// Initialize GA4 client
|
||||
const credentials = process.env.GOOGLE_APPLICATION_CREDENTIALS_JSON;
|
||||
this.analyticsClient = new BetaAnalyticsDataClient({
|
||||
credentials: typeof credentials === 'string' ? JSON.parse(credentials) : credentials
|
||||
});
|
||||
|
||||
this.propertyId = process.env.GA_PROPERTY_ID;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize GA4 client:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache durations
|
||||
CACHE_DURATIONS = {
|
||||
REALTIME_BASIC: 60, // 1 minute
|
||||
REALTIME_DETAILED: 300, // 5 minutes
|
||||
BASIC_METRICS: 3600, // 1 hour
|
||||
USER_BEHAVIOR: 3600 // 1 hour
|
||||
};
|
||||
|
||||
async getBasicMetrics(startDate = '7daysAgo') {
|
||||
const cacheKey = `analytics:basic_metrics:${startDate}`;
|
||||
|
||||
try {
|
||||
// Try Redis first
|
||||
const cachedData = await this.redis.get(cacheKey);
|
||||
if (cachedData) {
|
||||
console.log('Analytics metrics found in Redis cache');
|
||||
return JSON.parse(cachedData);
|
||||
}
|
||||
|
||||
// Fetch from GA4
|
||||
console.log('Fetching fresh metrics data from GA4');
|
||||
const [response] = await this.analyticsClient.runReport({
|
||||
property: `properties/${this.propertyId}`,
|
||||
dateRanges: [{ startDate, endDate: 'today' }],
|
||||
dimensions: [{ name: 'date' }],
|
||||
metrics: [
|
||||
{ name: 'activeUsers' },
|
||||
{ name: 'newUsers' },
|
||||
{ name: 'averageSessionDuration' },
|
||||
{ name: 'screenPageViews' },
|
||||
{ name: 'bounceRate' },
|
||||
{ name: 'conversions' }
|
||||
],
|
||||
returnPropertyQuota: true
|
||||
});
|
||||
|
||||
// Cache the response
|
||||
await this.redis.set(cacheKey, JSON.stringify(response), {
|
||||
EX: this.CACHE_DURATIONS.BASIC_METRICS
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Error fetching analytics metrics:', {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getRealTimeBasicData() {
|
||||
const cacheKey = 'analytics:realtime:basic';
|
||||
|
||||
try {
|
||||
// Try Redis first
|
||||
const cachedData = await this.redis.get(cacheKey);
|
||||
if (cachedData) {
|
||||
console.log('Realtime basic data found in Redis cache');
|
||||
return JSON.parse(cachedData);
|
||||
}
|
||||
|
||||
console.log('Fetching fresh realtime data from GA4');
|
||||
|
||||
// Fetch active users
|
||||
const [userResponse] = await this.analyticsClient.runRealtimeReport({
|
||||
property: `properties/${this.propertyId}`,
|
||||
metrics: [{ name: 'activeUsers' }],
|
||||
returnPropertyQuota: true
|
||||
});
|
||||
|
||||
// Fetch last 5 minutes
|
||||
const [fiveMinResponse] = await this.analyticsClient.runRealtimeReport({
|
||||
property: `properties/${this.propertyId}`,
|
||||
metrics: [{ name: 'activeUsers' }],
|
||||
minuteRanges: [{ startMinutesAgo: 5, endMinutesAgo: 0 }]
|
||||
});
|
||||
|
||||
// Fetch time series data
|
||||
const [timeSeriesResponse] = await this.analyticsClient.runRealtimeReport({
|
||||
property: `properties/${this.propertyId}`,
|
||||
dimensions: [{ name: 'minutesAgo' }],
|
||||
metrics: [{ name: 'activeUsers' }]
|
||||
});
|
||||
|
||||
const response = {
|
||||
userResponse,
|
||||
fiveMinResponse,
|
||||
timeSeriesResponse,
|
||||
quotaInfo: {
|
||||
projectHourly: userResponse.propertyQuota.tokensPerProjectPerHour,
|
||||
daily: userResponse.propertyQuota.tokensPerDay,
|
||||
serverErrors: userResponse.propertyQuota.serverErrorsPerProjectPerHour,
|
||||
thresholdedRequests: userResponse.propertyQuota.potentiallyThresholdedRequestsPerHour
|
||||
}
|
||||
};
|
||||
|
||||
// Cache the response
|
||||
await this.redis.set(cacheKey, JSON.stringify(response), {
|
||||
EX: this.CACHE_DURATIONS.REALTIME_BASIC
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Error fetching realtime basic data:', {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getRealTimeDetailedData() {
|
||||
const cacheKey = 'analytics:realtime:detailed';
|
||||
|
||||
try {
|
||||
// Try Redis first
|
||||
const cachedData = await this.redis.get(cacheKey);
|
||||
if (cachedData) {
|
||||
console.log('Realtime detailed data found in Redis cache');
|
||||
return JSON.parse(cachedData);
|
||||
}
|
||||
|
||||
console.log('Fetching fresh realtime detailed data from GA4');
|
||||
|
||||
// Fetch current pages
|
||||
const [pageResponse] = await this.analyticsClient.runRealtimeReport({
|
||||
property: `properties/${this.propertyId}`,
|
||||
dimensions: [{ name: 'unifiedScreenName' }],
|
||||
metrics: [{ name: 'screenPageViews' }],
|
||||
orderBy: [{ metric: { metricName: 'screenPageViews' }, desc: true }],
|
||||
limit: 25
|
||||
});
|
||||
|
||||
// Fetch events
|
||||
const [eventResponse] = await this.analyticsClient.runRealtimeReport({
|
||||
property: `properties/${this.propertyId}`,
|
||||
dimensions: [{ name: 'eventName' }],
|
||||
metrics: [{ name: 'eventCount' }],
|
||||
orderBy: [{ metric: { metricName: 'eventCount' }, desc: true }],
|
||||
limit: 25
|
||||
});
|
||||
|
||||
// Fetch device categories
|
||||
const [deviceResponse] = await this.analyticsClient.runRealtimeReport({
|
||||
property: `properties/${this.propertyId}`,
|
||||
dimensions: [{ name: 'deviceCategory' }],
|
||||
metrics: [{ name: 'activeUsers' }],
|
||||
orderBy: [{ metric: { metricName: 'activeUsers' }, desc: true }],
|
||||
limit: 10,
|
||||
returnPropertyQuota: true
|
||||
});
|
||||
|
||||
const response = {
|
||||
pageResponse,
|
||||
eventResponse,
|
||||
sourceResponse: deviceResponse
|
||||
};
|
||||
|
||||
// Cache the response
|
||||
await this.redis.set(cacheKey, JSON.stringify(response), {
|
||||
EX: this.CACHE_DURATIONS.REALTIME_DETAILED
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Error fetching realtime detailed data:', {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getUserBehavior(timeRange = '30') {
|
||||
const cacheKey = `analytics:user_behavior:${timeRange}`;
|
||||
|
||||
try {
|
||||
// Try Redis first
|
||||
const cachedData = await this.redis.get(cacheKey);
|
||||
if (cachedData) {
|
||||
console.log('User behavior data found in Redis cache');
|
||||
return JSON.parse(cachedData);
|
||||
}
|
||||
|
||||
console.log('Fetching fresh user behavior data from GA4');
|
||||
|
||||
// Fetch page data
|
||||
const [pageResponse] = await this.analyticsClient.runReport({
|
||||
property: `properties/${this.propertyId}`,
|
||||
dateRanges: [{ startDate: `${timeRange}daysAgo`, endDate: 'today' }],
|
||||
dimensions: [{ name: 'pagePath' }],
|
||||
metrics: [
|
||||
{ name: 'screenPageViews' },
|
||||
{ name: 'averageSessionDuration' },
|
||||
{ name: 'bounceRate' },
|
||||
{ name: 'sessions' }
|
||||
],
|
||||
orderBy: [{
|
||||
metric: { metricName: 'screenPageViews' },
|
||||
desc: true
|
||||
}],
|
||||
limit: 25
|
||||
});
|
||||
|
||||
// Fetch device data
|
||||
const [deviceResponse] = await this.analyticsClient.runReport({
|
||||
property: `properties/${this.propertyId}`,
|
||||
dateRanges: [{ startDate: `${timeRange}daysAgo`, endDate: 'today' }],
|
||||
dimensions: [{ name: 'deviceCategory' }],
|
||||
metrics: [
|
||||
{ name: 'screenPageViews' },
|
||||
{ name: 'sessions' }
|
||||
]
|
||||
});
|
||||
|
||||
// Fetch source data
|
||||
const [sourceResponse] = await this.analyticsClient.runReport({
|
||||
property: `properties/${this.propertyId}`,
|
||||
dateRanges: [{ startDate: `${timeRange}daysAgo`, endDate: 'today' }],
|
||||
dimensions: [{ name: 'sessionSource' }],
|
||||
metrics: [
|
||||
{ name: 'sessions' },
|
||||
{ name: 'conversions' }
|
||||
],
|
||||
orderBy: [{
|
||||
metric: { metricName: 'sessions' },
|
||||
desc: true
|
||||
}],
|
||||
limit: 25,
|
||||
returnPropertyQuota: true
|
||||
});
|
||||
|
||||
const response = {
|
||||
pageResponse,
|
||||
deviceResponse,
|
||||
sourceResponse
|
||||
};
|
||||
|
||||
// Cache the response
|
||||
await this.redis.set(cacheKey, JSON.stringify(response), {
|
||||
EX: this.CACHE_DURATIONS.USER_BEHAVIOR
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Error fetching user behavior data:', {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AnalyticsService();
|
||||
@@ -1,35 +0,0 @@
|
||||
const winston = require('winston');
|
||||
const path = require('path');
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.json()
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.File({
|
||||
filename: path.join(__dirname, '../logs/pm2/error.log'),
|
||||
level: 'error',
|
||||
maxsize: 10485760, // 10MB
|
||||
maxFiles: 5
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: path.join(__dirname, '../logs/pm2/combined.log'),
|
||||
maxsize: 10485760, // 10MB
|
||||
maxFiles: 5
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
// Add console transport in development
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
logger.add(new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple()
|
||||
)
|
||||
}));
|
||||
}
|
||||
|
||||
module.exports = logger;
|
||||
Reference in New Issue
Block a user