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
1 change: 1 addition & 0 deletions docs/constants/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,7 @@ export const eas = [
]),
makeGroup('Reference', [
makePage('eas/observe/reference/metrics.mdx'),
makePage('eas/observe/reference/client-id.mdx'),
makePage('eas/observe/reference/troubleshooting.mdx'),
]),
]),
Expand Down
58 changes: 58 additions & 0 deletions docs/pages/eas/observe/reference/client-id.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: Client ID
description: Read the EAS client ID that EAS Observe records on every metric and event, and use it to correlate data with other services.
---

Every metric, event, and log record that EAS Observe sends carries an **EAS client ID**: a random identifier for one installation of your app. The dashboard and the EAS CLI use it to group data by installation, and `Observe.clientId` exposes the same value to your app.

Use it to look up an installation's Observe data from another tool, such as your crash reporter, your analytics provider, or your own backend.

## Read the client ID

`Observe.clientId` is a string on Android and iOS, and `null` on web:

```tsx
import { Observe } from 'expo-observe';

console.log(Observe.clientId);
// 'f81d4fae-7dec-41d0-a765-00a0c91e6bf6'
```

The value is available as soon as `expo-observe` is imported. You do not need to call `configure()` first.

## Correlate with another service

Attach the client ID to the data you send elsewhere. Then, when you find a problem in that service, you can query the same installation in EAS Observe.

The following example sends the client ID alongside a report to your own backend:

```tsx
import { Observe } from 'expo-observe';

async function reportFeedback(message: string) {
await fetch('https://example.com/feedback', {
method: 'POST',
body: JSON.stringify({
message,
easClientId: Observe.clientId,
}),
});
}
```

Crash reporting and analytics SDKs usually offer a tag, a custom property, or a context field for this. Set it once at startup, then use the value to find the matching installation in EAS Observe.

To go the other direction, run [`eas observe:metrics`](/eas/observe/eas-cli/#eas-observemetrics) or [`eas observe:events`](/eas/observe/eas-cli/#eas-observeevents) with `--json`. Each sample includes its `easClientId`.

## How the client ID behaves

- The ID is generated on the device the first time an EAS client library needs it, and is stored in native preferences. It is not derived from any hardware or account identifier.
- It is stable across app launches, app updates, and EAS Updates.
- It is shared with the other EAS client libraries in your app, such as `expo-updates`. The same installation has one ID across all of them.
- It changes when the app's data is cleared or when the app is reinstalled, although a backup restore (including Android Auto Backup on reinstall) can carry the previous ID over.

Because the ID identifies an installation rather than a person, treat it as pseudonymous data. If you send it to a third-party service, check that your privacy policy covers that use.

## Sampling and the client ID

The [`sampleRate`](/eas/observe/configuration/#sampling) decision is derived from the client ID, which is why an installation stays in-sample or out-of-sample across launches. `Observe.clientId` returns the ID whether or not the installation is in-sample, so you can log it even when the app dispatches no metrics.
73 changes: 72 additions & 1 deletion docs/pages/eas/workflows/syntax.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ With the `paths` list, you can trigger the workflow only when changes are made t

When neither `branches` nor `tags` are provided, `branches` defaults to `['*']` and `tags` defaults to `[]`, which means the workflow triggers on push events to all branches and does not trigger on tag pushes. If only one of the two lists is provided the other defaults to `[]`.

With the [`if:`](#ontriggerif) condition, you can decide whether a workflow run starts.

```yaml
on:
# @info #
Expand Down Expand Up @@ -112,6 +114,8 @@ With the `tags` list, you can trigger the workflow only when those specified tag

When neither `branches` nor `tags` are provided, `branches` defaults to `['*']` and `tags` defaults to `[]`, which means the workflow triggers when any branch is deleted and does not trigger on tag deletions. If only one of the two lists is provided the other defaults to `[]`.

With the [`if:`](#ontriggerif) condition, you can decide whether a workflow run starts.

> **info** Workflow files are read from the default branch HEAD at the time of deletion, not from the deleted ref.

Pair this trigger with the [`branch-delete`](/eas/workflows/pre-packaged-jobs#branch-delete) job to remove EAS Update branches when GitHub branches are deleted. See the [Clean up update branches example](/eas/workflows/examples/branch-cleanup) for a complete workflow.
Expand Down Expand Up @@ -156,6 +160,8 @@ The `branches` filter matches the pull request's current base branch, so retarge

With the `paths` list, you can trigger the workflow only when changes are made to files matching the specified paths. For example, if you use `paths: ['apps/mobile/**']`, only changes to files in the `apps/mobile` directory trigger the workflow. Supports globs. By default, changes to any path trigger the workflow.

You can add an [`if:`](#ontriggerif) condition to decide whether a workflow run starts.

```yaml
on:
# @info #
Expand Down Expand Up @@ -184,6 +190,8 @@ Runs your workflow when a pull request is labeled with a matching label.

With the `labels` list, you can specify which labels, when assigned to your pull request, trigger the workflow. For example, if you use `labels: ['Test']`, only labeling a pull request with the `Test` label triggers the workflow. Defaults to `[]` when not provided, which means no labels trigger the workflow.

To decide whether a workflow run starts, add an [`if:`](#ontriggerif) condition.

You can also provide a list of matching labels directly to `on.pull_request_labeled` for simpler syntax.

```yaml
Expand All @@ -209,6 +217,33 @@ on:
# other labels
```

### `on.pull_request_comment`

Runs your workflow when someone creates, edits, or deletes a comment on a pull request.

> **info** Only open, unmerged pull requests trigger `pull_request_comment` workflow runs. Pull requests opened from a fork of the connected repository do not trigger workflow runs.

With the `types` list, you can specify which events trigger the workflow. Defaults to `['created']` when not provided. Supported event types:

- `created`
- `edited`
- `deleted`

Unlike `on.pull_request`, this trigger has no `branches` or `paths` filter.

With the [`if:`](#ontriggerif) condition, you can decide whether a workflow run starts.

```yaml
on:
# @info #
pull_request_comment:
# @end #
types:
- created
- edited
# other event types
```

### `on.app_store_connect`

Runs your workflow when one of the selected App Store Connect events occurs.
Expand All @@ -217,6 +252,8 @@ Runs your workflow when one of the selected App Store Connect events occurs.

When `on.app_store_connect` is present, you must specify at least one event domain (`app_version`, `build_upload`, `external_beta`, or `beta_feedback`). Within a configured event domain, you can specify which states should trigger your workflow.

Each event domain also accepts an [`if:`](#ontriggerif) condition, so you can decide whether a workflow run starts.

#### `on.app_store_connect.app_version.states`

Filters app store app version state change events. Defaults to all supported app version states when not provided.
Expand Down Expand Up @@ -409,6 +446,36 @@ jobs:
echo "Hello, ${{ inputs.name || 'World' }}!"
```

### `on.<trigger>.if`

The `if` condition on a trigger decides whether a workflow run starts.

You can add `if:` under `push`, `ref_delete`, `pull_request`, `pull_request_labeled`, `pull_request_comment`, and each `app_store_connect` event domain (`app_version`, `build_upload`, `external_beta`, or `beta_feedback`).

The value is a boolean or an expression string. You can write it with or without the `${{ }}` wrapper.

```yaml
on:
pull_request:
# @info #
if: ${{ !github.event.pull_request.draft }}
# @end #
```

The expression must fit in one `${{ }}` block and can be at most 250 characters.

When the condition evaluates to false, no workflow run starts for that trigger event. A retry of an existing run skips this check. It only applies when a new run is created.

The expression can use the [`github`](#github), [`app_store_connect`](#app_store_connect), `inputs`, [`workflow`](#workflow), `app`, and `account` contexts. It supports all [context functions](#context-functions) except `success()`, `failure()`, and `hashFiles()`. Those functions need a job or step that has already run, but a trigger's `if` condition evaluates before any job starts.

```yaml
on:
push:
if: ${{ github.ref_name == 'main' }}
pull_request_comment:
if: ${{ startsWith(github.event.comment.body, '/deploy') }}
```

## `jobs`

A workflow run is made up of one or more jobs.
Expand Down Expand Up @@ -825,6 +892,10 @@ type GitHubContext = {
merged: boolean | null;
// ... Other fields from the GitHub Pull Request webhook payload
};
comment?: {
body: string;
// ... Other fields from the GitHub issue_comment webhook payload
};
changes?: {
base?: {
ref?: {
Expand All @@ -839,7 +910,7 @@ type GitHubContext = {
};
```

> **info** The `event` object contains the full [GitHub webhook payload](https://docs.github.com/en/webhooks/webhook-events-and-payloads). For `pull_request` events, `event.pull_request` includes fields from GitHub's Pull Request webhook payload, such as `github.event.pull_request.title` and `github.event.pull_request.body`. For edited pull request events, `github.event.changes` contains the fields that changed, such as `github.event.changes.base.ref.from` when the base branch changed. The type above lists a few useful fields, but additional fields such as `user`, `labels`, `milestone`, and others are also available.
> **info** The `event` object contains the full [GitHub webhook payload](https://docs.github.com/en/webhooks/webhook-events-and-payloads). For `pull_request` events, `event.pull_request` includes fields from GitHub's Pull Request webhook payload, such as `github.event.pull_request.title` and `github.event.pull_request.body`. For edited pull request events, `github.event.changes` contains the fields that changed, such as `github.event.changes.base.ref.from` when the base branch changed. For `pull_request_comment` events, `event.comment.body` contains the comment text, and `event.pull_request.number` and `event.number` contain the pull request number. The type above lists a few useful fields, but additional fields such as `user`, `labels`, `milestone`, and others are also available.

If a workflow run is started from `eas workflow:run`, its `event_name` will be `workflow_dispatch` and all the rest of the properties will be empty.

Expand Down
85 changes: 85 additions & 0 deletions docs/public/_redirects
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,91 @@
/versions/v55.0.0/sdk/ui/jetpack-compose/textinput /versions/v55.0.0/sdk/ui/jetpack-compose/textfield 301
/versions/v55.0.0/sdk/ui/jetpack-compose/textinput/ /versions/v55.0.0/sdk/ui/jetpack-compose/textfield 301

# AI agents guess /<section>/introduction as a section's first page; redirect the HTML and .md forms to the real one.
# Keep this block above the first splat rule, or the parser's 100 dynamic-rule cap silently drops the rest of the file.
/agents/introduction /agents 301
/agents/introduction/ /agents 301
/agents/introduction.md /agents.md 301
/develop/introduction /develop/tools 301
/develop/introduction/ /develop/tools 301
/develop/introduction.md /develop/tools.md 301
/review/introduction /review/overview 301
/review/introduction/ /review/overview 301
/review/introduction.md /review/overview.md 301
/deploy/introduction /deploy/build-project 301
/deploy/introduction/ /deploy/build-project 301
/deploy/introduction.md /deploy/build-project.md 301
/monitoring/introduction /monitoring/services 301
/monitoring/introduction/ /monitoring/services 301
/monitoring/introduction.md /monitoring/services.md 301
/debugging/introduction /debugging/errors-and-warnings 301
/debugging/introduction/ /debugging/errors-and-warnings 301
/debugging/introduction.md /debugging/errors-and-warnings.md 301
/guides/introduction /guides/overview 301
/guides/introduction/ /guides/overview 301
/guides/introduction.md /guides/overview.md 301
/workflow/introduction /workflow/overview 301
/workflow/introduction/ /workflow/overview 301
/workflow/introduction.md /workflow/overview.md 301
/modules/introduction /modules/overview 301
/modules/introduction/ /modules/overview 301
/modules/introduction.md /modules/overview.md 301
/push-notifications/introduction /push-notifications/overview 301
/push-notifications/introduction/ /push-notifications/overview 301
/push-notifications/introduction.md /push-notifications/overview.md 301
/regulatory-compliance/introduction /regulatory-compliance/data-and-privacy-protection 301
/regulatory-compliance/introduction/ /regulatory-compliance/data-and-privacy-protection 301
/regulatory-compliance/introduction.md /regulatory-compliance/data-and-privacy-protection.md 301
/linking/introduction /linking/overview 301
/linking/introduction/ /linking/overview 301
/linking/introduction.md /linking/overview.md 301
/bare/introduction /bare/overview 301
/bare/introduction/ /bare/overview 301
/bare/introduction.md /bare/overview.md 301
/brownfield/introduction /brownfield/overview 301
/brownfield/introduction/ /brownfield/overview 301
/brownfield/introduction.md /brownfield/overview.md 301
/troubleshooting/introduction /troubleshooting/overview 301
/troubleshooting/introduction/ /troubleshooting/overview 301
/troubleshooting/introduction.md /troubleshooting/overview.md 301
/eas/introduction /eas 301
/eas/introduction/ /eas 301
/eas/introduction.md /eas.md 301
/eas/metadata/introduction /eas/metadata 301
/eas/metadata/introduction/ /eas/metadata 301
/eas/metadata/introduction.md /eas/metadata.md 301
/accounts/introduction /accounts/account-types 301
/accounts/introduction/ /accounts/account-types 301
/accounts/introduction.md /accounts/account-types.md 301
/billing/introduction /billing/overview 301
/billing/introduction/ /billing/overview 301
/billing/introduction.md /billing/overview.md 301
/app-signing/introduction /app-signing/app-credentials 301
/app-signing/introduction/ /app-signing/app-credentials 301
/app-signing/introduction.md /app-signing/app-credentials.md 301
/custom-builds/introduction /custom-builds/get-started 301
/custom-builds/introduction/ /custom-builds/get-started 301
/custom-builds/introduction.md /custom-builds/get-started.md 301
/additional-resources/introduction /additional-resources 301
/additional-resources/introduction/ /additional-resources 301
/additional-resources/introduction.md /additional-resources.md 301
/versions/introduction /versions/latest 301
/versions/introduction/ /versions/latest 301
/versions/introduction.md /versions/latest.md 301
/technical-specs/introduction /technical-specs/expo-updates-1 301
/technical-specs/introduction/ /technical-specs/expo-updates-1 301
/technical-specs/introduction.md /technical-specs/expo-updates-1.md 301
/more/introduction /more/expo-cli 301
/more/introduction/ /more/expo-cli 301
/more/introduction.md /more/expo-cli.md 301
/archive/introduction /archive 301
/archive/introduction/ /archive 301
/archive/introduction.md /archive.md 301

# .md siblings of introduction redirects declared elsewhere in this file
/get-started/introduction.md /get-started/create-a-project.md 301
/submit/introduction.md /deploy/submit-to-app-stores.md 301

# EAS Build, Submit, Update, and Insights predate the /eas/* URL convention;
/eas/build/* /build/:splat 301
/eas/build /build/introduction 301
Expand Down
2 changes: 1 addition & 1 deletion docs/public/static/data/unversioned/expo-observe.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/ui/components/Dropdown/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function Item({
<DropdownMenu.Item
aria-disabled={disabled}
className={mergeClasses(
'group relative z-40 flex cursor-pointer items-center justify-between rounded-sm px-2 py-1 transition-colors select-none',
'group relative z-40 flex cursor-pointer items-center justify-between rounded-lg px-2 py-1 transition-colors select-none',
'hover:outline-0 hocus:bg-hover',
disabled && 'cursor-default opacity-60 hocus:bg-default'
)}
Expand Down
32 changes: 15 additions & 17 deletions docs/ui/components/Dropdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,21 @@ export function Dropdown({
<Root modal={false}>
<Trigger asChild>{trigger}</Trigger>
<Portal>
<div className="bg-danger">
<Content
className={mergeClasses(
'flex min-w-45 flex-col gap-0.5 rounded-md border border-default bg-default p-1 shadow-md',
'will-change-[opacity,transform] data-[side=bottom]:animate-slideUpAndFadeIn',
className
)}
side={side}
sideOffset={sideOffset}
collisionPadding={collisionPadding}
{...rest}>
<Arrow asChild>
<div className="relative -top-1 size-2.5 rotate-45 border-r border-b border-default bg-default" />
</Arrow>
{children}
</Content>
</div>
<Content
className={mergeClasses(
'flex min-w-45 flex-col gap-1 rounded-xl border border-default bg-default p-2 shadow-md',
'will-change-[opacity,transform] data-[side=bottom]:animate-slideUpAndFadeIn',
className
)}
side={side}
sideOffset={sideOffset}
collisionPadding={collisionPadding}
{...rest}>
<Arrow asChild>
<div className="relative -top-1 size-2.5 rotate-45 border-r border-b border-default bg-default" />
</Arrow>
{children}
</Content>
</Portal>
</Root>
);
Expand Down
17 changes: 9 additions & 8 deletions docs/ui/components/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function Select({
/>
}
className={mergeClasses(
'min-h-9 transform-none justify-between truncate px-3',
'min-h-9 justify-between truncate px-3 active:scale-100',
!value && 'text-quaternary',
size === 'lg' && 'min-h-13',
className
Expand All @@ -81,13 +81,14 @@ export function Select({
</SelectPrimitive.Trigger>
<SelectPrimitive.Portal>
<SelectPrimitive.Content
// z-[605] to be above the dialogs (601)
position="popper"
sideOffset={4}
className={mergeClasses(
'relative z-605 max-w-[87.5vw] overflow-hidden rounded-md border border-default bg-overlay shadow-md',
'max-md:max-w-[unset]'
)}
data-orientation="horizontal">
<SelectPrimitive.ScrollUpButton className="flex h-7 items-center justify-center rounded-t-md bg-element">
'relative z-605 overflow-hidden rounded-xl border border-default bg-overlay shadow-md',
'max-h-(--radix-select-content-available-height) min-w-(--radix-select-trigger-width)',
'max-w-[87.5vw] max-md:max-w-[unset]'
)}>
<SelectPrimitive.ScrollUpButton className="flex h-7 items-center justify-center bg-element">
<ChevronUpIcon aria-hidden="true" className="icon-sm text-icon-secondary" />
</SelectPrimitive.ScrollUpButton>
<SelectPrimitive.Viewport>
Expand Down Expand Up @@ -141,7 +142,7 @@ export function Select({
))}
</SelectPrimitive.Group>
</SelectPrimitive.Viewport>
<SelectPrimitive.ScrollDownButton className="flex h-7 items-center justify-center rounded-b-md bg-element">
<SelectPrimitive.ScrollDownButton className="flex h-7 items-center justify-center bg-element">
<ChevronDownIcon aria-hidden="true" className="icon-sm text-icon-secondary" />
</SelectPrimitive.ScrollDownButton>
</SelectPrimitive.Content>
Expand Down
Loading
Loading