Add gorgias component and services
This commit is contained in:
@@ -0,0 +1,292 @@
|
||||
// src/services/googleAnalyticsService.js
|
||||
class GoogleAnalyticsService {
|
||||
constructor() {
|
||||
this.baseUrl = "/api/analytics"; // This matches your NGINX config
|
||||
}
|
||||
|
||||
async getBasicMetrics({ startDate = "7daysAgo" } = {}) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${this.baseUrl}/metrics?startDate=${startDate}`,
|
||||
{
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch metrics");
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!result?.data) {
|
||||
throw new Error("No data received");
|
||||
}
|
||||
|
||||
return this.processMetricsData(result.data);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch basic metrics:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getRealTimeBasicData() {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/realtime/basic`, {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch basic realtime data");
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!result?.data) {
|
||||
throw new Error("No data received");
|
||||
}
|
||||
|
||||
const processed = this.processRealTimeBasicData(result.data);
|
||||
return {
|
||||
...processed,
|
||||
lastUpdated: result.lastUpdated || new Date().toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch basic realtime data:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getRealTimeDetailedData() {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/realtime/detailed`, {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch detailed realtime data");
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!result?.data) {
|
||||
throw new Error("No data received");
|
||||
}
|
||||
|
||||
const processed = this.processRealTimeDetailedData(result.data);
|
||||
return {
|
||||
...processed,
|
||||
lastUpdated: result.lastUpdated || new Date().toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch detailed realtime data:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getUserBehavior(timeRange = "30") {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${this.baseUrl}/user-behavior?timeRange=${timeRange}`,
|
||||
{
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch user behavior");
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log("Raw user behavior response:", result);
|
||||
|
||||
if (!result?.success) {
|
||||
throw new Error("Invalid response structure");
|
||||
}
|
||||
|
||||
// Handle both data structures
|
||||
const rawData = result.data?.data || result.data;
|
||||
|
||||
// Try to access the data differently based on the structure
|
||||
const pageResponse = rawData?.pageResponse || rawData?.reports?.[0];
|
||||
const deviceResponse = rawData?.deviceResponse || rawData?.reports?.[1];
|
||||
const sourceResponse = rawData?.sourceResponse || rawData?.reports?.[2];
|
||||
|
||||
console.log("Extracted responses:", {
|
||||
pageResponse,
|
||||
deviceResponse,
|
||||
sourceResponse,
|
||||
});
|
||||
|
||||
const processed = {
|
||||
success: true,
|
||||
data: {
|
||||
pageData: {
|
||||
pageData: this.processPageData(pageResponse),
|
||||
deviceData: this.processDeviceData(deviceResponse),
|
||||
},
|
||||
sourceData: this.processSourceData(sourceResponse),
|
||||
},
|
||||
};
|
||||
|
||||
console.log("Final processed data:", processed);
|
||||
return processed;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user behavior:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
processMetricsData(data) {
|
||||
if (!data?.rows) {
|
||||
console.log("No rows found in data");
|
||||
return [];
|
||||
}
|
||||
|
||||
return data.rows.map((row) => ({
|
||||
date: row.dimensionValues[0].value,
|
||||
activeUsers: parseInt(row.metricValues[0].value),
|
||||
newUsers: parseInt(row.metricValues[1].value),
|
||||
avgSessionDuration: parseFloat(row.metricValues[2].value),
|
||||
pageViews: parseInt(row.metricValues[3].value),
|
||||
bounceRate: parseFloat(row.metricValues[4].value) * 100,
|
||||
conversions: parseInt(row.metricValues[5].value),
|
||||
}));
|
||||
}
|
||||
|
||||
processRealTimeBasicData(data) {
|
||||
const last30MinUsers = parseInt(
|
||||
data.userResponse?.rows?.[0]?.metricValues?.[0]?.value || 0
|
||||
);
|
||||
const last5MinUsers = parseInt(
|
||||
data.fiveMinResponse?.rows?.[0]?.metricValues?.[0]?.value || 0
|
||||
);
|
||||
|
||||
const byMinute = Array.from({ length: 30 }, (_, i) => {
|
||||
const matchingRow = data.timeSeriesResponse?.rows?.find(
|
||||
(row) => parseInt(row.dimensionValues[0].value) === i
|
||||
);
|
||||
const users = matchingRow
|
||||
? parseInt(matchingRow.metricValues[0].value)
|
||||
: 0;
|
||||
const timestamp = new Date(Date.now() - i * 60000);
|
||||
return {
|
||||
minute: -i,
|
||||
users,
|
||||
timestamp: timestamp.toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}),
|
||||
};
|
||||
}).reverse();
|
||||
|
||||
const tokenQuota = data.quotaInfo
|
||||
? {
|
||||
projectHourly: data.quotaInfo.projectHourly || {},
|
||||
daily: data.quotaInfo.daily || {},
|
||||
serverErrors: data.quotaInfo.serverErrors || {},
|
||||
thresholdedRequests: data.quotaInfo.thresholdedRequests || {},
|
||||
}
|
||||
: null;
|
||||
|
||||
return {
|
||||
last30MinUsers,
|
||||
last5MinUsers,
|
||||
byMinute,
|
||||
tokenQuota,
|
||||
};
|
||||
}
|
||||
|
||||
processRealTimeDetailedData(data) {
|
||||
return {
|
||||
currentPages:
|
||||
data.pageResponse?.rows?.map((row) => ({
|
||||
page: row.dimensionValues[0].value,
|
||||
views: parseInt(row.metricValues[0].value),
|
||||
})) || [],
|
||||
|
||||
sources:
|
||||
data.sourceResponse?.rows?.map((row) => ({
|
||||
device: row.dimensionValues[0].value,
|
||||
users: parseInt(row.metricValues[0].value),
|
||||
})) || [],
|
||||
|
||||
recentEvents:
|
||||
data.eventResponse?.rows
|
||||
?.filter(
|
||||
(row) =>
|
||||
!["session_start", "(other)"].includes(
|
||||
row.dimensionValues[0].value
|
||||
)
|
||||
)
|
||||
.map((row) => ({
|
||||
event: row.dimensionValues[0].value,
|
||||
count: parseInt(row.metricValues[0].value),
|
||||
})) || [],
|
||||
};
|
||||
}
|
||||
|
||||
processPageData(data) {
|
||||
console.log("Processing page data input:", data);
|
||||
if (!data?.rows) {
|
||||
console.log("No rows in page data");
|
||||
return [];
|
||||
}
|
||||
|
||||
const processed = data.rows.map((row) => ({
|
||||
path: row.dimensionValues[0].value || "Unknown",
|
||||
pageViews: parseInt(row.metricValues[0].value || 0),
|
||||
avgSessionDuration: parseFloat(row.metricValues[1].value || 0),
|
||||
bounceRate: parseFloat(row.metricValues[2].value || 0) * 100,
|
||||
engagedSessions: parseInt(row.metricValues[3].value || 0),
|
||||
}));
|
||||
console.log("Processed page data:", processed);
|
||||
return processed;
|
||||
}
|
||||
processDeviceData(data) {
|
||||
console.log("Processing device data input:", data);
|
||||
if (!data?.rows) {
|
||||
console.log("No rows in device data");
|
||||
return [];
|
||||
}
|
||||
|
||||
const processed = data.rows
|
||||
.filter((row) => {
|
||||
const device = (row.dimensionValues[0].value || "").toLowerCase();
|
||||
return ["desktop", "mobile", "tablet"].includes(device);
|
||||
})
|
||||
.map((row) => {
|
||||
const device = row.dimensionValues[0].value || "Unknown";
|
||||
return {
|
||||
device:
|
||||
device.charAt(0).toUpperCase() + device.slice(1).toLowerCase(),
|
||||
pageViews: parseInt(row.metricValues[0].value || 0),
|
||||
sessions: parseInt(row.metricValues[1].value || 0),
|
||||
};
|
||||
})
|
||||
.sort((a, b) => b.pageViews - a.pageViews);
|
||||
|
||||
console.log("Processed device data:", processed);
|
||||
return processed;
|
||||
}
|
||||
processSourceData(data) {
|
||||
console.log("Processing source data input:", data);
|
||||
if (!data?.rows) {
|
||||
console.log("No rows in source data");
|
||||
return [];
|
||||
}
|
||||
|
||||
const processed = data.rows.map((row) => ({
|
||||
source: row.dimensionValues[0].value || "Unknown",
|
||||
sessions: parseInt(row.metricValues[0].value || 0),
|
||||
conversions: parseInt(row.metricValues[1].value || 0),
|
||||
}));
|
||||
console.log("Processed source data:", processed);
|
||||
return processed;
|
||||
}
|
||||
}
|
||||
// Create a single instance
|
||||
const service = new GoogleAnalyticsService();
|
||||
|
||||
// Export both the instance and the class
|
||||
export { service as googleAnalyticsService, GoogleAnalyticsService };
|
||||
Reference in New Issue
Block a user