feat: group analytics
* wip * wip * wip * wip * wip * add buffer * wip * wip * fixes * fix * wip * group validation * fix group issues * docs: add groups
This commit is contained in:
committed by
GitHub
parent
88a2d876ce
commit
11e9ecac1a
@@ -68,6 +68,34 @@ app.listen(3000, () => {
|
||||
- `trackRequest` - A function that returns `true` if the request should be tracked.
|
||||
- `getProfileId` - A function that returns the profile ID of the user making the request.
|
||||
|
||||
## Working with Groups
|
||||
|
||||
Groups let you track analytics at the account or company level. Since Express is a backend SDK, you can upsert groups and assign users from your route handlers.
|
||||
|
||||
See the [Groups guide](/docs/get-started/groups) for the full walkthrough.
|
||||
|
||||
```ts
|
||||
app.post('/login', async (req, res) => {
|
||||
const user = await loginUser(req.body);
|
||||
|
||||
// Identify the user
|
||||
req.op.identify({ profileId: user.id, email: user.email });
|
||||
|
||||
// Create/update the group entity
|
||||
req.op.upsertGroup({
|
||||
id: user.organizationId,
|
||||
type: 'company',
|
||||
name: user.organizationName,
|
||||
properties: { plan: user.plan },
|
||||
});
|
||||
|
||||
// Assign the user to the group
|
||||
req.op.setGroup(user.organizationId);
|
||||
|
||||
res.json({ ok: true });
|
||||
});
|
||||
```
|
||||
|
||||
## Typescript
|
||||
|
||||
If `req.op` is not typed you can extend the `Request` interface.
|
||||
|
||||
@@ -116,9 +116,38 @@ op.decrement({
|
||||
});
|
||||
```
|
||||
|
||||
### Working with Groups
|
||||
|
||||
Groups let you track analytics at the account or company level. See the [Groups guide](/docs/get-started/groups) for the full walkthrough.
|
||||
|
||||
**Create or update a group:**
|
||||
|
||||
```js title="index.js"
|
||||
import { op } from './op.ts'
|
||||
|
||||
op.upsertGroup({
|
||||
id: 'org_acme',
|
||||
type: 'company',
|
||||
name: 'Acme Inc',
|
||||
properties: { plan: 'enterprise' },
|
||||
});
|
||||
```
|
||||
|
||||
**Assign the current user to a group** (call after `identify`):
|
||||
|
||||
```js title="index.js"
|
||||
import { op } from './op.ts'
|
||||
|
||||
op.setGroup('org_acme');
|
||||
// or multiple groups:
|
||||
op.setGroups(['org_acme', 'team_eng']);
|
||||
```
|
||||
|
||||
Once set, all subsequent `track()` calls will automatically include the group IDs.
|
||||
|
||||
### Clearing User Data
|
||||
|
||||
To clear the current user's data:
|
||||
To clear the current user's data (including groups):
|
||||
|
||||
```js title="index.js"
|
||||
import { op } from './op.ts'
|
||||
|
||||
@@ -227,9 +227,32 @@ useOpenPanel().decrement({
|
||||
});
|
||||
```
|
||||
|
||||
### Working with Groups
|
||||
|
||||
Groups let you track analytics at the account or company level. See the [Groups guide](/docs/get-started/groups) for the full walkthrough.
|
||||
|
||||
**Create or update a group:**
|
||||
|
||||
```tsx title="app/login/page.tsx"
|
||||
useOpenPanel().upsertGroup({
|
||||
id: 'org_acme',
|
||||
type: 'company',
|
||||
name: 'Acme Inc',
|
||||
properties: { plan: 'enterprise' },
|
||||
});
|
||||
```
|
||||
|
||||
**Assign the current user to a group** (call after `identify`):
|
||||
|
||||
```tsx title="app/login/page.tsx"
|
||||
useOpenPanel().setGroup('org_acme');
|
||||
```
|
||||
|
||||
Once set, all subsequent `track()` calls will automatically include the group IDs.
|
||||
|
||||
### Clearing User Data
|
||||
|
||||
To clear the current user's data:
|
||||
To clear the current user's data (including groups):
|
||||
|
||||
```js title="index.js"
|
||||
useOpenPanel().clear()
|
||||
|
||||
@@ -174,9 +174,37 @@ function MyComponent() {
|
||||
}
|
||||
```
|
||||
|
||||
### Working with Groups
|
||||
|
||||
Groups let you track analytics at the account or company level. See the [Groups guide](/docs/get-started/groups) for the full walkthrough.
|
||||
|
||||
```tsx
|
||||
import { op } from '@/openpanel';
|
||||
|
||||
function LoginComponent() {
|
||||
const handleLogin = async (user: User) => {
|
||||
// 1. Identify the user
|
||||
op.identify({ profileId: user.id, email: user.email });
|
||||
|
||||
// 2. Create/update the group entity (only when data changes)
|
||||
op.upsertGroup({
|
||||
id: user.organizationId,
|
||||
type: 'company',
|
||||
name: user.organizationName,
|
||||
properties: { plan: user.plan },
|
||||
});
|
||||
|
||||
// 3. Link the user to their group — tags all future events
|
||||
op.setGroup(user.organizationId);
|
||||
};
|
||||
|
||||
return <button onClick={() => handleLogin(user)}>Login</button>;
|
||||
}
|
||||
```
|
||||
|
||||
### Clearing User Data
|
||||
|
||||
To clear the current user's data:
|
||||
To clear the current user's data (including groups):
|
||||
|
||||
```tsx
|
||||
import { op } from '@/openpanel';
|
||||
|
||||
@@ -59,7 +59,16 @@ The trailing edge of the line (the current, incomplete interval) is shown as a d
|
||||
|
||||
## Insights
|
||||
|
||||
If you have configured insights for your project, a scrollable row of insight cards appears below the chart. Each card shows a pre-configured metric with its current value and trend. Clicking a card applies that insight's filter to the entire overview page. Insights are optional—this section is hidden when none have been configured.
|
||||
A scrollable row of insight cards appears below the chart once your project has at least 30 days of data. OpenPanel automatically detects significant trends across pageviews, entry pages, referrers, and countries—no configuration needed.
|
||||
|
||||
Each card shows:
|
||||
- **Share**: The percentage of total traffic that property represents (e.g., "United States: 42% of all sessions")
|
||||
- **Absolute change**: The raw increase or decrease in sessions compared to the previous period
|
||||
- **Percentage change**: How much that property grew or declined relative to its own previous value
|
||||
|
||||
For example, if the US had 1,000 sessions last week and 1,200 this week, the card shows "+200 sessions (+20%)".
|
||||
|
||||
Clicking any insight card filters the entire overview page to show only data matching that property—letting you drill into what's driving the trend.
|
||||
|
||||
---
|
||||
|
||||
|
||||
208
apps/public/content/docs/get-started/groups.mdx
Normal file
208
apps/public/content/docs/get-started/groups.mdx
Normal file
@@ -0,0 +1,208 @@
|
||||
---
|
||||
title: Groups
|
||||
description: Track analytics at the account, company, or team level — not just individual users.
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout';
|
||||
|
||||
Groups let you associate users with a shared entity — like a company, workspace, or team — and analyze behavior at that level. Instead of asking "what did Jane do?", you can ask "what is Acme Inc doing?"
|
||||
|
||||
This is especially useful for B2B SaaS products where a single paying account has many users.
|
||||
|
||||
## How Groups work
|
||||
|
||||
There are two separate concepts:
|
||||
|
||||
1. **The group entity** — created/updated with `upsertGroup()`. Stores metadata about the group (name, plan, etc.).
|
||||
2. **Group membership** — set with `setGroup()` / `setGroups()`. Links a user profile to one or more groups, and automatically attaches those group IDs to every subsequent `track()` call.
|
||||
|
||||
## Creating or updating a group
|
||||
|
||||
Call `upsertGroup()` to create a group or update its properties. The group is identified by its `id` and `type`.
|
||||
|
||||
```typescript
|
||||
op.upsertGroup({
|
||||
id: 'org_acme', // Your group's unique ID
|
||||
type: 'company', // Group type (company, workspace, team, etc.)
|
||||
name: 'Acme Inc', // Display name
|
||||
properties: {
|
||||
plan: 'enterprise',
|
||||
seats: 25,
|
||||
industry: 'logistics',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Group payload
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `id` | `string` | Yes | Unique identifier for the group |
|
||||
| `type` | `string` | Yes | Category of group (e.g. `"company"`, `"workspace"`) |
|
||||
| `name` | `string` | Yes | Human-readable display name |
|
||||
| `properties` | `object` | No | Custom metadata about the group |
|
||||
|
||||
## Managing groups in the dashboard
|
||||
|
||||
The easiest way to create, edit, and delete groups is directly in the OpenPanel dashboard. Navigate to your project and open the **Groups** section — from there you can manage group names, types, and properties without touching any code.
|
||||
|
||||
`upsertGroup()` is the right tool when your group properties are **dynamic and driven by your own data** — for example, syncing a customer's current plan, seat count, or MRR from your backend at login time.
|
||||
|
||||
<Callout>
|
||||
A good rule of thumb: call `upsertGroup()` on login or when group properties change — not on every request or page view. If you find yourself calling it frequently with the same data, the dashboard is probably the better place to manage that group.
|
||||
</Callout>
|
||||
|
||||
## Assigning a user to a group
|
||||
|
||||
After identifying a user, call `setGroup()` to link them to a group. This also attaches the group ID to all future `track()` calls for the current session.
|
||||
|
||||
```typescript
|
||||
// After login
|
||||
op.identify({ profileId: 'user_123' });
|
||||
|
||||
// Link the user to their organization
|
||||
op.setGroup('org_acme');
|
||||
```
|
||||
|
||||
For users that belong to multiple groups:
|
||||
|
||||
```typescript
|
||||
op.setGroups(['org_acme', 'team_engineering']);
|
||||
```
|
||||
|
||||
<Callout>
|
||||
`setGroup()` and `setGroups()` persist group IDs on the SDK instance. All subsequent `track()` calls will automatically include these group IDs until `clear()` is called.
|
||||
</Callout>
|
||||
|
||||
## Full login flow example
|
||||
|
||||
`setGroup()` doesn't require the group to exist first. You can call it with just an ID — events will be tagged with that group ID, and you can create the group later in the dashboard or via `upsertGroup()`.
|
||||
|
||||
```typescript
|
||||
// 1. Identify the user
|
||||
op.identify({
|
||||
profileId: 'user_123',
|
||||
firstName: 'Jane',
|
||||
email: 'jane@acme.com',
|
||||
});
|
||||
|
||||
// 2. Assign the user to the group — the group doesn't need to exist yet
|
||||
op.setGroup('org_acme');
|
||||
|
||||
// 3. All subsequent events are now tagged with the group
|
||||
op.track('dashboard_viewed'); // → includes groups: ['org_acme']
|
||||
op.track('report_exported'); // → includes groups: ['org_acme']
|
||||
```
|
||||
|
||||
If you want to sync dynamic group properties from your own data (plan, seats, MRR), add `upsertGroup()` to the flow:
|
||||
|
||||
```typescript
|
||||
op.identify({ profileId: 'user_123', email: 'jane@acme.com' });
|
||||
|
||||
// Sync group metadata from your backend
|
||||
op.upsertGroup({
|
||||
id: 'org_acme',
|
||||
type: 'company',
|
||||
name: 'Acme Inc',
|
||||
properties: { plan: 'pro' },
|
||||
});
|
||||
|
||||
op.setGroup('org_acme');
|
||||
```
|
||||
|
||||
## Per-event group override
|
||||
|
||||
You can attach group IDs to a specific event without affecting the SDK's persistent group state:
|
||||
|
||||
```typescript
|
||||
op.track('file_shared', {
|
||||
filename: 'q4-report.pdf',
|
||||
groups: ['org_acme', 'org_partner'], // Only applies to this event
|
||||
});
|
||||
```
|
||||
|
||||
Groups passed in `track()` are **merged** with any groups already set on the SDK instance.
|
||||
|
||||
## Clearing groups on logout
|
||||
|
||||
`clear()` resets the profile, device, session, and all groups. Always call it on logout.
|
||||
|
||||
```typescript
|
||||
function handleLogout() {
|
||||
op.clear();
|
||||
// redirect to login...
|
||||
}
|
||||
```
|
||||
|
||||
## Common patterns
|
||||
|
||||
### B2B SaaS — company accounts
|
||||
|
||||
```typescript
|
||||
// On login
|
||||
op.identify({ profileId: user.id, email: user.email });
|
||||
op.upsertGroup({
|
||||
id: user.organizationId,
|
||||
type: 'company',
|
||||
name: user.organizationName,
|
||||
properties: { plan: user.plan, mrr: user.mrr },
|
||||
});
|
||||
op.setGroup(user.organizationId);
|
||||
```
|
||||
|
||||
### Multi-tenant — workspaces
|
||||
|
||||
```typescript
|
||||
// When user switches workspace
|
||||
op.upsertGroup({
|
||||
id: workspace.id,
|
||||
type: 'workspace',
|
||||
name: workspace.name,
|
||||
});
|
||||
op.setGroup(workspace.id);
|
||||
```
|
||||
|
||||
### Teams within a company
|
||||
|
||||
```typescript
|
||||
// User belongs to a company and a specific team
|
||||
op.setGroups([user.organizationId, user.teamId]);
|
||||
```
|
||||
|
||||
## API reference
|
||||
|
||||
### `upsertGroup(payload)`
|
||||
|
||||
Creates the group if it doesn't exist, or merges properties into the existing group.
|
||||
|
||||
```typescript
|
||||
op.upsertGroup({
|
||||
id: string; // Required
|
||||
type: string; // Required
|
||||
name: string; // Required
|
||||
properties?: Record<string, unknown>;
|
||||
});
|
||||
```
|
||||
|
||||
### `setGroup(groupId)`
|
||||
|
||||
Adds a single group ID to the SDK's internal group list and sends an `assign_group` event to link the current profile to that group.
|
||||
|
||||
```typescript
|
||||
op.setGroup('org_acme');
|
||||
```
|
||||
|
||||
### `setGroups(groupIds)`
|
||||
|
||||
Same as `setGroup()` but for multiple group IDs at once.
|
||||
|
||||
```typescript
|
||||
op.setGroups(['org_acme', 'team_engineering']);
|
||||
```
|
||||
|
||||
## What to avoid
|
||||
|
||||
- **Calling `upsertGroup()` on every event or page view** — call it on login or when group properties actually change. For static group management, use the dashboard instead.
|
||||
- **Not calling `setGroup()` after `identify()`** — without it, events won't be tagged with the group and you won't see group-level data in the dashboard.
|
||||
- **Forgetting `clear()` on logout** — groups persist on the SDK instance, so a new user logging in on the same session could inherit the previous user's groups.
|
||||
- **Using `upsertGroup()` to link a user to a group** — `upsertGroup()` manages the group entity only. Use `setGroup()` to link a user profile to it.
|
||||
@@ -3,6 +3,7 @@
|
||||
"install-openpanel",
|
||||
"track-events",
|
||||
"identify-users",
|
||||
"groups",
|
||||
"revenue-tracking"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user