통계 기간 지표 원장 기준 보정
This commit is contained in:
@@ -703,6 +703,124 @@ const markContentSessionRead = async (sql, input) => {
|
||||
return rows.length > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 콘텐츠 세션 집계 행이 실제 원장 데이터를 포함하는지 확인한다.
|
||||
* @param {Object | undefined} row - 원장 집계 행
|
||||
* @returns {boolean} 원장 데이터 존재 여부
|
||||
*/
|
||||
const hasContentSessionAggregateRows = (row) => {
|
||||
return Number(row?.content_sessions || 0) > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 선택 기간이 콘텐츠 세션 원장 보관 범위 안에 있는지 확인한다.
|
||||
* @param {string} startDay - 시작일
|
||||
* @param {string} today - 오늘 날짜 키
|
||||
* @returns {boolean} 원장 기준 계산 가능 여부
|
||||
*/
|
||||
const canUseContentSessionAggregateForRange = (startDay, today = getAnalyticsDayKey()) => {
|
||||
const retentionStartDay = getAnalyticsDayBefore(today, ANALYTICS_VISITOR_HASH_RETENTION_DAYS)
|
||||
return String(startDay || '') >= retentionStartDay
|
||||
}
|
||||
|
||||
/**
|
||||
* 사이트 콘텐츠 세션 집계를 조회한다.
|
||||
* @param {import('postgres').Sql} sql - DB 클라이언트
|
||||
* @param {string} startDay - 시작일
|
||||
* @param {string} endDay - 종료일
|
||||
* @returns {Promise<Object>} 원장 집계 행
|
||||
*/
|
||||
const getSiteContentSessionAggregate = async (sql, startDay, endDay) => {
|
||||
const rows = await sql`
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE view_recorded)::int AS views,
|
||||
COUNT(DISTINCT visitor_hash) FILTER (WHERE view_recorded)::int AS visitors,
|
||||
COUNT(*) FILTER (WHERE engaged_recorded)::int AS engaged_views,
|
||||
COALESCE(SUM(duration_seconds), 0)::int AS total_engaged_seconds,
|
||||
COUNT(*)::int AS content_sessions
|
||||
FROM analytics_content_sessions
|
||||
WHERE day BETWEEN ${startDay}::date AND ${endDay}::date
|
||||
`
|
||||
|
||||
return rows[0] || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 게시물 콘텐츠 세션 집계를 조회한다.
|
||||
* @param {import('postgres').Sql} sql - DB 클라이언트
|
||||
* @param {string} postId - 게시물 ID
|
||||
* @param {string} startDay - 시작일
|
||||
* @param {string} endDay - 종료일
|
||||
* @returns {Promise<Object>} 원장 집계 행
|
||||
*/
|
||||
const getPostContentSessionAggregate = async (sql, postId, startDay, endDay) => {
|
||||
const rows = await sql`
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE view_recorded)::int AS views,
|
||||
COUNT(DISTINCT visitor_hash) FILTER (WHERE view_recorded)::int AS visitors,
|
||||
COUNT(*) FILTER (WHERE read_recorded)::int AS reads,
|
||||
COUNT(*) FILTER (WHERE engaged_recorded)::int AS engaged_views,
|
||||
COALESCE(SUM(duration_seconds), 0)::int AS total_engaged_seconds,
|
||||
COUNT(*) FILTER (WHERE scroll_50_recorded)::int AS scroll_50,
|
||||
COUNT(*) FILTER (WHERE scroll_75_recorded)::int AS scroll_75,
|
||||
COUNT(*) FILTER (WHERE scroll_100_recorded)::int AS scroll_100,
|
||||
COUNT(*)::int AS content_sessions
|
||||
FROM analytics_content_sessions
|
||||
WHERE post_id = ${postId}
|
||||
AND day BETWEEN ${startDay}::date AND ${endDay}::date
|
||||
`
|
||||
|
||||
return rows[0] || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 페이지 콘텐츠 세션 집계를 조회한다.
|
||||
* @param {import('postgres').Sql} sql - DB 클라이언트
|
||||
* @param {string} pageId - 페이지 ID
|
||||
* @param {string} startDay - 시작일
|
||||
* @param {string} endDay - 종료일
|
||||
* @returns {Promise<Object>} 원장 집계 행
|
||||
*/
|
||||
const getPageContentSessionAggregate = async (sql, pageId, startDay, endDay) => {
|
||||
const rows = await sql`
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE view_recorded)::int AS views,
|
||||
COUNT(DISTINCT visitor_hash) FILTER (WHERE view_recorded)::int AS visitors,
|
||||
COUNT(*) FILTER (WHERE engaged_recorded)::int AS engaged_views,
|
||||
COALESCE(SUM(duration_seconds), 0)::int AS total_engaged_seconds,
|
||||
COUNT(*) FILTER (WHERE scroll_50_recorded)::int AS scroll_50,
|
||||
COUNT(*) FILTER (WHERE scroll_75_recorded)::int AS scroll_75,
|
||||
COUNT(*) FILTER (WHERE scroll_100_recorded)::int AS scroll_100,
|
||||
COUNT(*)::int AS content_sessions
|
||||
FROM analytics_content_sessions
|
||||
WHERE page_id = ${pageId}
|
||||
AND day BETWEEN ${startDay}::date AND ${endDay}::date
|
||||
`
|
||||
|
||||
return rows[0] || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 게시물 전체 콘텐츠 세션 스크롤 집계를 조회한다.
|
||||
* @param {import('postgres').Sql} sql - DB 클라이언트
|
||||
* @param {string} startDay - 시작일
|
||||
* @param {string} endDay - 종료일
|
||||
* @returns {Promise<Object>} 원장 집계 행
|
||||
*/
|
||||
const getAllPostContentSessionScrollAggregate = async (sql, startDay, endDay) => {
|
||||
const rows = await sql`
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE view_recorded)::int AS views,
|
||||
COUNT(*) FILTER (WHERE scroll_50_recorded)::int AS scroll_50,
|
||||
COUNT(*)::int AS content_sessions
|
||||
FROM analytics_content_sessions
|
||||
WHERE post_id IS NOT NULL
|
||||
AND day BETWEEN ${startDay}::date AND ${endDay}::date
|
||||
`
|
||||
|
||||
return rows[0] || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 날짜 문자열이 통계 조회 형식인지 확인한다.
|
||||
* @param {string} value - 날짜 문자열
|
||||
@@ -1285,6 +1403,20 @@ export const getAnalyticsSummary = async (options = {}) => {
|
||||
WHERE day >= ${rangeStartDay}::date
|
||||
`
|
||||
|
||||
const todayContentRows = await getSiteContentSessionAggregate(sql, today, today)
|
||||
const contentEngagementRows = await getSiteContentSessionAggregate(sql, rangeStartDay, today)
|
||||
const contentScrollRows = await getAllPostContentSessionScrollAggregate(sql, rangeStartDay, today)
|
||||
const shouldUseContentRows = canUseContentSessionAggregateForRange(rangeStartDay, today)
|
||||
const todayEngagementRow = hasContentSessionAggregateRows(todayContentRows)
|
||||
? todayContentRows
|
||||
: todayRows[0]
|
||||
const engagementRow = shouldUseContentRows && hasContentSessionAggregateRows(contentEngagementRows)
|
||||
? contentEngagementRows
|
||||
: engagementRows[0]
|
||||
const scrollRow = shouldUseContentRows && hasContentSessionAggregateRows(contentScrollRows)
|
||||
? contentScrollRows
|
||||
: scrollRows[0]
|
||||
|
||||
const onlineRows = await sql`
|
||||
SELECT
|
||||
COUNT(DISTINCT CASE
|
||||
@@ -1519,10 +1651,10 @@ export const getAnalyticsSummary = async (options = {}) => {
|
||||
GROUP BY day, source_group
|
||||
`
|
||||
|
||||
const engagedViews = Number(engagementRows[0]?.engaged_views || 0)
|
||||
const totalEngagedSeconds = Number(engagementRows[0]?.total_engaged_seconds || 0)
|
||||
const todayEngagedViews = Number(todayRows[0]?.engaged_views || 0)
|
||||
const todayTotalEngagedSeconds = Number(todayRows[0]?.total_engaged_seconds || 0)
|
||||
const engagedViews = Number(engagementRow?.engaged_views || 0)
|
||||
const totalEngagedSeconds = Number(engagementRow?.total_engaged_seconds || 0)
|
||||
const todayEngagedViews = Number(todayEngagementRow?.engaged_views || 0)
|
||||
const todayTotalEngagedSeconds = Number(todayEngagementRow?.total_engaged_seconds || 0)
|
||||
|
||||
/**
|
||||
* 버킷 구간의 유입 방문자를 합산한다.
|
||||
@@ -1573,8 +1705,8 @@ export const getAnalyticsSummary = async (options = {}) => {
|
||||
? Math.round(totalEngagedSeconds / engagedViews)
|
||||
: 0,
|
||||
scroll50Reach: Math.min(
|
||||
Number(scrollRows[0]?.scroll_50 || 0),
|
||||
Number(scrollRows[0]?.views || 0)
|
||||
Number(scrollRow?.scroll_50 || 0),
|
||||
Number(scrollRow?.views || 0)
|
||||
),
|
||||
collectedSince,
|
||||
trends: trendRows.map((row) => {
|
||||
@@ -1893,6 +2025,16 @@ export const getAnalyticsRangeDetail = async (options = {}) => {
|
||||
WHERE post_analytics_daily.day BETWEEN ${startDay}::date AND ${endDay}::date
|
||||
`
|
||||
|
||||
const contentSummaryRows = await getSiteContentSessionAggregate(sql, startDay, endDay)
|
||||
const contentScrollRows = await getAllPostContentSessionScrollAggregate(sql, startDay, endDay)
|
||||
const shouldUseContentRows = canUseContentSessionAggregateForRange(startDay, endDay)
|
||||
const summaryEngagementRow = shouldUseContentRows && hasContentSessionAggregateRows(contentSummaryRows)
|
||||
? contentSummaryRows
|
||||
: summaryRows[0]
|
||||
const summaryScrollRow = shouldUseContentRows && hasContentSessionAggregateRows(contentScrollRows)
|
||||
? contentScrollRows
|
||||
: scrollRows[0]
|
||||
|
||||
const sourceRows = await sql`
|
||||
SELECT
|
||||
source_group,
|
||||
@@ -1978,8 +2120,8 @@ export const getAnalyticsRangeDetail = async (options = {}) => {
|
||||
LIMIT ${limit}
|
||||
`
|
||||
|
||||
const engagedViews = Number(summaryRows[0]?.engaged_views || 0)
|
||||
const totalEngagedSeconds = Number(summaryRows[0]?.total_engaged_seconds || 0)
|
||||
const engagedViews = Number(summaryEngagementRow?.engaged_views || 0)
|
||||
const totalEngagedSeconds = Number(summaryEngagementRow?.total_engaged_seconds || 0)
|
||||
|
||||
return {
|
||||
startDay,
|
||||
@@ -1992,8 +2134,8 @@ export const getAnalyticsRangeDetail = async (options = {}) => {
|
||||
? Math.round(totalEngagedSeconds / engagedViews)
|
||||
: 0,
|
||||
scroll50Reach: Math.min(
|
||||
Number(scrollRows[0]?.scroll_50 || 0),
|
||||
Number(scrollRows[0]?.views || 0)
|
||||
Number(summaryScrollRow?.scroll_50 || 0),
|
||||
Number(summaryScrollRow?.views || 0)
|
||||
)
|
||||
},
|
||||
sources: sourceRows.map((row) => ({
|
||||
@@ -2145,6 +2287,12 @@ export const getPostAnalyticsDetail = async (options = {}) => {
|
||||
AND day >= ${rangeStartDay}::date
|
||||
`
|
||||
|
||||
const periodContentRows = await getPostContentSessionAggregate(sql, postId, rangeStartDay, today)
|
||||
const shouldUseContentRows = canUseContentSessionAggregateForRange(rangeStartDay, today)
|
||||
const periodSummaryRow = shouldUseContentRows && hasContentSessionAggregateRows(periodContentRows)
|
||||
? periodContentRows
|
||||
: periodRows[0]
|
||||
|
||||
let trendRows = []
|
||||
|
||||
if (trendPeriod === 'weekly') {
|
||||
@@ -2293,7 +2441,7 @@ export const getPostAnalyticsDetail = async (options = {}) => {
|
||||
trendPeriod,
|
||||
updatedAt: new Date().toISOString(),
|
||||
total: mapSummaryRow(totalRows[0]),
|
||||
period: mapSummaryRow(periodRows[0]),
|
||||
period: mapSummaryRow(periodSummaryRow),
|
||||
trends: trendRows.map((row) => {
|
||||
const engagementStats = normalizeContentEngagementStats({
|
||||
views: row.views,
|
||||
@@ -2403,6 +2551,12 @@ export const getPageAnalyticsDetail = async (options = {}) => {
|
||||
AND day >= ${rangeStartDay}::date
|
||||
`
|
||||
|
||||
const periodContentRows = await getPageContentSessionAggregate(sql, pageId, rangeStartDay, today)
|
||||
const shouldUseContentRows = canUseContentSessionAggregateForRange(rangeStartDay, today)
|
||||
const periodSummaryRow = shouldUseContentRows && hasContentSessionAggregateRows(periodContentRows)
|
||||
? periodContentRows
|
||||
: periodRows[0]
|
||||
|
||||
let trendRows = []
|
||||
|
||||
if (trendPeriod === 'weekly') {
|
||||
@@ -2505,7 +2659,7 @@ export const getPageAnalyticsDetail = async (options = {}) => {
|
||||
trendPeriod,
|
||||
updatedAt: new Date().toISOString(),
|
||||
total: mapPageSummaryRow(totalRows[0]),
|
||||
period: mapPageSummaryRow(periodRows[0]),
|
||||
period: mapPageSummaryRow(periodSummaryRow),
|
||||
trends: trendRows.map((row) => ({
|
||||
day: row.start_day ? new Date(row.start_day).toISOString().slice(0, 10) : '',
|
||||
startDay: row.start_day ? new Date(row.start_day).toISOString().slice(0, 10) : '',
|
||||
|
||||
Reference in New Issue
Block a user