Skip to content

[eas-cli] Add observe namespace and commands#3564

Open
douglowder wants to merge 17 commits intomainfrom
doug/eng-20174-observe-events
Open

[eas-cli] Add observe namespace and commands#3564
douglowder wants to merge 17 commits intomainfrom
doug/eng-20174-observe-events

Conversation

@douglowder
Copy link
Copy Markdown
Contributor

@douglowder douglowder commented Mar 31, 2026

This is a merge of @Ubax 's changes from #3431 and #3432 , with refactoring to align with the recent OCLIF upgrade and some other adjustments:

Why

Provide CLI commands to display Observe metrics and events.

USAGE
  $ eas observe:metrics [--platform android|ios] [--metric tti|ttr|cold_launch|warm_launch|bundle_load...] [--stat
    min|median|max|average|p80|p90|p99|eventCount...] [--start <value> | --days-from-now <value>] [--end <value> | ]
    [--project-id <value>] [--json] [--non-interactive]

FLAGS
  --days-from-now=<value>  Show metrics from the last N days (mutually exclusive with --start/--end)
  --end=<value>            End of time range for metrics data (ISO date).
  --json                   Enable JSON output, non-JSON messages will be printed to stderr. Implies --non-interactive.
  --metric=<option>...     Metric name to display (can be specified multiple times).
                           <options: tti|ttr|cold_launch|warm_launch|bundle_load>
  --non-interactive        Run the command in non-interactive mode.
  --platform=<option>      Filter by platform
                           <options: android|ios>
  --project-id=<value>     EAS project ID (defaults to the project ID of the current directory)
  --start=<value>          Start of time range for metrics data (ISO date).
  --stat=<option>...       Statistic to display per metric (can be specified multiple times)
                           <options: min|median|max|average|p80|p90|p99|eventCount>

DESCRIPTION
  display app performance metrics grouped by app version

---------------------------
USAGE
  $ eas observe:events --metric
    tti|ttr|cold_launch|warm_launch|bundle_load|expo.app_startup.tti|expo.app_startup.ttr|expo.app_startup.cold_launch_t
    ime|expo.app_startup.warm_launch_time|expo.app_startup.bundle_load_time [--sort slowest|fastest|newest|oldest]
    [--platform android|ios] [--after <value>] [--limit <value>] [--start <value> | --days-from-now <value>] [--end
    <value> | ] [--app-version <value>] [--update-id <value>] [--project-id <value>] [--json] [--non-interactive]

FLAGS
  --after=<value>          Cursor for pagination. Use the endCursor from a previous query to fetch the next page.
  --app-version=<value>    Filter by app version
  --days-from-now=<value>  Show events from the last N days (mutually exclusive with --start/--end)
  --end=<value>            End of time range (ISO date)
  --json                   Enable JSON output, non-JSON messages will be printed to stderr. Implies --non-interactive.
  --limit=<value>          The number of items to fetch each query. Defaults to 10 and is capped at 100.
  --metric=<option>        (required) Metric to query (full name or alias)
                           <options: tti|ttr|cold_launch|warm_launch|bundle_load|expo.app_startup.tti|expo.app_startup.t
                           tr|expo.app_startup.cold_launch_time|expo.app_startup.warm_launch_time|expo.app_startup.bundl
                           e_load_time>
  --non-interactive        Run the command in non-interactive mode.
  --platform=<option>      Filter by platform
                           <options: android|ios>
  --project-id=<value>     EAS project ID (defaults to the project ID of the current directory)
  --sort=<option>          [default: oldest] Sort order for events
                           <options: slowest|fastest|newest|oldest>
  --start=<value>          Start of time range (ISO date)
  --update-id=<value>      Filter by EAS update ID

DESCRIPTION
  display individual app performance events ordered by metric value

--------------------------------------

USAGE
  $ eas observe:versions [--platform android|ios] [--start <value> | --days-from-now <value>] [--end <value> | ]
    [--project-id <value>] [--json] [--non-interactive]

FLAGS
  --days-from-now=<value>  Show versions from the last N days (mutually exclusive with --start/--end)
  --end=<value>            End of time range (ISO date)
  --json                   Enable JSON output, non-JSON messages will be printed to stderr. Implies --non-interactive.
  --non-interactive        Run the command in non-interactive mode.
  --platform=<option>      Filter by platform
                           <options: android|ios>
  --project-id=<value>     EAS project ID (defaults to the project ID of the current directory)
  --start=<value>          Start of time range (ISO date)

DESCRIPTION
  display app versions with build and update details

How

New commands and GraphQL queries.

Test Plan

New unit tests added in fetchEvents-test.ts

@linear
Copy link
Copy Markdown

linear bot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@douglowder douglowder changed the title [eas-cli] Add observe:metrics command [eas-cli] Add observe:metrics and observe:events commands Mar 31, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 31, 2026

Codecov Report

❌ Patch coverage is 87.66067% with 48 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.64%. Comparing base (c6d4fae) to head (2191454).

Files with missing lines Patch % Lines
packages/eas-cli/src/observe/formatVersions.ts 12.91% 27 Missing ⚠️
...ckages/eas-cli/src/graphql/queries/ObserveQuery.ts 31.25% 11 Missing ⚠️
packages/eas-cli/src/observe/fetchVersions.ts 33.34% 10 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3564      +/-   ##
==========================================
+ Coverage   54.27%   54.64%   +0.37%     
==========================================
  Files         820      833      +13     
  Lines       35055    35443     +388     
  Branches     7260     7335      +75     
==========================================
+ Hits        19024    19364     +340     
- Misses      15944    15992      +48     
  Partials       87       87              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@douglowder douglowder marked this pull request as ready for review March 31, 2026 21:56
@douglowder douglowder force-pushed the doug/eng-20174-observe-events branch from 5cd183a to 3b1dfa6 Compare April 8, 2026 20:12
@douglowder douglowder changed the title [eas-cli] Add observe:metrics and observe:events commands [eas-cli] Add observe namespace and commands Apr 8, 2026
Add new observe:versions subcommand that queries the appVersions
resolver to display app version hierarchy with build numbers,
updates, and event/user counts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@douglowder douglowder force-pushed the doug/eng-20174-observe-events branch from b84ba0e to 9a4a75c Compare April 8, 2026 20:49
Switch timeSeries query from deprecated versionMarkers to
appVersionMarkers, which uses the new app version hierarchy
with buildNumbers, updates, and easBuilds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@douglowder douglowder force-pushed the doug/eng-20174-observe-events branch from e17f0fb to 4a2bed0 Compare April 8, 2026 21:02
Update test mocks to use timeSeriesAsync and the new
AppObserveTimeSeriesResult shape with appVersionMarkers
and aggregate statistics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment on lines +111 to +118
if (flags['days-from-now']) {
endTime = new Date().toISOString();
startTime = new Date(Date.now() - flags['days-from-now'] * 24 * 60 * 60 * 1000).toISOString();
} else {
endTime = flags.end ?? new Date().toISOString();
startTime =
flags.start ?? new Date(Date.now() - DEFAULT_DAYS_BACK * 24 * 60 * 60 * 1000).toISOString();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is duplicated in events, metrics and versions - could we pull it out to a util?

- [eas-cli] Add `--non-interactive` flag to `metadata:push` and `metadata:pull` commands with ASC API Key auth support. ([#3548](https://github.com/expo/eas-cli/pull/3548) by [@EvanBacon](https://github.com/EvanBacon))
- Add `eas observe:metrics` command for monitoring app performance metrics. ([#3401](https://github.com/expo/eas-cli/pull/3401) by [@ubax](https://github.com/ubax))
- Add `eas observe:events` command for monitoring app performance metrics. ([#3401](https://github.com/expo/eas-cli/pull/3401) by [@ubax](https://github.com/ubax))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should update the changelog to add eas observe:versions command

})(),
sort: Flags.option({
description: 'Sort order for events',
options: Object.values(EventsOrderPreset).map(s => s.toLowerCase()),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The --sort flag doesn't work at all, because we're inputting in lowercase and comparing against UPPERCASE in resolveOrderBy


if (flags.json) {
const stats: StatisticKey[] = argumentsStat ?? DEFAULT_STATS_JSON;
printJsonOnlyOutput(buildObserveMetricsJson(metricsMap, metricNames, stats));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the default table we get is just way too wide and will wrap in most terminals, making it hard to understand.

Image

I suggest we:

  1. show ios and android separately (so we can skip the platform column)
  2. merge the value (event count)
  3. say at top what the user is looking at, e.g. "Median values (event count) for the last 60 days"
Image

Comment on lines +73 to +74
1.2.0 iOS 0.35s 110 1.32s 90
1.1.0 Android 0.25s 120 1.12s 100 "
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The app version should always include the build number, so e.g. if we get the following back as releases:

[
  {
    "appVersion": "1.3.0", // <--- top level, everything starts with app version
    "firstSeenAt": "2026-03-31T00:16:32.687Z",
    "eventCount": 1280,
    "uniqueUserCount": 20,
    "buildNumbers": [
      {
        "appBuildNumber": "47", // <--- every app version can have one or many builds
        "firstSeenAt": "2026-03-31T00:16:32.687Z",
        "eventCount": 1280,
        "uniqueUserCount": 20,
        "easBuilds": [
          {
            "easBuildId": "e1a2b3c4-1300-4001-a000-100000000003", // <--- every build can have 0 to or many EAS Builds
            "firstSeenAt": "2026-03-31T00:16:32.687Z",
            "eventCount": 1280,
            "uniqueUserCount": 20
          }
        ]
      }
    ],
    "updates": [
      {
        "appUpdateId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",  // <--- every app version can have one or many updates
        "firstSeenAt": "2026-04-02T03:52:43.860Z",
        "eventCount": 592,
        "uniqueUserCount": 20,
        "easBuilds": [
           {
             "easBuildId": "e1a2b3c4-1300-4001-a000-100000000003", // <--- every update should really only be used on one build at a time, but it's possible that it's on more than one
              "firstSeenAt": "2026-04-02T03:52:43.860Z",
              "eventCount": 592,
             "uniqueUserCount": 20
           }
        ]
      }
    ]
  }
]

Then the app version we use should be:

  • 1.3.0 (47)

Plus, it has one update:

  • b2c3d4e5-f6a7-8901-bcde-f12345678901

required: true,
options: [
...Object.keys(METRIC_ALIASES),
...Object.keys(METRIC_ALIASES).map(key => METRIC_ALIASES[key]),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we should NOT accept the values here, let's only accept tti, ttr, cold_launch, warm_launch, bundle_load (same as the observe:events) - so, remove the second line.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

✅ Thank you for adding the changelog entry!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants