diff --git a/artifacts/observe-reach.webp b/artifacts/observe-reach.webp
new file mode 100644
index 0000000000..a5c8ffa1e7
Binary files /dev/null and b/artifacts/observe-reach.webp differ
diff --git a/messages/en.context.json b/messages/en.context.json
index cbabf70e51..e04fa74e89 100644
--- a/messages/en.context.json
+++ b/messages/en.context.json
@@ -632,8 +632,12 @@
"builds-last-month-trend": "Used in Capgo web console areas: pages/admin/dashboard. Role: UI sentence. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"builds-period": "Used in Capgo web console areas: pages/admin/dashboard. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"builds-trend": "Used in Capgo web console areas: pages/admin/dashboard. Role: UI sentence. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
- "bundle": "Used in Capgo web console areas: app, components/dashboard, components/tables, composables, layouts. Role: short UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
+ "bundle": "Used in Capgo web console areas: app, components/bundle, components/dashboard, components/tables, composables. Role: short UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"bundle-active": "Used in Capgo web console areas: pages/app. Role: short UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
+ "bundle-adoption": "Used in Capgo web console areas: components/bundle. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
+ "bundle-adoption-devices": "Used in Capgo web console areas: components/bundle. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
+ "bundle-adoption-empty": "Used in Capgo web console areas: components/bundle. Role: empty state text. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
+ "bundle-adoption-error": "Used in Capgo web console areas: components/bundle. Role: toast or status message. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"bundle-assigned-to-this-channel": "Used in Capgo web console areas: pages/app. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"bundle-comment": "Used in Capgo web console areas: pages/app. Role: short UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"bundle-comment-empty": "Used in Capgo web console areas: pages/app. Role: empty state text. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
@@ -1501,7 +1505,7 @@
"link-this-bundle-to-another-channel": "Used in Capgo web console areas: pages/app. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"linked-bundle": "Used in Capgo web console areas: pages/app. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"loading": "Used in Capgo web console areas: components, components/bundle, components/dashboard, components/permissions, components/tables. Role: short UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
- "loading-statistics": "Used in Capgo web console areas: pages/app. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
+ "loading-statistics": "Used in Capgo web console areas: components/bundle, pages/app. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"loading-version": "Used in Capgo web console areas: components/tables. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"log-as": "Used in Capgo web console areas: components/dashboard. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"log-in": "Used in Capgo web console areas: pages. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
@@ -2169,6 +2173,7 @@
"registration-to-subscription-conversion": "Used in Capgo web console areas: pages/admin/dashboard. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"registrations-by-source": "Used in Capgo web console areas: pages/admin/dashboard. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"registrations-by-source-description": "Used in Capgo web console areas: pages/admin/dashboard. Role: helper or description text. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
+ "release-banner-adoption": "Used in Capgo web console areas: components/dashboard. Role: UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"release-or-30-days": "Used in Capgo web console. Role: filter or date-range option label about \"release or 30 days\". Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"release-status": "Used in Capgo web console. Role: UI label about \"release status\". Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
"released": "Used in Capgo web console areas: components/dashboard, pages/app. Role: short UI label. Translate for UI; keep Capgo product names, code, and placeholders unchanged.",
diff --git a/messages/en.json b/messages/en.json
index 20c4d5400e..5e25a1172a 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -609,6 +609,10 @@
"builds-trend": "Native Builds Trend (Total, iOS, Android)",
"bundle": "Bundle",
"bundle-active": "Active",
+ "bundle-adoption": "Bundle reach",
+ "bundle-adoption-devices": "{count} / {total} devices",
+ "bundle-adoption-empty": "No devices have checked in on {version} yet.",
+ "bundle-adoption-error": "Could not load how many devices currently have {version}.",
"bundle-comment": "Comment",
"bundle-comment-empty": "No comment",
"bundle-comment-placeholder": "Release notes, commit hash, or context",
@@ -1543,6 +1547,7 @@
"new-name-to-short": "The apikey name is too short. It must be at least 4 characters long",
"new-record": "New Record",
"new-release-available": "New release available",
+ "release-banner-adoption": "{percent} of devices",
"next": "Next",
"next-run": "Next update",
"no": "no",
@@ -1791,7 +1796,7 @@
"org-notifications-bundle-deployed-desc": "Send bundle deployment notifications to the organization's management email",
"org-notifications-channel-self-rejected-desc": "Send channel self-assignment rejection notifications to the organization's management email",
"org-notifications-credit-usage-desc": "Send credit usage alerts to the organization's management email",
- "org-notifications-deploy-stats-desc": "Send 24-hour deployment statistics to the organization's management email",
+ "org-notifications-deploy-stats-desc": "Send 24-hour install counts and how many devices reached the deployed bundle to the organization's management email",
"org-notifications-description": "Control which email notifications are sent to your organization's management email address. These settings only apply when the management email is different from individual admin email addresses.",
"org-notifications-device-error-desc": "Send device update error notifications to the organization's management email",
"org-notifications-monthly-stats-desc": "Send monthly statistics to the organization's management email",
diff --git a/playwright/e2e/bundle-adoption.spec.ts b/playwright/e2e/bundle-adoption.spec.ts
new file mode 100644
index 0000000000..4f7e882377
--- /dev/null
+++ b/playwright/e2e/bundle-adoption.spec.ts
@@ -0,0 +1,17 @@
+import { expect, test } from '../support/commands'
+
+test.describe('Bundle reach', () => {
+ test.beforeEach(async ({ page }) => {
+ await page.login('test@capgo.app', 'testtest')
+ })
+
+ test('shows reach on observe updater', async ({ page }) => {
+ await page.goto('/app/com.demo.app/observe/updater')
+ const reachCard = page.locator('[data-test="bundle-adoption-card"]').first()
+ await expect(reachCard).toBeVisible()
+ await expect(reachCard).toContainText('Bundle reach')
+
+ await reachCard.click()
+ await expect(page).toHaveURL(/\/app\/com\.demo\.app\/channel\/\d+\/statistics/)
+ })
+})
diff --git a/playwright/visual-diff.config.ts b/playwright/visual-diff.config.ts
index 32317805c2..e18d58de3c 100644
--- a/playwright/visual-diff.config.ts
+++ b/playwright/visual-diff.config.ts
@@ -61,6 +61,11 @@ export const visualDiffRoutes: VisualDiffRoute[] = [
{ slug: 'observe-native', path: '/app/com.demo.app/observe/native', auth: true },
{ slug: 'observe-compatibility', path: '/app/com.demo.app/observe/compatibility', auth: true },
{ slug: 'observe-plugins', path: '/app/com.demo.app/observe/plugins', auth: true },
+ {
+ slug: 'channel-statistics',
+ path: '/app/com.demo.app/channel/1/statistics',
+ auth: true,
+ },
{
slug: 'api-keys-app-preview',
path: '/apikeys',
diff --git a/src/components/bundle/BundleAdoptionCard.vue b/src/components/bundle/BundleAdoptionCard.vue
new file mode 100644
index 0000000000..d579af7129
--- /dev/null
+++ b/src/components/bundle/BundleAdoptionCard.vue
@@ -0,0 +1,122 @@
+
+
+
+
+
diff --git a/src/components/dashboard/ReleaseBanner.vue b/src/components/dashboard/ReleaseBanner.vue
index 904cbe79ca..69d763667e 100644
--- a/src/components/dashboard/ReleaseBanner.vue
+++ b/src/components/dashboard/ReleaseBanner.vue
@@ -4,7 +4,10 @@ import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import IconCheckCircle from '~icons/lucide/check-circle'
import IconTrendingUp from '~icons/lucide/trending-up'
-import { formatDistanceToNow } from '~/services/date'
+import { getLatestDayVersionAdoption } from '~/services/bundleAdoption'
+import { useChartData } from '~/services/chartDataService'
+import { formatDistanceToNow, getChartDateRange } from '~/services/date'
+import { formatNumberValue } from '~/services/formatLocale'
import { useSupabase } from '~/services/supabase'
import { useOrganizationStore } from '~/stores/organization'
@@ -21,6 +24,8 @@ const isLoading = ref(false)
const lastVersion = ref
{{ t('version') }} {{ lastVersion }} — {{ t('released') }} {{ lastReleaseDisplay }} + + · {{ t('release-banner-adoption', { percent: adoptionPercentLabel }) }} +
diff --git a/src/pages/app/[app].bundle.[bundle].vue b/src/pages/app/[app].bundle.[bundle].vue index 493f140377..4a7119b136 100644 --- a/src/pages/app/[app].bundle.[bundle].vue +++ b/src/pages/app/[app].bundle.[bundle].vue @@ -815,236 +815,238 @@ async function deleteBundle() {