diff --git a/analytics/analytics_package/analytics/static_site/charts.py b/analytics/analytics_package/analytics/static_site/charts.py
index bb9724ac3..95b6c5fd3 100644
--- a/analytics/analytics_package/analytics/static_site/charts.py
+++ b/analytics/analytics_package/analytics/static_site/charts.py
@@ -20,21 +20,21 @@ def make_event_charts(entity_label, entity_path, chart_start):
"title": "Export to Terra",
"series": [
{"label": f"Single {entity_label}", "event_key": "dataset_analyze_in_terra_requested", "event_name": "dataset_analyze_in_terra_requested"},
- {"label": "Cross-Dataset", "event_key": "index_analyze_in_terra_requested", "event_name": "index_analyze_in_terra_requested"},
+ {"label": f"Cross-{entity_label}", "event_key": "index_analyze_in_terra_requested", "event_name": "index_analyze_in_terra_requested"},
],
},
{
"title": "curl Command",
"series": [
{"label": f"Single {entity_label}", "event_key": "dataset_bulk_download_requested", "event_name": "bulk_download_requested", "page_path_regex": rf"^/{path_segment}/[0-9a-f-]+"},
- {"label": "Cross-Dataset", "event_key": "index_bulk_download_requested", "event_name": "bulk_download_requested", "page_path_regex": rf"^(?!/{path_segment}/[0-9a-f-]+)"},
+ {"label": f"Cross-{entity_label}", "event_key": "index_bulk_download_requested", "event_name": "bulk_download_requested", "page_path_regex": rf"^(?!/{path_segment}/[0-9a-f-]+)"},
],
},
{
"title": "File Manifest",
"series": [
{"label": f"Single {entity_label}", "event_key": "dataset_file_manifest_requested", "event_name": "dataset_file_manifest_requested"},
- {"label": "Cross-Dataset", "event_key": "index_file_manifest_requested", "event_name": "index_file_manifest_requested"},
+ {"label": f"Cross-{entity_label}", "event_key": "index_file_manifest_requested", "event_name": "index_file_manifest_requested"},
],
},
],
diff --git a/analytics/analytics_package/analytics/static_site/export.py b/analytics/analytics_package/analytics/static_site/export.py
index d78958c40..78f8a605e 100644
--- a/analytics/analytics_package/analytics/static_site/export.py
+++ b/analytics/analytics_package/analytics/static_site/export.py
@@ -148,11 +148,14 @@ def export_data(data, config, current_month, analytics_start, custom_events, out
for event in custom_events:
key = event_key(event)
stats = data.get(f"event_{key}", {"current": 0, "prior": 0, "change": None})
- events_output.append({
+ event_entry = {
"event_name": key,
"label": event["label"],
**stats,
- })
+ }
+ if event.get("detail_file_column"):
+ event_entry["detail_file_column"] = event["detail_file_column"]
+ events_output.append(event_entry)
with open(os.path.join(output_dir, "custom_events.json"), "w") as f:
json.dump(events_output, f, indent=2)
@@ -211,7 +214,6 @@ def export_data(data, config, current_month, analytics_start, custom_events, out
"prior_month_end": dates.get("end_prior", ""),
"analytics_start": analytics_start,
"sessions": data.get("sessions", {}),
- "engaged_sessions": data.get("engaged_sessions", {}),
"engagement_rate": data.get("engagement_rate", {}),
}
diff --git a/analytics/analytics_package/analytics/static_site/fetch.py b/analytics/analytics_package/analytics/static_site/fetch.py
index 1ff92b9f3..67084ad41 100644
--- a/analytics/analytics_package/analytics/static_site/fetch.py
+++ b/analytics/analytics_package/analytics/static_site/fetch.py
@@ -22,11 +22,6 @@
"alias": "Engagement Rate",
}
-METRIC_ENGAGED_SESSIONS = {
- "id": "engagedSessions",
- "alias": "Engaged Sessions",
-}
-
# Regex matching page paths that are clearly not real pages (bot probes,
# broken markdown links, asset requests, etc.).
SUSPICIOUS_PAGE_PATH_RE = re.compile(
@@ -49,11 +44,13 @@ def event_key(event):
return event.get("key", event["event_name"])
-def _count_events(event_name, params, page_path_regex=None):
- """Fetch event count, optionally filtered by page path regex."""
+def _count_events(event_name, params, page_path_regex=None, click_url_regex=None):
+ """Fetch event count, optionally filtered by page path and/or click URL regex."""
dimensions = [DIMENSION_EVENT_NAME]
if page_path_regex:
dimensions.append(DIMENSION_PAGE_PATH)
+ if click_url_regex:
+ dimensions.append(DIMENSION_CUSTOM_URL)
df = get_data_df_from_fields(
[METRIC_EVENT_COUNT],
@@ -66,14 +63,15 @@ def _count_events(event_name, params, page_path_regex=None):
return 0
if page_path_regex:
- pattern = re.compile(page_path_regex)
- mask = df[DIMENSION_PAGE_PATH["alias"]].str.match(pattern, na=False)
- return int(df.loc[mask, METRIC_EVENT_COUNT["alias"]].sum())
+ df = df[df[DIMENSION_PAGE_PATH["alias"]].str.match(page_path_regex, na=False)]
+
+ if click_url_regex:
+ df = df[df[DIMENSION_CUSTOM_URL["alias"]].str.contains(click_url_regex, na=False)]
return int(df[METRIC_EVENT_COUNT["alias"]].sum())
-def get_custom_event_change(event_name, params_current, params_prior, page_path_regex=None):
+def get_custom_event_change(event_name, params_current, params_prior, page_path_regex=None, click_url_regex=None):
"""Fetch a custom event count with month-over-month change.
Args:
@@ -81,12 +79,13 @@ def get_custom_event_change(event_name, params_current, params_prior, page_path_
params_current: Analytics params for the current period.
params_prior: Analytics params for the prior period.
page_path_regex: Optional regex to filter by page path.
+ click_url_regex: Optional regex to filter by click URL.
Returns:
Dict with "current", "prior", and "change" keys.
"""
- current_count = _count_events(event_name, params_current, page_path_regex)
- prior_count = _count_events(event_name, params_prior, page_path_regex)
+ current_count = _count_events(event_name, params_current, page_path_regex, click_url_regex)
+ prior_count = _count_events(event_name, params_prior, page_path_regex, click_url_regex)
change = None
if prior_count > 0:
@@ -95,21 +94,26 @@ def get_custom_event_change(event_name, params_current, params_prior, page_path_
return {"current": current_count, "prior": prior_count, "change": change}
-def get_event_detail_table(event_name, params, page_path_regex=None):
+def get_event_detail_table(event_name, params, page_path_regex=None, click_url_regex=None):
"""Fetch event details broken down by page path and entity name.
Args:
event_name: GA4 event name.
params: Analytics params for the period.
page_path_regex: Optional regex to filter by page path.
+ click_url_regex: Optional regex to filter by click URL.
Returns:
List of dicts with "page_path", "entity_name", and "count" keys,
- sorted by count descending.
+ sorted by count descending. Includes "click_url" when click_url_regex is used.
"""
+ dimensions = [DIMENSION_EVENT_NAME, DIMENSION_PAGE_PATH, DIMENSION_ENTITY_NAME]
+ if click_url_regex:
+ dimensions.append(DIMENSION_CUSTOM_URL)
+
df = get_data_df_from_fields(
[METRIC_EVENT_COUNT],
- [DIMENSION_EVENT_NAME, DIMENSION_PAGE_PATH, DIMENSION_ENTITY_NAME],
+ dimensions,
dimension_filter=f"eventName=={event_name}",
**params,
)
@@ -118,14 +122,25 @@ def get_event_detail_table(event_name, params, page_path_regex=None):
return []
if page_path_regex:
- pattern = re.compile(page_path_regex)
- df = df[df[DIMENSION_PAGE_PATH["alias"]].str.match(pattern, na=False)]
+ df = df[df[DIMENSION_PAGE_PATH["alias"]].str.match(page_path_regex, na=False)]
+
+ if click_url_regex:
+ df = df[df[DIMENSION_CUSTOM_URL["alias"]].str.contains(click_url_regex, na=False)]
if len(df) == 0:
return []
- result = df[[DIMENSION_PAGE_PATH["alias"], DIMENSION_ENTITY_NAME["alias"], METRIC_EVENT_COUNT["alias"]]].copy()
- result.columns = ["page_path", "entity_name", "count"]
+ export_cols = [DIMENSION_PAGE_PATH["alias"], DIMENSION_ENTITY_NAME["alias"]]
+ output_names = ["page_path", "entity_name"]
+ if click_url_regex:
+ export_cols.append(DIMENSION_CUSTOM_URL["alias"])
+ output_names.append("click_url")
+ export_cols.append(METRIC_EVENT_COUNT["alias"])
+ output_names.append("count")
+
+ result = df[export_cols].copy()
+ result.columns = output_names
+ result["count"] = result["count"].astype(int)
result = result.sort_values("count", ascending=False)
return result.to_dict(orient="records")
@@ -396,15 +411,13 @@ def fetch_data(
print("Fetching sessions and engagement data...")
df_sessions_current = get_data_df_from_fields(
- [METRIC_SESSIONS, METRIC_ENGAGED_SESSIONS, METRIC_ENGAGEMENT_RATE], [], **params,
+ [METRIC_SESSIONS, METRIC_ENGAGEMENT_RATE], [], **params,
)
df_sessions_prior = get_data_df_from_fields(
- [METRIC_SESSIONS, METRIC_ENGAGED_SESSIONS, METRIC_ENGAGEMENT_RATE], [], **params_prior,
+ [METRIC_SESSIONS, METRIC_ENGAGEMENT_RATE], [], **params_prior,
)
sessions_current = int(df_sessions_current[METRIC_SESSIONS["alias"]].sum()) if len(df_sessions_current) > 0 else 0
sessions_prior = int(df_sessions_prior[METRIC_SESSIONS["alias"]].sum()) if len(df_sessions_prior) > 0 else 0
- engaged_sessions_current = int(df_sessions_current[METRIC_ENGAGED_SESSIONS["alias"]].sum()) if len(df_sessions_current) > 0 else 0
- engaged_sessions_prior = int(df_sessions_prior[METRIC_ENGAGED_SESSIONS["alias"]].sum()) if len(df_sessions_prior) > 0 else 0
engagement_current = float(df_sessions_current[METRIC_ENGAGEMENT_RATE["alias"]].mean()) if len(df_sessions_current) > 0 else 0
engagement_prior = float(df_sessions_prior[METRIC_ENGAGEMENT_RATE["alias"]].mean()) if len(df_sessions_prior) > 0 else 0
@@ -440,10 +453,6 @@ def fetch_data(
"current": sessions_current,
"prior": sessions_prior,
},
- "engaged_sessions": {
- "current": engaged_sessions_current,
- "prior": engaged_sessions_prior,
- },
"engagement_rate": {
"current": engagement_current,
"prior": engagement_prior,
@@ -470,12 +479,14 @@ def fetch_data(
data[f"event_{key}"] = get_custom_event_change(
event["event_name"], params, params_prior,
page_path_regex=event.get("page_path_regex"),
+ click_url_regex=event.get("click_url_regex"),
)
if event.get("detail_table"):
print(f"Fetching {event['label']} detail table...")
data[f"event_{key}_detail"] = get_event_detail_table(
event["event_name"], params,
page_path_regex=event.get("page_path_regex"),
+ click_url_regex=event.get("click_url_regex"),
)
if event_charts:
diff --git a/analytics/analytics_package/analytics/static_site/template/index.html b/analytics/analytics_package/analytics/static_site/template/index.html
index fca0fdf14..d593b14f8 100644
--- a/analytics/analytics_package/analytics/static_site/template/index.html
+++ b/analytics/analytics_package/analytics/static_site/template/index.html
@@ -489,11 +489,10 @@
const previous = traffic[1] || {};
const sessions = meta.sessions || {};
- const engagedSessions = meta.engaged_sessions || {};
const engagement = meta.engagement_rate || {};
const usersChange = pctChange(latest.users, previous.users);
- const engagedSessionsChange = pctChange(engagedSessions.current, engagedSessions.prior);
+ const sessionsChange = pctChange(sessions.current, sessions.prior);
const pageviewsChange = pctChange(latest.pageviews, previous.pageviews);
const engagementChange = pctChange(engagement.current, engagement.prior);
@@ -511,9 +510,9 @@
`Total number of unique individuals who visited your site during ${monthName}.`
) +
statCard(
- formatNumber(engagedSessions.current != null ? engagedSessions.current : (sessions.current || 0)), 'Engaged Sessions',
- engagedSessionsChange,
- `Sessions where users actively engaged with your site during ${monthName} (stayed 10+ seconds, viewed 2+ pages, or triggered a conversion). Learn more.`
+ formatNumber(sessions.current || 0), 'User Sessions',
+ sessionsChange,
+ `Total number of visits to your site during ${monthName}. A single user can have multiple sessions.`
) +
statCard(
formatNumber(latest.pageviews), 'Pageviews',
@@ -551,7 +550,10 @@
for (let i = 0; i < countCards.length; i += 2) {
const pair = countCards.slice(i, i + 2);
const rowLabel = pair[0].label.split('\n')[0];
- html += `${escapeHtml(rowLabel)}
`;
+ const pairSharesLabel = pair.length > 1 && pair[1].label.split('\n')[0] === rowLabel;
+ if (pairSharesLabel) {
+ html += `${escapeHtml(rowLabel)}
`;
+ }
// Stat cards row
for (const card of pair) {
@@ -580,7 +582,8 @@
if (chartDataByKey[card.event_key] && chartDataByKey[card.event_key].length > 0) {
const canvasId = `event-trend-chart-${chartIndex++}`;
const parts = card.label.split('\n');
- const chartTitle = escapeHtml(rowLabel) + ' Over Time' + (parts[1] ? ` (${escapeHtml(parts[1].replace(/[()]/g, ''))})` : '');
+ const cardLabel = parts[0];
+ const chartTitle = escapeHtml(cardLabel) + ' Over Time' + (parts[1] ? ` (${escapeHtml(parts[1].replace(/[()]/g, ''))})` : '');
html += `