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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,10 @@ The legacy `GET /airflow-fleet-health` Better Stack monitor endpoint has been re
`GET /apps` reads the Segment BigQuery export and shows the highest observed Apollos
version signal per church/app/platform. It uses the analytics metadata sent by the mobile and TV
apps, including the exported `apollos_version`, `app_version`, `app_update_id`, `bundle_id`,
`application_name`, `church`, `apollos_platform`, `source_revision`, and `source_version` fields.
`application_name`, `church`, `build_church`, `apollos_platform`, `source_revision`, and
`source_version` fields. For mobile apps, `build_church` is the configured deployment slug while
`church` is a church selected inside the app (which can differ in Preview). Older mobile events
without `build_church` show "Unknown build slug" rather than mislabeling the selected church.
The public US Apple lookup is shown separately for iOS bundle IDs, with the fetch time. It can
lag App Store Connect and must not be treated as an authoritative published release. The seen
build is the version reported by the selected installation in Segment, not the latest shipped
Expand Down
16 changes: 11 additions & 5 deletions app_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

FIELD_CANDIDATES = {
"church": ("church", "group_id", "groupId"),
"build_church": ("build_church", "buildChurch"),
"apollos_platform": ("apollos_platform", "apollosPlatform", "apollosplatform"),
"apollos_version": ("apollos_version", "apollosVersion"),
"app_version": ("app_version", "appVersion"),
Expand Down Expand Up @@ -262,8 +263,6 @@ def _build_app_versions_query(
CURRENT_TIMESTAMP(),
INTERVAL @lookback_days DAY
)
AND `{version_column}` IS NOT NULL
AND CAST(`{version_column}` AS STRING) != ''
"""
)

Expand All @@ -278,6 +277,7 @@ def _build_app_versions_query(
SELECT
seen_at,
COALESCE(NULLIF(church, ''), 'Unknown church') AS church,
NULLIF(build_church, '') AS build_church,
COALESCE(
NULLIF(apollos_platform, ''),
IF(source_dataset = 'apollos_roku', 'roku', NULL),
Expand All @@ -304,7 +304,7 @@ def _build_app_versions_query(
source_table,
version_source
FROM version_events
WHERE apollos_version IS NOT NULL AND apollos_version != ''
WHERE apollos_version IS NOT NULL OR build_church IS NOT NULL
),
filtered_events AS (
SELECT *
Expand Down Expand Up @@ -335,17 +335,20 @@ def _build_app_versions_query(
SELECT
app_identity_key,
ARRAY_AGG(
church
IF(apollos_version IS NOT NULL, church, NULL) IGNORE NULLS
ORDER BY IF(church = 'Unknown church', 1, 0), church
LIMIT 1
)[OFFSET(0)] AS church
)[SAFE_OFFSET(0)] AS church,
ARRAY_AGG(build_church IGNORE NULLS ORDER BY seen_at DESC LIMIT 1)
[SAFE_OFFSET(0)] AS build_church
FROM app_identity_events
GROUP BY app_identity_key
),
version_observations AS (
SELECT
events.app_identity_key,
display_churches.church,
display_churches.build_church,
events.apollos_platform,
events.application_name,
events.bundle_id,
Expand All @@ -362,9 +365,11 @@ def _build_app_versions_query(
FROM app_identity_events events
JOIN display_churches
USING (app_identity_key)
WHERE events.apollos_version IS NOT NULL
GROUP BY
events.app_identity_key,
display_churches.church,
display_churches.build_church,
events.apollos_platform,
events.application_name,
events.bundle_id,
Expand All @@ -380,6 +385,7 @@ def _build_app_versions_query(
)
SELECT
observation.church,
observation.build_church,
observation.apollos_platform,
observation.application_name,
observation.bundle_id,
Expand Down
9 changes: 7 additions & 2 deletions templates/app_versions.html
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ <h2>App data is unavailable</h2>
<table class="version-table">
<thead>
<tr>
<th>Church</th>
<th>{{ "Build slug / church seen" if tab.key in ("ios", "android") else "Church seen" }}</th>
<th>App</th>
<th>Seen build</th>
{% if tab.key == "ios" %}<th>Apple lookup (US)</th>{% endif %}
Expand All @@ -171,7 +171,12 @@ <h2>App data is unavailable</h2>
{% for row in tab.rows %}
<tr>
<td>
<strong>{{ row.church }}</strong>
{% if tab.key in ("ios", "android") %}
<strong>{{ row.build_church or "Unknown build slug" }}</strong><br />
<span class="version-muted">Church seen: {{ row.church }}</span>
{% else %}
<strong>{{ row.church }}</strong>
{% endif %}
</td>
<td>{{ row.application_name }}<br /><span class="version-muted">{{ row.bundle_id }}</span></td>
<td>
Expand Down
16 changes: 15 additions & 1 deletion tests/test_app_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,7 @@ def test_builds_query_from_discovered_segment_columns(self):
("apollos", "tracks"): {
"timestamp": "timestamp",
"church": "church",
"buildchurch": "buildChurch",
"apollos_version": "apollos_version",
"app_version": "app_version",
"source_revision": "source_revision",
Expand Down Expand Up @@ -660,6 +661,14 @@ def test_builds_query_from_discovered_segment_columns(self):
)
self.assertIn("CAST(NULL AS STRING) AS source_revision", query)
self.assertIn("NULLIF(CAST(`groupId` AS STRING), '') AS church", query)
self.assertIn("NULLIF(CAST(`buildChurch` AS STRING), '') AS build_church", query)
self.assertIn("[SAFE_OFFSET(0)] AS church", query)
self.assertIn("[SAFE_OFFSET(0)] AS build_church", query)
self.assertIn("observation.build_church", query)
self.assertNotIn("AND `apollos_version` IS NOT NULL", query)
self.assertIn("WHERE apollos_version IS NOT NULL OR build_church IS NOT NULL", query)
self.assertIn("IF(apollos_version IS NOT NULL, church, NULL) IGNORE NULLS", query)
self.assertIn("WHERE events.apollos_version IS NOT NULL", query)
self.assertIn("'analytics_library' AS version_source", query)
self.assertIn("TIMESTAMP_SUB(", query)
self.assertIn("INTERVAL @lookback_days DAY", query)
Expand Down Expand Up @@ -797,6 +806,8 @@ def test_apps_route_renders_platform_tabs(self):
self.assertIn('data-version-tab="android"', body)
self.assertIn('id="version-panel-ios"', body)
self.assertIn("One Church", body)
self.assertIn("Unknown build slug", body)
self.assertIn("Church seen: one-church", body)
self.assertIn("<th>Seen build</th>", body)
self.assertIn("<th>Apple lookup (US)</th>", body)
self.assertIn("<th>Expo Runtime</th>", body)
Expand All @@ -810,6 +821,7 @@ def test_apps_route_renders_platform_tabs(self):
def test_preview_shows_church_slug_and_distinguishes_seen_from_apple_lookup(self):
row = {
"church": "apollos_demo",
"build_church": "apollos_preview",
"bundle_id": "com.differential.apollospreview",
"application_name": "Apollos Preview",
"app_version": "1.0.0",
Expand Down Expand Up @@ -838,7 +850,9 @@ def test_preview_shows_church_slug_and_distinguishes_seen_from_apple_lookup(self
}
with patch.object(app_module, "get_app_versions_context", return_value=context):
body = self.client.get("/apps").get_data(as_text=True)
self.assertIn("<strong>apollos_demo</strong>", body)
self.assertIn("<strong>apollos_preview</strong>", body)
self.assertIn("Church seen: apollos_demo", body)
self.assertNotIn("<strong>apollos_demo</strong>", body)
self.assertIn("Apollos Preview", body)
self.assertIn("Last seen unknown", body)
self.assertIn("Checked 2026-09-25 08:45 PM EDT", body)
Expand Down
Loading