From 31469799f25e1f9587192b91f25f8af32846ab71 Mon Sep 17 00:00:00 2001 From: Kadi Kraman Date: Thu, 21 May 2026 22:08:48 +0100 Subject: [PATCH 1/9] Rename /metrics -> /metrics-summary --- ...metrics.test.ts => metric-summary.test.ts} | 46 +++++++++---------- .../observe/{metrics.ts => metric-summary.ts} | 11 +++-- 2 files changed, 29 insertions(+), 28 deletions(-) rename packages/eas-cli/src/commands/observe/__tests__/{metrics.test.ts => metric-summary.test.ts} (79%) rename packages/eas-cli/src/commands/observe/{metrics.ts => metric-summary.ts} (90%) diff --git a/packages/eas-cli/src/commands/observe/__tests__/metrics.test.ts b/packages/eas-cli/src/commands/observe/__tests__/metric-summary.test.ts similarity index 79% rename from packages/eas-cli/src/commands/observe/__tests__/metrics.test.ts rename to packages/eas-cli/src/commands/observe/__tests__/metric-summary.test.ts index 738539dff8..090d22481e 100644 --- a/packages/eas-cli/src/commands/observe/__tests__/metrics.test.ts +++ b/packages/eas-cli/src/commands/observe/__tests__/metric-summary.test.ts @@ -4,7 +4,7 @@ import { AppPlatform } from '../../../graphql/generated'; import { fetchObserveMetricsAsync, validateDateFlag } from '../../../observe/fetchMetrics'; import { buildObserveMetricsJson, buildObserveMetricsTable } from '../../../observe/formatMetrics'; import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json'; -import ObserveMetrics from '../metrics'; +import ObserveMetricSummary from '../metric-summary'; jest.mock('../../../observe/fetchMetrics', () => { const actual = jest.requireActual('../../../observe/fetchMetrics'); @@ -21,20 +21,20 @@ jest.mock('../../../observe/formatMetrics', () => ({ jest.mock('../../../log'); jest.mock('../../../utils/json'); -const mockFetchObserveMetricsAsync = jest.mocked(fetchObserveMetricsAsync); -const mockBuildObserveMetricsTable = jest.mocked(buildObserveMetricsTable); -const mockBuildObserveMetricsJson = jest.mocked(buildObserveMetricsJson); +const mockFetchObserveMetricSummaryAsync = jest.mocked(fetchObserveMetricsAsync); +const mockBuildObserveMetricSummaryTable = jest.mocked(buildObserveMetricsTable); +const mockBuildObserveMetricSummaryJson = jest.mocked(buildObserveMetricsJson); const mockEnableJsonOutput = jest.mocked(enableJsonOutput); const mockPrintJsonOnlyOutput = jest.mocked(printJsonOnlyOutput); -describe(ObserveMetrics, () => { +describe(ObserveMetricSummary, () => { const graphqlClient = {} as any as ExpoGraphqlClient; const mockConfig = getMockOclifConfig(); const projectId = 'test-project-id'; beforeEach(() => { jest.clearAllMocks(); - mockFetchObserveMetricsAsync.mockResolvedValue({ + mockFetchObserveMetricSummaryAsync.mockResolvedValue({ metricsMap: new Map(), buildNumbersMap: new Map(), updateIdsMap: new Map(), @@ -42,8 +42,8 @@ describe(ObserveMetrics, () => { }); }); - function createCommand(argv: string[]): ObserveMetrics { - const command = new ObserveMetrics(argv, mockConfig); + function createCommand(argv: string[]): ObserveMetricSummary { + const command = new ObserveMetricSummary(argv, mockConfig); // @ts-expect-error getContextAsync is a protected method jest.spyOn(command, 'getContextAsync').mockReturnValue({ projectId, @@ -59,8 +59,8 @@ describe(ObserveMetrics, () => { const command = createCommand([]); await command.runAsync(); - expect(mockFetchObserveMetricsAsync).toHaveBeenCalledTimes(1); - const platforms = mockFetchObserveMetricsAsync.mock.calls[0][3]; + expect(mockFetchObserveMetricSummaryAsync).toHaveBeenCalledTimes(1); + const platforms = mockFetchObserveMetricSummaryAsync.mock.calls[0][3]; expect(platforms).toEqual([AppPlatform.Android, AppPlatform.Ios]); jest.useRealTimers(); @@ -70,7 +70,7 @@ describe(ObserveMetrics, () => { const command = createCommand(['--platform', 'android']); await command.runAsync(); - const platforms = mockFetchObserveMetricsAsync.mock.calls[0][3]; + const platforms = mockFetchObserveMetricSummaryAsync.mock.calls[0][3]; expect(platforms).toEqual([AppPlatform.Android]); }); @@ -78,7 +78,7 @@ describe(ObserveMetrics, () => { const command = createCommand(['--platform', 'ios']); await command.runAsync(); - const platforms = mockFetchObserveMetricsAsync.mock.calls[0][3]; + const platforms = mockFetchObserveMetricSummaryAsync.mock.calls[0][3]; expect(platforms).toEqual([AppPlatform.Ios]); }); @@ -86,7 +86,7 @@ describe(ObserveMetrics, () => { const command = createCommand(['--metric', 'tti', '--metric', 'cold_launch']); await command.runAsync(); - const metricNames = mockFetchObserveMetricsAsync.mock.calls[0][2]; + const metricNames = mockFetchObserveMetricSummaryAsync.mock.calls[0][2]; expect(metricNames).toEqual(['expo.app_startup.tti', 'expo.app_startup.cold_launch_time']); }); @@ -97,8 +97,8 @@ describe(ObserveMetrics, () => { const command = createCommand([]); await command.runAsync(); - const startTime = mockFetchObserveMetricsAsync.mock.calls[0][4]; - const endTime = mockFetchObserveMetricsAsync.mock.calls[0][5]; + const startTime = mockFetchObserveMetricSummaryAsync.mock.calls[0][4]; + const endTime = mockFetchObserveMetricSummaryAsync.mock.calls[0][5]; expect(endTime).toBe('2025-06-15T12:00:00.000Z'); expect(startTime).toBe('2025-04-16T12:00:00.000Z'); @@ -114,8 +114,8 @@ describe(ObserveMetrics, () => { ]); await command.runAsync(); - const startTime = mockFetchObserveMetricsAsync.mock.calls[0][4]; - const endTime = mockFetchObserveMetricsAsync.mock.calls[0][5]; + const startTime = mockFetchObserveMetricSummaryAsync.mock.calls[0][4]; + const endTime = mockFetchObserveMetricSummaryAsync.mock.calls[0][5]; expect(startTime).toBe('2025-01-01T00:00:00.000Z'); expect(endTime).toBe('2025-02-01T00:00:00.000Z'); }); @@ -124,7 +124,7 @@ describe(ObserveMetrics, () => { const command = createCommand(['--stat', 'p90', '--stat', 'eventCount']); await command.runAsync(); - expect(mockBuildObserveMetricsTable).toHaveBeenCalledWith( + expect(mockBuildObserveMetricSummaryTable).toHaveBeenCalledWith( expect.any(Map), expect.any(Array), ['p90', 'eventCount'], @@ -136,7 +136,7 @@ describe(ObserveMetrics, () => { const command = createCommand(['--stat', 'median', '--stat', 'median']); await command.runAsync(); - expect(mockBuildObserveMetricsTable).toHaveBeenCalledWith( + expect(mockBuildObserveMetricSummaryTable).toHaveBeenCalledWith( expect.any(Map), expect.any(Array), ['median'], @@ -151,8 +151,8 @@ describe(ObserveMetrics, () => { const command = createCommand(['--days', '7']); await command.runAsync(); - const startTime = mockFetchObserveMetricsAsync.mock.calls[0][4]; - const endTime = mockFetchObserveMetricsAsync.mock.calls[0][5]; + const startTime = mockFetchObserveMetricSummaryAsync.mock.calls[0][4]; + const endTime = mockFetchObserveMetricSummaryAsync.mock.calls[0][5]; expect(endTime).toBe('2025-06-15T12:00:00.000Z'); expect(startTime).toBe('2025-06-08T12:00:00.000Z'); @@ -175,7 +175,7 @@ describe(ObserveMetrics, () => { const command = createCommand([]); await command.runAsync(); - expect(mockBuildObserveMetricsTable).toHaveBeenCalledWith( + expect(mockBuildObserveMetricSummaryTable).toHaveBeenCalledWith( expect.any(Map), expect.any(Array), ['median', 'eventCount'], @@ -195,7 +195,7 @@ describe(ObserveMetrics, () => { await command.runAsync(); expect(mockEnableJsonOutput).toHaveBeenCalled(); - expect(mockBuildObserveMetricsJson).toHaveBeenCalledWith( + expect(mockBuildObserveMetricSummaryJson).toHaveBeenCalledWith( expect.any(Map), expect.any(Array), ['min', 'average'], diff --git a/packages/eas-cli/src/commands/observe/metrics.ts b/packages/eas-cli/src/commands/observe/metric-summary.ts similarity index 90% rename from packages/eas-cli/src/commands/observe/metrics.ts rename to packages/eas-cli/src/commands/observe/metric-summary.ts index 915f88f71a..d314118f52 100644 --- a/packages/eas-cli/src/commands/observe/metrics.ts +++ b/packages/eas-cli/src/commands/observe/metric-summary.ts @@ -41,9 +41,10 @@ const DEFAULT_STATS_JSON: StatisticKey[] = [ 'eventCount', ]; -export default class ObserveMetrics extends EasCommand { +export default class ObserveMetricSummary extends EasCommand { static override hidden = true; - static override description = 'display app performance metrics grouped by app version'; + static override description = + 'display aggregated performance metric statistics grouped by app version'; static override flags = { ...ObservePlatformFlag, @@ -72,12 +73,12 @@ export default class ObserveMetrics extends EasCommand { }; async runAsync(): Promise { - const { flags } = await this.parse(ObserveMetrics); + const { flags } = await this.parse(ObserveMetricSummary); const { projectId, graphqlClient } = await resolveObserveCommandContextAsync({ command: this, - commandClass: ObserveMetrics, - loggedInOnlyContextDefinition: ObserveMetrics.loggedInOnlyContextDefinition, + commandClass: ObserveMetricSummary, + loggedInOnlyContextDefinition: ObserveMetricSummary.loggedInOnlyContextDefinition, projectIdOverride: flags['project-id'], nonInteractive: flags['non-interactive'], }); From 66bdff2b7dd8a483575eadaba746dbeef61f80ee Mon Sep 17 00:00:00 2001 From: Kadi Kraman Date: Thu, 21 May 2026 22:11:54 +0100 Subject: [PATCH 2/9] Rename events -> metrics --- .../__tests__/{events.test.ts => metrics.test.ts} | 8 ++++---- .../src/commands/observe/{events.ts => metrics.ts} | 11 ++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) rename packages/eas-cli/src/commands/observe/__tests__/{events.test.ts => metrics.test.ts} (98%) rename packages/eas-cli/src/commands/observe/{events.ts => metrics.ts} (92%) diff --git a/packages/eas-cli/src/commands/observe/__tests__/events.test.ts b/packages/eas-cli/src/commands/observe/__tests__/metrics.test.ts similarity index 98% rename from packages/eas-cli/src/commands/observe/__tests__/events.test.ts rename to packages/eas-cli/src/commands/observe/__tests__/metrics.test.ts index 8b4c17d951..e5a25a70c2 100644 --- a/packages/eas-cli/src/commands/observe/__tests__/events.test.ts +++ b/packages/eas-cli/src/commands/observe/__tests__/metrics.test.ts @@ -8,7 +8,7 @@ import { import { fetchObserveEventsAsync, resolveOrderBy } from '../../../observe/fetchEvents'; import { buildObserveEventsJson } from '../../../observe/formatEvents'; import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json'; -import ObserveEvents from '../events'; +import ObserveMetrics from '../metrics'; jest.mock('../../../observe/fetchEvents', () => { const actual = jest.requireActual('../../../observe/fetchEvents'); @@ -29,7 +29,7 @@ const mockBuildObserveEventsJson = jest.mocked(buildObserveEventsJson); const mockEnableJsonOutput = jest.mocked(enableJsonOutput); const mockPrintJsonOnlyOutput = jest.mocked(printJsonOnlyOutput); -describe(ObserveEvents, () => { +describe(ObserveMetrics, () => { const graphqlClient = {} as any as ExpoGraphqlClient; const mockConfig = getMockOclifConfig(); const projectId = 'test-project-id'; @@ -42,8 +42,8 @@ describe(ObserveEvents, () => { }); }); - function createCommand(argv: string[]): ObserveEvents { - const command = new ObserveEvents(argv, mockConfig); + function createCommand(argv: string[]): ObserveMetrics { + const command = new ObserveMetrics(argv, mockConfig); // @ts-expect-error jest.spyOn(command, 'getContextAsync').mockReturnValue({ projectId, diff --git a/packages/eas-cli/src/commands/observe/events.ts b/packages/eas-cli/src/commands/observe/metrics.ts similarity index 92% rename from packages/eas-cli/src/commands/observe/events.ts rename to packages/eas-cli/src/commands/observe/metrics.ts index 2e9b59b3fa..43bd150e6b 100644 --- a/packages/eas-cli/src/commands/observe/events.ts +++ b/packages/eas-cli/src/commands/observe/metrics.ts @@ -29,9 +29,10 @@ import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json'; const DEFAULT_EVENTS_LIMIT = 10; -export default class ObserveEvents extends EasCommand { +export default class ObserveMetrics extends EasCommand { static override hidden = true; - static override description = 'display individual app performance events ordered by metric value'; + static override description = + 'display individual performance metric samples ordered by value'; static override args = { metric: Args.string({ @@ -71,12 +72,12 @@ export default class ObserveEvents extends EasCommand { }; async runAsync(): Promise { - const { flags, args } = await this.parse(ObserveEvents); + const { flags, args } = await this.parse(ObserveMetrics); const { projectId, graphqlClient } = await resolveObserveCommandContextAsync({ command: this, - commandClass: ObserveEvents, - loggedInOnlyContextDefinition: ObserveEvents.loggedInOnlyContextDefinition, + commandClass: ObserveMetrics, + loggedInOnlyContextDefinition: ObserveMetrics.loggedInOnlyContextDefinition, projectIdOverride: flags['project-id'], nonInteractive: flags['non-interactive'], }); From 1b96d341c0bb6c658b1bc67e70a34a558fdfbfc1 Mon Sep 17 00:00:00 2001 From: Kadi Kraman Date: Thu, 21 May 2026 22:21:10 +0100 Subject: [PATCH 3/9] Rename logs -> events --- .../__tests__/{logs.test.ts => events.test.ts} | 8 ++++---- .../src/commands/observe/{logs.ts => events.ts} | 12 ++++++------ .../src/observe/__tests__/formatCustomEvents.test.ts | 10 +++++----- packages/eas-cli/src/observe/formatCustomEvents.ts | 10 +++++----- 4 files changed, 20 insertions(+), 20 deletions(-) rename packages/eas-cli/src/commands/observe/__tests__/{logs.test.ts => events.test.ts} (98%) rename packages/eas-cli/src/commands/observe/{logs.ts => events.ts} (91%) diff --git a/packages/eas-cli/src/commands/observe/__tests__/logs.test.ts b/packages/eas-cli/src/commands/observe/__tests__/events.test.ts similarity index 98% rename from packages/eas-cli/src/commands/observe/__tests__/logs.test.ts rename to packages/eas-cli/src/commands/observe/__tests__/events.test.ts index 8b98568d8f..34a4a180a2 100644 --- a/packages/eas-cli/src/commands/observe/__tests__/logs.test.ts +++ b/packages/eas-cli/src/commands/observe/__tests__/events.test.ts @@ -10,7 +10,7 @@ import { buildObserveCustomEventsJson, } from '../../../observe/formatCustomEvents'; import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json'; -import ObserveLogs from '../logs'; +import ObserveEvents from '../events'; jest.mock('../../../observe/fetchCustomEvents'); jest.mock('../../../observe/formatCustomEvents', () => ({ @@ -49,7 +49,7 @@ const mockCustomEventNamesAsync = jest.mocked(ObserveQuery.customEventNamesAsync const mockEnableJsonOutput = jest.mocked(enableJsonOutput); const mockPrintJsonOnlyOutput = jest.mocked(printJsonOnlyOutput); -describe(ObserveLogs, () => { +describe(ObserveEvents, () => { const graphqlClient = {} as any as ExpoGraphqlClient; const mockConfig = getMockOclifConfig(); const projectId = 'test-project-id'; @@ -63,8 +63,8 @@ describe(ObserveLogs, () => { mockCustomEventNamesAsync.mockResolvedValue({ names: [], isTruncated: false }); }); - function createCommand(argv: string[]): ObserveLogs { - const command = new ObserveLogs(argv, mockConfig); + function createCommand(argv: string[]): ObserveEvents { + const command = new ObserveEvents(argv, mockConfig); // @ts-expect-error jest.spyOn(command, 'getContextAsync').mockReturnValue({ projectId, diff --git a/packages/eas-cli/src/commands/observe/logs.ts b/packages/eas-cli/src/commands/observe/events.ts similarity index 91% rename from packages/eas-cli/src/commands/observe/logs.ts rename to packages/eas-cli/src/commands/observe/events.ts index 89cec95e3f..55c945d07d 100644 --- a/packages/eas-cli/src/commands/observe/logs.ts +++ b/packages/eas-cli/src/commands/observe/events.ts @@ -29,14 +29,14 @@ import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json'; const DEFAULT_EVENTS_LIMIT = 10; -export default class ObserveLogs extends EasCommand { +export default class ObserveEvents extends EasCommand { static override hidden = true; static override description = - 'display individual custom events (logs) emitted by the app, filtered by the event name in the argument. With no arguments, a list of the available event names and associated event counts is returned.'; + 'display individual events emitted by the app via `logEvent`, filtered by the event name in the argument. With no arguments, a list of the available event names and associated event counts is returned.'; static override args = { eventName: Args.string({ - description: 'Custom event name to filter by', + description: 'Event name to filter by', required: false, }), }; @@ -73,7 +73,7 @@ export default class ObserveLogs extends EasCommand { }; async runAsync(): Promise { - const { flags, args } = await this.parse(ObserveLogs); + const { flags, args } = await this.parse(ObserveEvents); if (args.eventName && flags['all-events']) { throw new Error( @@ -83,8 +83,8 @@ export default class ObserveLogs extends EasCommand { const { projectId, graphqlClient } = await resolveObserveCommandContextAsync({ command: this, - commandClass: ObserveLogs, - loggedInOnlyContextDefinition: ObserveLogs.loggedInOnlyContextDefinition, + commandClass: ObserveEvents, + loggedInOnlyContextDefinition: ObserveEvents.loggedInOnlyContextDefinition, projectIdOverride: flags['project-id'], nonInteractive: flags['non-interactive'], }); diff --git a/packages/eas-cli/src/observe/__tests__/formatCustomEvents.test.ts b/packages/eas-cli/src/observe/__tests__/formatCustomEvents.test.ts index 3f3e87428d..62c72db24e 100644 --- a/packages/eas-cli/src/observe/__tests__/formatCustomEvents.test.ts +++ b/packages/eas-cli/src/observe/__tests__/formatCustomEvents.test.ts @@ -69,7 +69,7 @@ Jan 14, 2025, 08:15:00.000 AM checkout 1.1.0 (38) Android 14 Pixel 8 PL it('returns yellow warning for empty events', () => { const output = buildObserveCustomEventsTable([], noNextPage); - expect(output).toContain('No custom events found.'); + expect(output).toContain('No events found.'); }); it('shows event name in summary header when an event name option is provided', () => { @@ -82,13 +82,13 @@ Jan 14, 2025, 08:15:00.000 AM checkout 1.1.0 (38) Android 14 Pixel 8 PL expect(output).toContain('login events for the last 30 days'); }); - it('shows the generic "Custom events" subject when no event name option is provided', () => { + it('shows the generic "Events" subject when no event name option is provided', () => { const events = [makeCustomEvent()]; const output = buildObserveCustomEventsTable(events, noNextPage, { daysBack: 30, }); - expect(output).toContain('Custom events for the last 30 days'); + expect(output).toContain('Events for the last 30 days'); }); it('shows date range in summary header when start/end provided', () => { @@ -294,7 +294,7 @@ describe(buildObserveCustomEventsJson, () => { describe(buildObserveCustomEventNamesTable, () => { it('returns yellow warning when names list is empty', () => { const output = buildObserveCustomEventNamesTable([]); - expect(output).toContain('No custom event names found.'); + expect(output).toContain('No event names found.'); }); it('shows event names with counts', () => { @@ -336,7 +336,7 @@ describe(buildObserveCustomEventsEmptyWithSuggestionsTable, () => { const output = buildObserveCustomEventsEmptyWithSuggestionsTable('login', []); expect(output).toContain('No events found matching "login"'); - expect(output).toContain('No custom event names found in this time range.'); + expect(output).toContain('No event names found in this time range.'); }); it('appends a truncation notice when isTruncated is set', () => { diff --git a/packages/eas-cli/src/observe/formatCustomEvents.ts b/packages/eas-cli/src/observe/formatCustomEvents.ts index 646bf5f587..e98558b960 100644 --- a/packages/eas-cli/src/observe/formatCustomEvents.ts +++ b/packages/eas-cli/src/observe/formatCustomEvents.ts @@ -54,7 +54,7 @@ export function buildObserveCustomEventsTable( options?: BuildCustomEventsTableOptions ): string { if (events.length === 0) { - return chalk.yellow('No custom events found.'); + return chalk.yellow('No events found.'); } const showEventName = !options?.eventName; @@ -88,7 +88,7 @@ export function buildObserveCustomEventsTable( options.totalEventCount != null ? ` — ${options.totalEventCount.toLocaleString()} total events` : ''; - const subject = options.eventName ? `${options.eventName} events` : 'Custom events'; + const subject = options.eventName ? `${options.eventName} events` : 'Events'; lines.push(chalk.bold(`${subject} ${timeDesc}${totalDesc}`.trim()), ''); } @@ -156,7 +156,7 @@ export function buildObserveCustomEventsEmptyWithSuggestionsTable( lines.push(chalk.yellow(`No events found matching "${eventName}" ${timeDesc}.`.trim())); if (names.length === 0) { - lines.push('', chalk.yellow('No custom event names found in this time range.')); + lines.push('', chalk.yellow('No event names found in this time range.')); return lines.join('\n'); } @@ -203,7 +203,7 @@ export function buildObserveCustomEventNamesTable( options?: BuildCustomEventNamesTableOptions ): string { if (names.length === 0) { - return chalk.yellow('No custom event names found.'); + return chalk.yellow('No event names found.'); } const headers = ['Event Name', 'Count']; @@ -213,7 +213,7 @@ export function buildObserveCustomEventNamesTable( if (options) { const timeDesc = buildTimeRangeDescription(options); - const subject = 'Custom event names'; + const subject = 'Event names'; lines.push(chalk.bold(`${subject} ${timeDesc}`.trim()), ''); } From 16282c6383d99e8f98081021cfcf1f1073edff88 Mon Sep 17 00:00:00 2001 From: Kadi Kraman Date: Thu, 21 May 2026 22:26:34 +0100 Subject: [PATCH 4/9] Add changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 372dc7e3e1..5146997912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This is the log of notable changes to EAS CLI and related packages. ### 🛠 Breaking changes +- [eas-cli] Rename observe commands: `observe:logs` → `observe:events` (events emitted via `logEvent`), previous `observe:events` → `observe:metrics` (individual performance metric samples), previous `observe:metrics` → `observe:metric-summary` (aggregated stats by app version). ([#XXXX](https://github.com/expo/eas-cli/pull/XXXX) by [@kadikraman](https://github.com/kadikraman)) + ### 🎉 New features ### 🐛 Bug fixes From 281ea19908fe6f9a756ba71cc7d01da5d3911838 Mon Sep 17 00:00:00 2001 From: Kadi Kraman Date: Thu, 21 May 2026 22:29:14 +0100 Subject: [PATCH 5/9] Add update download to metric names --- packages/eas-cli/src/observe/__tests__/metricNames.test.ts | 5 +++++ packages/eas-cli/src/observe/metricNames.ts | 2 ++ 2 files changed, 7 insertions(+) diff --git a/packages/eas-cli/src/observe/__tests__/metricNames.test.ts b/packages/eas-cli/src/observe/__tests__/metricNames.test.ts index f0702a638d..1b3905b253 100644 --- a/packages/eas-cli/src/observe/__tests__/metricNames.test.ts +++ b/packages/eas-cli/src/observe/__tests__/metricNames.test.ts @@ -25,6 +25,10 @@ describe(resolveMetricName, () => { expect(resolveMetricName('bundle_load')).toBe('expo.app_startup.bundle_load_time'); }); + it('resolves short alias "update_download" to full metric name', () => { + expect(resolveMetricName('update_download')).toBe('expo.updates.download_time'); + }); + it('passes through full metric names unchanged', () => { expect(resolveMetricName('expo.app_startup.tti')).toBe('expo.app_startup.tti'); expect(resolveMetricName('expo.app_startup.cold_launch_time')).toBe( @@ -73,6 +77,7 @@ describe(getMetricDisplayName, () => { expect(getMetricDisplayName('expo.app_startup.tti')).toBe('Startup TTI'); expect(getMetricDisplayName('expo.app_startup.ttr')).toBe('Startup TTR'); expect(getMetricDisplayName('expo.app_startup.bundle_load_time')).toBe('Bundle Load'); + expect(getMetricDisplayName('expo.updates.download_time')).toBe('Update Download'); }); it('returns short display name for known navigation metrics', () => { diff --git a/packages/eas-cli/src/observe/metricNames.ts b/packages/eas-cli/src/observe/metricNames.ts index de11e8e494..7dc8b08dbd 100644 --- a/packages/eas-cli/src/observe/metricNames.ts +++ b/packages/eas-cli/src/observe/metricNames.ts @@ -6,6 +6,7 @@ export const METRIC_ALIASES: Record = { cold_launch: 'expo.app_startup.cold_launch_time', warm_launch: 'expo.app_startup.warm_launch_time', bundle_load: 'expo.app_startup.bundle_load_time', + update_download: 'expo.updates.download_time', }; export const NAVIGATION_METRIC_ALIASES: Record = { @@ -23,6 +24,7 @@ export const METRIC_SHORT_NAMES: Record = { 'expo.app_startup.tti': 'Startup TTI', 'expo.app_startup.ttr': 'Startup TTR', 'expo.app_startup.bundle_load_time': 'Bundle Load', + 'expo.updates.download_time': 'Update Download', 'expo.navigation.cold_ttr': 'Nav Cold TTR', 'expo.navigation.warm_ttr': 'Nav Warm TTR', 'expo.navigation.tti': 'Nav TTI', From d9097eaf6230f94fe81dbc57b4c89074bb826b82 Mon Sep 17 00:00:00 2001 From: Kadi Kraman Date: Thu, 21 May 2026 22:33:23 +0100 Subject: [PATCH 6/9] Fix warnings --- .../observe/__tests__/metric-summary.test.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/eas-cli/src/commands/observe/__tests__/metric-summary.test.ts b/packages/eas-cli/src/commands/observe/__tests__/metric-summary.test.ts index 090d22481e..ffae9df2f6 100644 --- a/packages/eas-cli/src/commands/observe/__tests__/metric-summary.test.ts +++ b/packages/eas-cli/src/commands/observe/__tests__/metric-summary.test.ts @@ -209,16 +209,20 @@ describe(ObserveMetricSummary, () => { describe(validateDateFlag, () => { it('throws on invalid --start date', () => { - expect(() => validateDateFlag('not-a-date', '--start')).toThrow( - 'Invalid --start date: "not-a-date"' - ); + expect(() => { + validateDateFlag('not-a-date', '--start'); + }).toThrow('Invalid --start date: "not-a-date"'); }); it('throws on invalid --end date', () => { - expect(() => validateDateFlag('also-bad', '--end')).toThrow('Invalid --end date: "also-bad"'); + expect(() => { + validateDateFlag('also-bad', '--end'); + }).toThrow('Invalid --end date: "also-bad"'); }); it('accepts valid ISO date in --start', () => { - expect(() => validateDateFlag('2025-01-01', '--start')).not.toThrow(); + expect(() => { + validateDateFlag('2025-01-01', '--start'); + }).not.toThrow(); }); }); From 7939788ffda7a2d5603f69662a342e6db1cdbb21 Mon Sep 17 00:00:00 2001 From: Kadi Kraman Date: Thu, 21 May 2026 22:38:49 +0100 Subject: [PATCH 7/9] Add changelog PR number --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5146997912..09444b8fbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ This is the log of notable changes to EAS CLI and related packages. ### 🛠 Breaking changes -- [eas-cli] Rename observe commands: `observe:logs` → `observe:events` (events emitted via `logEvent`), previous `observe:events` → `observe:metrics` (individual performance metric samples), previous `observe:metrics` → `observe:metric-summary` (aggregated stats by app version). ([#XXXX](https://github.com/expo/eas-cli/pull/XXXX) by [@kadikraman](https://github.com/kadikraman)) +- [eas-cli] Rename observe commands: `observe:logs` → `observe:events` (events emitted via `logEvent`), previous `observe:events` → `observe:metrics` (individual performance metric samples), previous `observe:metrics` → `observe:metric-summary` (aggregated stats by app version). ([#3778](https://github.com/expo/eas-cli/pull/3778) by [@kadikraman](https://github.com/kadikraman)) ### 🎉 New features From d2fe17cd69446e6d340857e630231add97ccff17 Mon Sep 17 00:00:00 2001 From: Kadi Kraman Date: Thu, 21 May 2026 22:48:33 +0100 Subject: [PATCH 8/9] Format --- packages/eas-cli/src/commands/observe/metrics.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/eas-cli/src/commands/observe/metrics.ts b/packages/eas-cli/src/commands/observe/metrics.ts index 43bd150e6b..3e06be60b1 100644 --- a/packages/eas-cli/src/commands/observe/metrics.ts +++ b/packages/eas-cli/src/commands/observe/metrics.ts @@ -31,8 +31,7 @@ const DEFAULT_EVENTS_LIMIT = 10; export default class ObserveMetrics extends EasCommand { static override hidden = true; - static override description = - 'display individual performance metric samples ordered by value'; + static override description = 'display individual performance metric samples ordered by value'; static override args = { metric: Args.string({ From 119d83d1cc348504ee5238b3dc8122eb00195fa0 Mon Sep 17 00:00:00 2001 From: Kadi Kraman Date: Fri, 22 May 2026 11:08:45 +0100 Subject: [PATCH 9/9] Rename metric-summary -> metrics-summary --- CHANGELOG.md | 2 +- ...ummary.test.ts => metrics-summary.test.ts} | 46 +++++++++---------- .../{metric-summary.ts => metrics-summary.ts} | 8 ++-- 3 files changed, 28 insertions(+), 28 deletions(-) rename packages/eas-cli/src/commands/observe/__tests__/{metric-summary.test.ts => metrics-summary.test.ts} (79%) rename packages/eas-cli/src/commands/observe/{metric-summary.ts => metrics-summary.ts} (93%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09444b8fbb..a80c257b8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ This is the log of notable changes to EAS CLI and related packages. ### 🛠 Breaking changes -- [eas-cli] Rename observe commands: `observe:logs` → `observe:events` (events emitted via `logEvent`), previous `observe:events` → `observe:metrics` (individual performance metric samples), previous `observe:metrics` → `observe:metric-summary` (aggregated stats by app version). ([#3778](https://github.com/expo/eas-cli/pull/3778) by [@kadikraman](https://github.com/kadikraman)) +- [eas-cli] Rename observe commands: `observe:logs` → `observe:events` (events emitted via `logEvent`), previous `observe:events` → `observe:metrics` (individual performance metric samples), previous `observe:metrics` → `observe:metrics-summary` (aggregated stats by app version). ([#3778](https://github.com/expo/eas-cli/pull/3778) by [@kadikraman](https://github.com/kadikraman)) ### 🎉 New features diff --git a/packages/eas-cli/src/commands/observe/__tests__/metric-summary.test.ts b/packages/eas-cli/src/commands/observe/__tests__/metrics-summary.test.ts similarity index 79% rename from packages/eas-cli/src/commands/observe/__tests__/metric-summary.test.ts rename to packages/eas-cli/src/commands/observe/__tests__/metrics-summary.test.ts index ffae9df2f6..c8234afbbd 100644 --- a/packages/eas-cli/src/commands/observe/__tests__/metric-summary.test.ts +++ b/packages/eas-cli/src/commands/observe/__tests__/metrics-summary.test.ts @@ -4,7 +4,7 @@ import { AppPlatform } from '../../../graphql/generated'; import { fetchObserveMetricsAsync, validateDateFlag } from '../../../observe/fetchMetrics'; import { buildObserveMetricsJson, buildObserveMetricsTable } from '../../../observe/formatMetrics'; import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json'; -import ObserveMetricSummary from '../metric-summary'; +import ObserveMetricsSummary from '../metrics-summary'; jest.mock('../../../observe/fetchMetrics', () => { const actual = jest.requireActual('../../../observe/fetchMetrics'); @@ -21,20 +21,20 @@ jest.mock('../../../observe/formatMetrics', () => ({ jest.mock('../../../log'); jest.mock('../../../utils/json'); -const mockFetchObserveMetricSummaryAsync = jest.mocked(fetchObserveMetricsAsync); -const mockBuildObserveMetricSummaryTable = jest.mocked(buildObserveMetricsTable); -const mockBuildObserveMetricSummaryJson = jest.mocked(buildObserveMetricsJson); +const mockFetchObserveMetricsSummaryAsync = jest.mocked(fetchObserveMetricsAsync); +const mockBuildObserveMetricsSummaryTable = jest.mocked(buildObserveMetricsTable); +const mockBuildObserveMetricsSummaryJson = jest.mocked(buildObserveMetricsJson); const mockEnableJsonOutput = jest.mocked(enableJsonOutput); const mockPrintJsonOnlyOutput = jest.mocked(printJsonOnlyOutput); -describe(ObserveMetricSummary, () => { +describe(ObserveMetricsSummary, () => { const graphqlClient = {} as any as ExpoGraphqlClient; const mockConfig = getMockOclifConfig(); const projectId = 'test-project-id'; beforeEach(() => { jest.clearAllMocks(); - mockFetchObserveMetricSummaryAsync.mockResolvedValue({ + mockFetchObserveMetricsSummaryAsync.mockResolvedValue({ metricsMap: new Map(), buildNumbersMap: new Map(), updateIdsMap: new Map(), @@ -42,8 +42,8 @@ describe(ObserveMetricSummary, () => { }); }); - function createCommand(argv: string[]): ObserveMetricSummary { - const command = new ObserveMetricSummary(argv, mockConfig); + function createCommand(argv: string[]): ObserveMetricsSummary { + const command = new ObserveMetricsSummary(argv, mockConfig); // @ts-expect-error getContextAsync is a protected method jest.spyOn(command, 'getContextAsync').mockReturnValue({ projectId, @@ -59,8 +59,8 @@ describe(ObserveMetricSummary, () => { const command = createCommand([]); await command.runAsync(); - expect(mockFetchObserveMetricSummaryAsync).toHaveBeenCalledTimes(1); - const platforms = mockFetchObserveMetricSummaryAsync.mock.calls[0][3]; + expect(mockFetchObserveMetricsSummaryAsync).toHaveBeenCalledTimes(1); + const platforms = mockFetchObserveMetricsSummaryAsync.mock.calls[0][3]; expect(platforms).toEqual([AppPlatform.Android, AppPlatform.Ios]); jest.useRealTimers(); @@ -70,7 +70,7 @@ describe(ObserveMetricSummary, () => { const command = createCommand(['--platform', 'android']); await command.runAsync(); - const platforms = mockFetchObserveMetricSummaryAsync.mock.calls[0][3]; + const platforms = mockFetchObserveMetricsSummaryAsync.mock.calls[0][3]; expect(platforms).toEqual([AppPlatform.Android]); }); @@ -78,7 +78,7 @@ describe(ObserveMetricSummary, () => { const command = createCommand(['--platform', 'ios']); await command.runAsync(); - const platforms = mockFetchObserveMetricSummaryAsync.mock.calls[0][3]; + const platforms = mockFetchObserveMetricsSummaryAsync.mock.calls[0][3]; expect(platforms).toEqual([AppPlatform.Ios]); }); @@ -86,7 +86,7 @@ describe(ObserveMetricSummary, () => { const command = createCommand(['--metric', 'tti', '--metric', 'cold_launch']); await command.runAsync(); - const metricNames = mockFetchObserveMetricSummaryAsync.mock.calls[0][2]; + const metricNames = mockFetchObserveMetricsSummaryAsync.mock.calls[0][2]; expect(metricNames).toEqual(['expo.app_startup.tti', 'expo.app_startup.cold_launch_time']); }); @@ -97,8 +97,8 @@ describe(ObserveMetricSummary, () => { const command = createCommand([]); await command.runAsync(); - const startTime = mockFetchObserveMetricSummaryAsync.mock.calls[0][4]; - const endTime = mockFetchObserveMetricSummaryAsync.mock.calls[0][5]; + const startTime = mockFetchObserveMetricsSummaryAsync.mock.calls[0][4]; + const endTime = mockFetchObserveMetricsSummaryAsync.mock.calls[0][5]; expect(endTime).toBe('2025-06-15T12:00:00.000Z'); expect(startTime).toBe('2025-04-16T12:00:00.000Z'); @@ -114,8 +114,8 @@ describe(ObserveMetricSummary, () => { ]); await command.runAsync(); - const startTime = mockFetchObserveMetricSummaryAsync.mock.calls[0][4]; - const endTime = mockFetchObserveMetricSummaryAsync.mock.calls[0][5]; + const startTime = mockFetchObserveMetricsSummaryAsync.mock.calls[0][4]; + const endTime = mockFetchObserveMetricsSummaryAsync.mock.calls[0][5]; expect(startTime).toBe('2025-01-01T00:00:00.000Z'); expect(endTime).toBe('2025-02-01T00:00:00.000Z'); }); @@ -124,7 +124,7 @@ describe(ObserveMetricSummary, () => { const command = createCommand(['--stat', 'p90', '--stat', 'eventCount']); await command.runAsync(); - expect(mockBuildObserveMetricSummaryTable).toHaveBeenCalledWith( + expect(mockBuildObserveMetricsSummaryTable).toHaveBeenCalledWith( expect.any(Map), expect.any(Array), ['p90', 'eventCount'], @@ -136,7 +136,7 @@ describe(ObserveMetricSummary, () => { const command = createCommand(['--stat', 'median', '--stat', 'median']); await command.runAsync(); - expect(mockBuildObserveMetricSummaryTable).toHaveBeenCalledWith( + expect(mockBuildObserveMetricsSummaryTable).toHaveBeenCalledWith( expect.any(Map), expect.any(Array), ['median'], @@ -151,8 +151,8 @@ describe(ObserveMetricSummary, () => { const command = createCommand(['--days', '7']); await command.runAsync(); - const startTime = mockFetchObserveMetricSummaryAsync.mock.calls[0][4]; - const endTime = mockFetchObserveMetricSummaryAsync.mock.calls[0][5]; + const startTime = mockFetchObserveMetricsSummaryAsync.mock.calls[0][4]; + const endTime = mockFetchObserveMetricsSummaryAsync.mock.calls[0][5]; expect(endTime).toBe('2025-06-15T12:00:00.000Z'); expect(startTime).toBe('2025-06-08T12:00:00.000Z'); @@ -175,7 +175,7 @@ describe(ObserveMetricSummary, () => { const command = createCommand([]); await command.runAsync(); - expect(mockBuildObserveMetricSummaryTable).toHaveBeenCalledWith( + expect(mockBuildObserveMetricsSummaryTable).toHaveBeenCalledWith( expect.any(Map), expect.any(Array), ['median', 'eventCount'], @@ -195,7 +195,7 @@ describe(ObserveMetricSummary, () => { await command.runAsync(); expect(mockEnableJsonOutput).toHaveBeenCalled(); - expect(mockBuildObserveMetricSummaryJson).toHaveBeenCalledWith( + expect(mockBuildObserveMetricsSummaryJson).toHaveBeenCalledWith( expect.any(Map), expect.any(Array), ['min', 'average'], diff --git a/packages/eas-cli/src/commands/observe/metric-summary.ts b/packages/eas-cli/src/commands/observe/metrics-summary.ts similarity index 93% rename from packages/eas-cli/src/commands/observe/metric-summary.ts rename to packages/eas-cli/src/commands/observe/metrics-summary.ts index d314118f52..c658f43bf9 100644 --- a/packages/eas-cli/src/commands/observe/metric-summary.ts +++ b/packages/eas-cli/src/commands/observe/metrics-summary.ts @@ -41,7 +41,7 @@ const DEFAULT_STATS_JSON: StatisticKey[] = [ 'eventCount', ]; -export default class ObserveMetricSummary extends EasCommand { +export default class ObserveMetricsSummary extends EasCommand { static override hidden = true; static override description = 'display aggregated performance metric statistics grouped by app version'; @@ -73,12 +73,12 @@ export default class ObserveMetricSummary extends EasCommand { }; async runAsync(): Promise { - const { flags } = await this.parse(ObserveMetricSummary); + const { flags } = await this.parse(ObserveMetricsSummary); const { projectId, graphqlClient } = await resolveObserveCommandContextAsync({ command: this, - commandClass: ObserveMetricSummary, - loggedInOnlyContextDefinition: ObserveMetricSummary.loggedInOnlyContextDefinition, + commandClass: ObserveMetricsSummary, + loggedInOnlyContextDefinition: ObserveMetricsSummary.loggedInOnlyContextDefinition, projectIdOverride: flags['project-id'], nonInteractive: flags['non-interactive'], });