Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions analytics/analytics_package/analytics/static_site/charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
],
},
],
Expand Down
8 changes: 5 additions & 3 deletions analytics/analytics_package/analytics/static_site/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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", {}),
}

Expand Down
67 changes: 39 additions & 28 deletions analytics/analytics_package/analytics/static_site/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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],
Expand All @@ -66,27 +63,29 @@ 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:
event_name: GA4 event name (e.g., "chat_submitted").
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:
Expand All @@ -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,
)
Expand All @@ -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")

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand All @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,11 +489,10 @@ <h3 class="fui-card-header-title fui-heading-xsmall">Filter Selections</h3>
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);

Expand All @@ -511,9 +510,9 @@ <h3 class="fui-card-header-title fui-heading-xsmall">Filter Selections</h3>
`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). <a href="https://support.google.com/analytics/answer/11109416" target="_blank" rel="noopener noreferrer">Learn more</a>.`
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',
Expand Down Expand Up @@ -551,7 +550,10 @@ <h3 class="fui-card-header-title fui-heading-xsmall">Filter Selections</h3>
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 += `<h3 class="fui-heading-xsmall fui-grid-item-12">${escapeHtml(rowLabel)}</h3>`;
const pairSharesLabel = pair.length > 1 && pair[1].label.split('\n')[0] === rowLabel;
if (pairSharesLabel) {
html += `<h3 class="fui-heading-xsmall fui-grid-item-12">${escapeHtml(rowLabel)}</h3>`;
}

// Stat cards row
for (const card of pair) {
Expand Down Expand Up @@ -580,7 +582,8 @@ <h3 class="fui-card-header-title fui-heading-xsmall">Filter Selections</h3>
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 += `
<div class="fui-card fui-grid-item-6">
<div class="fui-card-header">
Expand Down Expand Up @@ -662,6 +665,7 @@ <h3 class="fui-card-header-title fui-heading-xsmall">${chartTitle}</h3>
if (!detail) continue;

const hasDatasetTitle = detail.some(r => r.dataset_title);
const hasClickUrl = detail.some(r => r.click_url);
const isEmpty = detail.length === 0;

let tableContent;
Expand All @@ -672,9 +676,12 @@ <h3 class="fui-card-header-title fui-heading-xsmall">${chartTitle}</h3>
const label = r.dataset_title || r.entity_name || r.page_path || '-';
const link = r.page_path ? `${siteBase}${r.page_path.replace(/\/export.*/, '')}` : null;
const cell = link ? `<a href="${safeHref(link)}" target="_blank" rel="noopener noreferrer">${escapeHtml(label)}</a>` : escapeHtml(label);
return `<tr><td>${cell}</td><td class="col-number">${formatNumber(r.count)}</td></tr>`;
const fileTd = hasClickUrl ? `<td class="truncate">${escapeHtml(r.file_label || fileNameFromUrl(r.click_url))}</td>` : '';
return `<tr><td>${cell}</td>${fileTd}<td class="col-number">${formatNumber(r.count)}</td></tr>`;
}).join('');
tableContent = `<table class="detail-table collapsible-table"><thead><tr><th>${escapeHtml(entityLabel)}</th><th class="col-number">Count</th></tr></thead><tbody>${rows}</tbody></table>`;
const fileColLabel = event.detail_file_column || 'File';
const fileHeader = hasClickUrl ? `<th>${escapeHtml(fileColLabel)}</th>` : '';
tableContent = `<table class="detail-table collapsible-table"><thead><tr><th>${escapeHtml(entityLabel)}</th>${fileHeader}<th class="col-number">Count</th></tr></thead><tbody>${rows}</tbody></table>`;
} else {
const rows = detail.map(r => {
const raw = r.entity_name && r.entity_name !== '(not set)' ? r.entity_name : null;
Expand Down Expand Up @@ -945,6 +952,18 @@ <h3 class="fui-card-header-title fui-heading-xsmall">${escapeHtml(event.label)}
}
}

function fileNameFromUrl(url) {
if (!url) return '-';
try {
const pathname = new URL(url).pathname.replace(/\/+$/, '');
const segments = pathname.split('/');
return decodeURIComponent(segments[segments.length - 1] || url);
} catch (e) {
const parts = url.split('/');
return parts[parts.length - 1] || url;
}
}
Comment thread
MillenniumFalconMechanic marked this conversation as resolved.

function formatChange(change) {
if (change == null) return '-';
return (change >= 0 ? '+' : '') + (change * 100).toFixed(1) + '%';
Expand Down
4 changes: 2 additions & 2 deletions analytics/anvil-explorer-sheets/constants.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# CHANGE THESE VALUES TO GENERATE NEW REPORTS
# The date of the current month to report on (yyyy-mm)
CURRENT_MONTH = "2026-04"
CURRENT_MONTH = "2026-05"
# The name of the folder in which to save the report
PARENT_FOLDER_NAME = "April 2026"
PARENT_FOLDER_NAME = "May 2026"

# The name of the spreadsheet with the report
SHEET_NAME = "AnVIL Explorer"
Expand Down
4 changes: 2 additions & 2 deletions analytics/hca-explorer-sheets/constants.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# CHANGE THESE VALUES TO GENERATE NEW REPORTS
# The date of the current month to report on (yyyy-mm)
CURRENT_MONTH = "2026-04"
CURRENT_MONTH = "2026-05"
# The name of the folder in which to save the report
PARENT_FOLDER_NAME = "April 2026"
PARENT_FOLDER_NAME = "May 2026"

# The name of the spreadsheet with the report
SHEET_NAME = "HCA Explorer"
Expand Down
6 changes: 3 additions & 3 deletions analytics/hca-explorer-sheets/generate_static_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ def resolve_project_titles(data):
"file_downloads_position": 3,
"event_counts": [
{"label": "Export to Terra\n(Single Project)", "event_key": "dataset_analyze_in_terra_requested"},
{"label": "Export to Terra\n(Cross-Dataset)", "event_key": "index_analyze_in_terra_requested"},
{"label": "Export to Terra\n(Cross-Project)", "event_key": "index_analyze_in_terra_requested"},
{"label": "curl Command\n(Single Project)", "event_key": "dataset_bulk_download_requested"},
{"label": "curl Command\n(Cross-Dataset)", "event_key": "index_bulk_download_requested"},
{"label": "curl Command\n(Cross-Project)", "event_key": "index_bulk_download_requested"},
{"label": "File Manifest\n(Single Project)", "event_key": "dataset_file_manifest_requested"},
{"label": "File Manifest\n(Cross-Dataset)", "event_key": "index_file_manifest_requested"},
{"label": "File Manifest\n(Cross-Project)", "event_key": "index_file_manifest_requested"},
],
},
property_id=HCA_ID,
Expand Down
4 changes: 2 additions & 2 deletions analytics/lungmap-analytics/sheets/constants.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# CHANGE THESE VALUES TO GENERATE NEW REPORTS
# The date of the current month to report on (yyyy-mm)
CURRENT_MONTH = "2026-04"
CURRENT_MONTH = "2026-05"
# The name of the folder in which to save the report
PARENT_FOLDER_NAME = "April 2026"
PARENT_FOLDER_NAME = "May 2026"

# The name of the spreadsheet with the report
SHEET_NAME = "Lungmap Data Browser"
Expand Down
6 changes: 3 additions & 3 deletions analytics/lungmap-analytics/sheets/generate_static_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ def resolve_project_titles(data):
"file_downloads_position": 3,
"event_counts": [
{"label": "Export to Terra\n(Single Project)", "event_key": "dataset_analyze_in_terra_requested"},
{"label": "Export to Terra\n(Cross-Dataset)", "event_key": "index_analyze_in_terra_requested"},
{"label": "Export to Terra\n(Cross-Project)", "event_key": "index_analyze_in_terra_requested"},
{"label": "curl Command\n(Single Project)", "event_key": "dataset_bulk_download_requested"},
{"label": "curl Command\n(Cross-Dataset)", "event_key": "index_bulk_download_requested"},
{"label": "curl Command\n(Cross-Project)", "event_key": "index_bulk_download_requested"},
{"label": "File Manifest\n(Single Project)", "event_key": "dataset_file_manifest_requested"},
{"label": "File Manifest\n(Cross-Dataset)", "event_key": "index_file_manifest_requested"},
{"label": "File Manifest\n(Cross-Project)", "event_key": "index_file_manifest_requested"},
],
},
property_id=LUNGMAP_ID,
Expand Down
Loading
Loading