Skip to content

Commit

Permalink
feat(core): add user (#169)
Browse files Browse the repository at this point in the history
* feat(core): add user

* feat(core): add user segments and flag rule sets to schema
fix(web): update feature flag component to use link and improve data handling

* feat(web): enhance flag detail page with history and environment details

* feat(web): add prebuild script and update lint ignore comments in RuleSetEditor and error page

* chore: update dependencies and improve accessibility in various components

* feat(web): add user segments settings and display component

* feat(core): add updateUserProperties method and improve user property notification

* feat(docs): add user segments and operators documentation

* fix: clean up imports and formatting in FeatureFlag and AddFeatureFlagModal components

* feat(core): enhance LocalData type to include ruleSet for flags and remoteConfig

* feat(tests): set currentEnvironment to "development" in node tests

* feat(tests): set currentEnvironment to "development" in Koa and Express tests

* fix: clean up formatting in package.json and koa.test.ts

* feat(tests): add additional flags and remoteConfig to mock data

* feat(api): add v2 project data route and update fetch URL

* fix(flags): change findUnique to findFirst for feature flag retrieval and enhance user validation

* chore: update package versions and dependencies across multiple packages

* fix: standardize "files" field formatting in package.json across multiple packages

* feat(tests): update API version in mock handlers to v2 across multiple packages

* test: skip A/B tests and feature flag assertions in node tests

* fix(tests): skip feature flag test and enable A/B test in express tests
  • Loading branch information
cstrnt authored Feb 18, 2025
1 parent f018ef1 commit dc0a57c
Show file tree
Hide file tree
Showing 95 changed files with 3,729 additions and 651 deletions.
1 change: 1 addition & 0 deletions apps/docs/pages/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"remote-config": "Remote Configuration",
"environments": "Environments",
"config": "Configuration as Code",
"user-segments": "User Segments",
"a-b-testing": "A/B Tests",
"-- More --": {
"type": "separator",
Expand Down
57 changes: 57 additions & 0 deletions apps/docs/pages/feature-flags.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,62 @@ This is useful for testing new features, or for hiding features that are not yet
A feature flag is always toggled for a certain [Environment](/environments) which allows you to test features in different environments.
For example, you can have a feature flag that is enabled in the `development` environment, but disabled in the `production` environment.

## Basic Usage

When a feature flag is toggled for an environment, every user will see the same value for that feature flag.
If you want to test a feature for a specific user, you can use [A/B Tests](/a-b-testing).

## User Segments & Conditional Rules

Feature flags can be made more powerful with user segments and conditional rules. This allows you to dynamically control flag values based on user properties.

### User Properties

First, define the properties you want to use for targeting in your user segment schema. Common properties include:

- User role/type (string)
- Subscription status (boolean)
- Usage metrics (number)
- Location (string)
- Custom attributes specific to your app

### Conditional Rules

Once you have user properties defined, you can create conditional rules for your feature flags:

- **Single Rules**: Simple conditions like "role equals admin" or "subscriptionActive equals true"
- **Rule Groups**: Complex logic combining multiple conditions with AND/OR operators
- **Operators**: Support for various comparisons:
- Strings: equals, not equals, contains, not contains, starts with, ends with, regex
- Numbers: equals, not equals, greater than, less than, greater/less than or equal
- Booleans: equals

### Example Usage

Here's how it works:

1. Define your user properties in the Abby dashboard
2. Create rules for your feature flags using these properties
3. Update user properties in your application code:

```typescript
// Update user properties
abby.updateUserProperties({
role: "admin",
subscriptionActive: true,
usageCount: 100,
});
```

When evaluating feature flags, Abby will:

1. Check the environment value first
2. Evaluate any conditional rules based on user properties
3. Return the appropriate flag value

This allows for powerful targeting scenarios like:

- Enable features only for admin users
- Show premium features to paid subscribers
- Roll out features gradually based on usage metrics
- Target specific user segments with custom rules
3 changes: 2 additions & 1 deletion apps/docs/pages/reference/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"svelte": "Svelte",
"angular": "Angular",
"http": "HTTP API",
"cli": "CLI"
"cli": "CLI",
"operators": "Operators"
}
79 changes: 70 additions & 9 deletions apps/docs/pages/reference/nextjs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@

The `createAbby` function takes an object as a parameter. The object can contain the following properties:

| Name | Type | Required | Description | details |
| ------------------ | -------- | :------: | ------------------------------------------------------------------ | --------------------- |
| projectId | `string` || The ID of your project in Abby | - |
| apiUrl | `string` | | The URL of the Abby API. Defaults to the hosted version | - |
| currentEnvironment | `string` || The current environment of your application | [link](/environments) |
| tests | `object` | | An object containing your defined A/B Tests | - |
| flags | `array` | | An array containing your defined Feature Flags | - |
| remoteConfig | `object` | | An object containing the name of your remote config and their type | - |
| settings | `object` | | An object with additional settings for Abby | - |
| Name | Type | Required | Description | details |
| ------------------ | -------- | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| projectId | `string` || The ID of your project in Abby | - |
| apiUrl | `string` | | The URL of the Abby API. Defaults to the hosted version | - |
| currentEnvironment | `string` || The current environment of your application | [link](/environments) |
| tests | `object` | | An object containing your defined A/B Tests | - |
| flags | `array` | | An array containing your defined Feature Flags | - |
| remoteConfig | `object` | | An object containing the name of your remote config and their type | - |
| settings | `object` | | An object with additional settings for Abby | - |
| user | `object` | | Optional configuration for user properties that can be used for targeting feature flags. This enables conditional rules based on user attributes. | - |

#### tests

Expand Down Expand Up @@ -80,6 +81,25 @@ The settings property is an object containing additional settings for Abby. The
- `remoteConfig.devOverrides`: An object containing the values of Remote Configuration variables in development mode.
The keys of the object represent the names of the flags. The value must be assignable to the type of the variable.

#### user

Optional configuration for user properties that can be used for targeting feature flags. This enables conditional rules based on user attributes. The configuration uses validation helpers from `@tryabby/core`:

```typescript
import * as validation from "@tryabby/core/validation";

const config = createAbby({
// ... other config
user: {
role: validation.string(),
isSubscribed: validation.boolean(),
purchaseCount: validation.number(),
// Optional fields
country: validation.optional(validation.string()),
},
});
```

### Return Values

#### useAbby
Expand Down Expand Up @@ -330,3 +350,44 @@ export default function SomePage() {
);
}
```

#### updateUserProperties

Updates the user properties used for evaluating conditional rules. Properties are validated against their defined types and will throw errors if invalid.

The function:

- Validates each property against its defined type
- Only updates if validation passes
- Throws descriptive errors for invalid values
- Allows partial updates (only updating some properties)
- Handles optional properties correctly

##### Example

```typescript
// Valid updates
abby.updateUserProperties({
role: "admin", // string
isSubscribed: true, // boolean
purchaseCount: 5, // number
country: "US", // optional string
});

// Invalid updates will throw
abby.updateUserProperties({
role: 123, // Error: Expected string but got number
isSubscribed: "true", // Error: Expected boolean but got string
purchaseCount: "5", // Error: Expected number but got string
});

// Partial updates are fine
abby.updateUserProperties({
role: "user", // Only updates role
});

// Optional fields can be undefined
abby.updateUserProperties({
country: undefined, // OK for optional fields
});
```
100 changes: 100 additions & 0 deletions apps/docs/pages/reference/operators.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Rule Operators

When configuring conditional rules for feature flags, you can use different operators based on the property type.

## String Operators

| Operator | Description | Example |
| ----------- | --------------------------------- | ------------------------------- |
| eq | Exact string match | `role eq "admin"` |
| neq | Not equal | `role neq "guest"` |
| contains | String contains substring | `email contains "@company.com"` |
| notContains | String does not contain substring | `plan notContains "free"` |
| startsWith | String starts with prefix | `country startsWith "US"` |
| endsWith | String ends with suffix | `domain endsWith ".edu"` |
| regex | String matches regular expression | `email regex "^[a-z]+@.*$"` |

## Number Operators

| Operator | Description | Example |
| -------- | ------------------------ | -------------------------- |
| eq | Equal to | `age eq 21` |
| neq | Not equal to | `loginCount neq 0` |
| gt | Greater than | `purchaseCount gt 5` |
| gte | Greater than or equal to | `subscriptionMonths gte 3` |
| lt | Less than | `errorCount lt 10` |
| lte | Less than or equal to | `failedAttempts lte 3` |

## Boolean Operators

| Operator | Description | Example |
| -------- | ----------- | ---------------------- |
| eq | Equal to | `isSubscribed eq true` |

## Rule Groups

Rules can be combined into groups using logical operators:

| Operator | Description | Example |
| -------- | -------------------------------------- | ----------------------------------------------- |
| and | All conditions in the group must match | `role eq "admin" AND subscriptionDays gt 30` |
| or | Any condition in the group can match | `isTeamMember eq true OR hasBetaAccess eq true` |

### Example Rule Sets

```typescript
// Single rule
{
propertyName: "role",
propertyType: "string",
operator: "eq",
value: "admin",
thenValue: true
}

// Rule group with AND logic
{
operator: "and",
rules: [
{
propertyName: "subscriptionTier",
propertyType: "string",
operator: "eq",
value: "premium"
},
{
propertyName: "usageCount",
propertyType: "number",
operator: "gt",
value: 100
}
],
thenValue: true
}

// Rule group with OR logic
{
operator: "or",
rules: [
{
propertyName: "isAdmin",
propertyType: "boolean",
operator: "eq",
value: true
},
{
propertyName: "email",
propertyType: "string",
operator: "endsWith",
value: "@company.com"
}
],
thenValue: true
}
```

When evaluating rules:

1. Rules are evaluated in order
2. The first matching rule's `thenValue` is used
3. If no rules match, the environment's default value is used
Loading

0 comments on commit dc0a57c

Please sign in to comment.