Skip to content
Open
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
10 changes: 9 additions & 1 deletion messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1590,8 +1590,16 @@
"native-observe-subtitle": "Native app health, launch timing, WebView load timing, and release impact from the updater plugin.",
"native-observe-tracked-devices": "Tracked devices",
"native-observe-version": "Version",
"native-observe-version-group": "Version grouping",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The new native-observe keys disrupt the alphabetical ordering of this block: native-observe-group-version*, native-observe-platform, and native-observe-channel are placed after native-observe-version-group even though they sort earlier (c/g/p), and channel belongs before open-logs. Reorder the keys to match the surrounding sorted convention so the catalog stays scannable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At messages/en.json, line 1593:

<comment>The new native-observe keys disrupt the alphabetical ordering of this block: `native-observe-group-version*`, `native-observe-platform`, and `native-observe-channel` are placed after `native-observe-version-group` even though they sort earlier (`c`/`g`/`p`), and `channel` belongs before `open-logs`. Reorder the keys to match the surrounding sorted convention so the catalog stays scannable.</comment>

<file context>
@@ -1590,8 +1590,16 @@
   "native-observe-subtitle": "Native app health, launch timing, WebView load timing, and release impact from the updater plugin.",
   "native-observe-tracked-devices": "Tracked devices",
   "native-observe-version": "Version",
+  "native-observe-version-group": "Version grouping",
+  "native-observe-group-version": "Version",
+  "native-observe-group-version-platform": "Version + platform",
</file context>

"native-observe-group-version": "Version",
"native-observe-group-version-platform": "Version + platform",
"native-observe-group-version-platform-channel": "Version + platform + channel",
"native-observe-platform": "Platform",
"native-observe-channel": "Channel",
"native-observe-version-health": "Version breakdown",
"native-observe-version-health-help": "Each row is one reported native app version in the same selected period; the summary above is the total across versions.",
"native-observe-version-health-help": "Each row is one reported app version in the same selected period; the summary above is the total across versions.",
"native-observe-version-health-help-platform": "Each row is one version and platform combo in the selected period (for example 1.0.0 android).",
"native-observe-version-health-help-platform-channel": "Each row is one version, platform, and channel combo in the selected period (for example 1.0.0 android production).",
"native-observe-volume": "Events and issues",
"native-observe-volume-help": "Daily native events, launches, WebView loads, and issue counts across all reported app versions.",
"native-observe-webview-loads": "WebView loads",
Expand Down
54 changes: 54 additions & 0 deletions src/components/dashboard/VersionGroupSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'

export type VersionGroupOption = 'version' | 'version_platform' | 'version_platform_channel'

const props = defineProps<{
modelValue: VersionGroupOption
}>()

const emit = defineEmits<{
'update:modelValue': [value: VersionGroupOption]
}>()

const { t } = useI18n()
const options: VersionGroupOption[] = ['version', 'version_platform', 'version_platform_channel']
const labels: Record<VersionGroupOption, string> = {
version: 'native-observe-group-version',
version_platform: 'native-observe-group-version-platform',
version_platform_channel: 'native-observe-group-version-platform-channel',
}

const selectedLabel = computed(() => labels[props.modelValue])

function select(option: VersionGroupOption) {
if (option !== props.modelValue)
emit('update:modelValue', option)
}
</script>

<template>
<fieldset
class="flex flex-wrap items-center p-1 space-x-1 shrink-0 bg-gray-200 rounded-lg dark:bg-gray-800"
data-testid="version-group-selector"
>
<legend class="sr-only">
{{ t('native-observe-version-group') }}: {{ t(selectedLabel) }}
</legend>
<button
v-for="option in options"
:key="option"
type="button"
:aria-pressed="props.modelValue === option"
:aria-label="t(labels[option])"
class="flex justify-center items-center h-9 min-h-9 min-w-[2.75rem] px-2.5 sm:px-3 py-1.5 text-xs font-medium text-center whitespace-nowrap rounded-md transition-colors duration-150 cursor-pointer"
:class="props.modelValue === option
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'"
@click="select(option)"
>
{{ t(labels[option]) }}
</button>
</fieldset>
</template>
71 changes: 59 additions & 12 deletions src/pages/app/[app].observe.native.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import type { ChartData, ChartOptions } from 'chart.js'
import type { VersionGroupOption } from '~/components/dashboard/VersionGroupSelector.vue'
import { BarElement, CategoryScale, Chart, Legend, LinearScale, LineElement, PointElement, Tooltip } from 'chart.js'
import { computed, ref, watch } from 'vue'
import { Bar, Line } from 'vue-chartjs'
Expand All @@ -12,6 +13,7 @@ import IconRocket from '~icons/lucide/rocket'
import IconTimer from '~icons/lucide/timer'
import DeliveryLatencyPanel from '~/components/dashboard/DeliveryLatencyPanel.vue'
import PeriodDaySelector from '~/components/dashboard/PeriodDaySelector.vue'
import VersionGroupSelector from '~/components/dashboard/VersionGroupSelector.vue'
import { useNativeObserveStats } from '~/composables/useNativeObserveStats'
import { formatLocalDateShort } from '~/services/date'
import { formatNumberValue } from '~/services/formatLocale'
Expand Down Expand Up @@ -63,8 +65,11 @@ interface NativeObserveStatsResponse {
p99_ms: number | null
is_issue: boolean
}>
version_group: VersionGroupOption
versions: Array<{
version_name: string
platform: string | null
channel_name: string | null
events: number
devices: number
issue_count: number
Expand All @@ -91,14 +96,31 @@ const packageId = computed(() => {
})
const appRouteSegment = computed(() => route.path.match(/^\/app\/([^/]+)/)?.[1] ?? encodeURIComponent(packageId.value))
const days = ref<PeriodDayOption>(7)
const versionGroup = ref<VersionGroupOption>('version')
const { stats, statsLoading, fetchStats } = useNativeObserveStats<NativeObserveStatsResponse>(
packageId,
() => ({ days: days.value }),
() => ({ days: days.value, version_group: versionGroup.value }),
'native observe stats',
)
const hasData = computed(() => (stats.value?.overview.total_events ?? 0) > 0)
const topActions = computed(() => stats.value?.actionBreakdown.slice(0, 10) ?? [])
const topVersions = computed(() => stats.value?.versions.slice(0, 8) ?? [])
const topVersions = computed(() => stats.value?.versions.slice(0, versionGroup.value === 'version' ? 8 : 24) ?? [])
const showPlatformColumn = computed(() => versionGroup.value !== 'version')
const showChannelColumn = computed(() => versionGroup.value === 'version_platform_channel')
const versionHealthHelp = computed(() => {
if (versionGroup.value === 'version_platform_channel')
return t('native-observe-version-health-help-platform-channel')
if (versionGroup.value === 'version_platform')
return t('native-observe-version-health-help-platform')
return t('native-observe-version-health-help')
})
const versionTableMinWidth = computed(() => {
if (showChannelColumn.value)
return 'min-w-[980px]'
if (showPlatformColumn.value)
return 'min-w-[860px]'
return 'min-w-[760px]'
})

const selectedPeriodLabel = computed(() => {
if (days.value === 1)
Expand Down Expand Up @@ -262,6 +284,16 @@ function selectPeriod(option: PeriodDayOption) {
days.value = option
}

function selectVersionGroup(option: VersionGroupOption) {
if (versionGroup.value === option)
return
versionGroup.value = option
}

function versionRowKey(version: NativeObserveStatsResponse['versions'][number]) {
return [version.version_name, version.platform ?? '', version.channel_name ?? ''].join('\0')
}

function openLogs(action: string) {
if (!stats.value)
return
Expand All @@ -281,7 +313,7 @@ watch(packageId, () => {
displayStore.defaultBack = '/apps'
}, { immediate: true })

watch([packageId, days], async () => {
watch([packageId, days, versionGroup], async () => {
await fetchStats()
}, { immediate: true })
</script>
Expand Down Expand Up @@ -428,24 +460,33 @@ watch([packageId, days], async () => {

<div class="grid grid-cols-1 gap-4 2xl:grid-cols-[minmax(0,1fr)_minmax(320px,0.65fr)]">
<div class="p-4 bg-white border rounded-lg shadow-sm dark:bg-slate-800 border-slate-200 dark:border-slate-700">
<div class="flex items-center justify-between gap-3 mb-4">
<div>
<h2 class="text-base font-semibold text-slate-950 dark:text-white">
{{ t('native-observe-version-health') }}
</h2>
<div class="flex flex-col gap-3 mb-4 sm:flex-row sm:items-start sm:justify-between">
<div class="min-w-0">
<div class="flex items-center gap-2">
<h2 class="text-base font-semibold text-slate-950 dark:text-white">
{{ t('native-observe-version-health') }}
</h2>
<IconRocket class="w-5 h-5 text-sky-500 shrink-0" />
</div>
<p class="mt-1 text-sm text-slate-500 dark:text-slate-400">
{{ t('native-observe-version-health-help') }}
{{ versionHealthHelp }}
</p>
</div>
<IconRocket class="w-5 h-5 text-sky-500" />
<VersionGroupSelector :model-value="versionGroup" @update:model-value="selectVersionGroup" />
</div>
<div class="overflow-x-auto">
<table class="d-table d-table-sm w-full min-w-[760px]">
<table class="d-table d-table-sm w-full" :class="versionTableMinWidth">
<thead>
<tr>
<th class="whitespace-nowrap">
{{ t('native-observe-version') }}
</th>
<th v-if="showPlatformColumn" class="whitespace-nowrap">
{{ t('native-observe-platform') }}
</th>
<th v-if="showChannelColumn" class="whitespace-nowrap">
{{ t('native-observe-channel') }}
</th>
<th class="whitespace-nowrap">
{{ t('events') }}
</th>
Expand All @@ -464,10 +505,16 @@ watch([packageId, days], async () => {
</tr>
</thead>
<tbody>
<tr v-for="version in topVersions" :key="version.version_name">
<tr v-for="version in topVersions" :key="versionRowKey(version)">
<td class="font-medium text-slate-900 dark:text-slate-100">
{{ version.version_name }}
</td>
<td v-if="showPlatformColumn">
{{ version.platform || '-' }}
</td>
<td v-if="showChannelColumn">
{{ version.channel_name || '-' }}
</td>
<td>{{ formatCount(version.events) }}</td>
<td>{{ formatCount(version.devices) }}</td>
<td>{{ formatPercent(version.issue_free_rate) }}</td>
Expand Down
Loading
Loading