@picked/client 0.0.70-next.6 → 0.0.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{api-ahdSBQ3Y.js → api-CPy9G49C.js} +1 -1
- package/dist/api-Ce6xYI--.cjs +1 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
- package/dist/cjs/server/index.js +1 -1
- package/dist/docs/actions.mdx +38 -18
- package/dist/docs/api/authentication.md +75 -48
- package/dist/docs/api/authorization-and-scopes.md +63 -47
- package/dist/docs/api/error-handling.md +70 -54
- package/dist/docs/api/index.md +41 -22
- package/dist/docs/api/pagination.md +30 -20
- package/dist/docs/api-keys.mdx +11 -6
- package/dist/docs/chrome-extension.md +22 -5
- package/dist/docs/conditions.mdx +32 -19
- package/dist/docs/content/asset-library.mdx +65 -34
- package/dist/docs/content/content-entries.mdx +8 -2
- package/dist/docs/content/layouts.mdx +45 -29
- package/dist/docs/content/pages.mdx +29 -14
- package/dist/docs/content/sections.mdx +19 -8
- package/dist/docs/delivery-api/experience-content.md +10 -5
- package/dist/docs/delivery-api/index.md +19 -12
- package/dist/docs/delivery-api/preview-caching-crawling.md +27 -14
- package/dist/docs/delivery-api/project-config.md +6 -4
- package/dist/docs/experiences.mdx +26 -10
- package/dist/docs/features.mdx +66 -25
- package/dist/docs/fields.mdx +42 -27
- package/dist/docs/flow-builder/building-flows.md +38 -20
- package/dist/docs/flow-builder/condition-builder.md +32 -21
- package/dist/docs/flow-builder/keyboard-shortcuts.md +18 -18
- package/dist/docs/flow-builder/nodes.md +26 -13
- package/dist/docs/flow-builder/overview.mdx +28 -15
- package/dist/docs/gtm-template.md +35 -26
- package/dist/docs/guides/build-a-personalized-page.mdx +36 -18
- package/dist/docs/image-optimisation-service.md +33 -17
- package/dist/docs/index.md +28 -17
- package/dist/docs/mcp.md +46 -38
- package/dist/docs/pickedql/data-sources.md +65 -43
- package/dist/docs/pickedql/dimensions.md +27 -12
- package/dist/docs/pickedql/examples.md +38 -26
- package/dist/docs/pickedql/filtering.md +10 -6
- package/dist/docs/pickedql/index.md +32 -14
- package/dist/docs/pickedql/schema.md +85 -76
- package/dist/docs/pickedql/virtual-functions.md +62 -36
- package/dist/docs/projects.mdx +14 -7
- package/dist/docs/reports.mdx +5 -3
- package/dist/docs/sdk/actions.md +33 -17
- package/dist/docs/sdk/conditions.md +8 -5
- package/dist/docs/sdk/content-security-policy.md +5 -4
- package/dist/docs/sdk/experiences.md +29 -11
- package/dist/docs/sdk/feature-allocation.md +79 -47
- package/dist/docs/sdk/features.md +130 -76
- package/dist/docs/sdk/fields.md +66 -42
- package/dist/docs/sdk/index.md +22 -13
- package/dist/docs/sdk/plugins/create-experience.md +18 -15
- package/dist/docs/sdk/plugins/dynamic-experiences.md +42 -16
- package/dist/docs/sdk/predicates.md +53 -37
- package/dist/docs/sdk/ssr/index.md +25 -8
- package/dist/docs/sdk/ssr/rendering-and-hydration.md +37 -16
- package/dist/docs/sdk/ssr/serialize.md +38 -20
- package/dist/docs/sdk/ssr/slots-and-placements.md +28 -10
- package/dist/docs/sdk/steps.md +100 -55
- package/dist/docs/steps.mdx +20 -11
- package/dist/docs/team-management.md +17 -11
- package/dist/docs/vite-plugin.md +26 -19
- package/dist/docs/workspace-versioning.mdx +14 -8
- package/dist/docs/workspaces.mdx +55 -24
- package/dist/esm/index.js +1 -1
- package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
- package/dist/esm/server/index.js +1 -1
- package/dist/{plugin-BxMR7fOV.js → plugin-ChPkTj__.js} +1 -1
- package/dist/{plugin-CASdGtTX.cjs → plugin-DHn11gn6.cjs} +1 -1
- package/package.json +1 -1
- package/dist/api-CoUkIxIr.cjs +0 -1
|
@@ -4,87 +4,108 @@ layout: docs
|
|
|
4
4
|
|
|
5
5
|
# Allocation and exposure
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
A read of the bucket of a feature flag is what allocates a visitor to that feature flag. This
|
|
8
|
+
page covers when that happens and how to watch it. It also covers how to be sure that
|
|
9
|
+
Picked allocates a visitor before you report on that visitor.
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## A registration is not a decision
|
|
10
12
|
|
|
11
|
-
The SDK loads every feature flag in your project at startup, but it
|
|
13
|
+
The SDK loads every feature flag in your project at startup, but it allocates no visitor
|
|
14
|
+
then. A feature flag stays undecided until something reads its bucket. The first read runs the
|
|
15
|
+
`decide` function, stores the result, and reports the decision.
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
You therefore choose when Picked commits a visitor to a feature flag. You choose it by the
|
|
18
|
+
point at which you read the feature flag.
|
|
14
19
|
|
|
15
|
-
##
|
|
20
|
+
## A read of a bucket records an exposure
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
The first read is the decision, so a read of a bucket is the moment when the visitor
|
|
23
|
+
enters the test. If you read every feature flag at startup, every visitor counts as exposed
|
|
24
|
+
to every test. This includes a test that the visitor never reached.
|
|
18
25
|
|
|
19
26
|
:::warning
|
|
20
|
-
|
|
27
|
+
Do not loop over every feature flag at startup to read its bucket. That commits every visitor
|
|
28
|
+
to every test immediately. You then have no way to wait for the data that a decision
|
|
29
|
+
needs.
|
|
21
30
|
:::
|
|
22
31
|
|
|
23
|
-
Read a feature flag at the point the decision matters
|
|
32
|
+
Read a feature flag at the point where the decision matters. That is after the cart loads,
|
|
33
|
+
after the quote returns, or after you know who the visitor is.
|
|
24
34
|
|
|
25
|
-
##
|
|
35
|
+
## Check a feature flag without a decision
|
|
26
36
|
|
|
27
|
-
`client.features.listStore` holds every registered feature flag. Each entry reports whether
|
|
37
|
+
`client.features.listStore` holds every registered feature flag. Each entry reports whether
|
|
38
|
+
the feature flag is decided yet, so you can check a feature flag and commit nobody:
|
|
28
39
|
|
|
29
40
|
```ts
|
|
30
|
-
for (const flag of client.features.listStore.getState()) {
|
|
31
|
-
console.log(flag.id, flag.isResolved)
|
|
41
|
+
for (const feature flag of client.features.listStore.getState()) {
|
|
42
|
+
console.log(feature flag.id, feature flag.isResolved)
|
|
32
43
|
}
|
|
33
44
|
```
|
|
34
45
|
|
|
35
|
-
`getBucket()` behaves differently
|
|
46
|
+
`getBucket()` behaves differently for each state:
|
|
36
47
|
|
|
37
|
-
- **Resolved** — returns `{ id, bucket }`
|
|
38
|
-
- **Not resolved** — returns a promise.
|
|
48
|
+
- **Resolved** — it returns `{ id, bucket }` at once. The read changes nothing.
|
|
49
|
+
- **Not resolved** — it returns a promise. An `await` runs `decide` and commits the
|
|
50
|
+
visitor.
|
|
39
51
|
|
|
40
|
-
`await` works for both, so you can treat them
|
|
52
|
+
`await` works for both states, so you can treat them the same way when you do intend
|
|
53
|
+
to decide.
|
|
41
54
|
|
|
42
55
|
## When `feature_decide` fires
|
|
43
56
|
|
|
44
|
-
|
|
57
|
+
The SDK sends a `feature_decide` event the first time that it decides a feature flag for a
|
|
58
|
+
visitor, and only then.
|
|
45
59
|
|
|
46
60
|
:::warning
|
|
47
|
-
On later page
|
|
61
|
+
On a later page load the SDK restores the decision from browser storage, and it
|
|
62
|
+
sends **no event**. A visitor who returns produces no `feature_decide` event at all,
|
|
63
|
+
although Picked still allocates that visitor.
|
|
48
64
|
|
|
49
|
-
An integration
|
|
65
|
+
An integration that reads only `feature_decide` therefore reports nothing for a
|
|
66
|
+
visitor who returns.
|
|
50
67
|
:::
|
|
51
68
|
|
|
52
|
-
Use the
|
|
69
|
+
Use the correct source for your question:
|
|
53
70
|
|
|
54
71
|
| Question | Use |
|
|
55
72
|
| --- | --- |
|
|
56
|
-
| What is this visitor allocated to
|
|
57
|
-
|
|
|
58
|
-
|
|
|
73
|
+
| What is this visitor allocated to now? | `client.features.listStore` |
|
|
74
|
+
| Did the SDK make a new decision? | `feature_decide` on `client.eventBus` |
|
|
75
|
+
| Which decisions happened before I subscribed? | `client.events` |
|
|
59
76
|
|
|
60
|
-
##
|
|
77
|
+
## Watch for decisions
|
|
61
78
|
|
|
62
|
-
`listStore` updates
|
|
79
|
+
`listStore` updates each time that a feature flag resolves. It does this whether the SDK
|
|
80
|
+
restored the feature flag from storage or decided it now. A watch on `listStore` therefore
|
|
81
|
+
always gives you the current state:
|
|
63
82
|
|
|
64
83
|
```ts
|
|
65
84
|
const buckets = new Map<string, string | boolean>()
|
|
66
85
|
|
|
67
|
-
client.features.listStore.watch((flags) => {
|
|
68
|
-
for (const flag of flags) {
|
|
69
|
-
if (!flag.isResolved) continue
|
|
70
|
-
buckets.set(flag.id, flag.getBucket().bucket)
|
|
86
|
+
client.features.listStore.watch((feature flags) => {
|
|
87
|
+
for (const feature flag of feature flags) {
|
|
88
|
+
if (!feature flag.isResolved) continue
|
|
89
|
+
buckets.set(feature flag.id, feature flag.getBucket().bucket)
|
|
71
90
|
}
|
|
72
91
|
})
|
|
73
92
|
```
|
|
74
93
|
|
|
75
|
-
Use `feature_decide` when you need to know
|
|
94
|
+
Use `feature_decide` when you need to know that the SDK *made* a decision, and not
|
|
95
|
+
what the current value is. An example is your own exposure event:
|
|
76
96
|
|
|
77
97
|
```ts
|
|
78
98
|
client.eventBus.watch((event) => {
|
|
79
99
|
if (event.type !== 'feature_decide') return
|
|
80
100
|
analytics.track('Experiment exposure', {
|
|
81
|
-
flag: event.feature.id,
|
|
101
|
+
feature flag: event.feature.id,
|
|
82
102
|
bucket: event.feature.bucket,
|
|
83
103
|
})
|
|
84
104
|
})
|
|
85
105
|
```
|
|
86
106
|
|
|
87
|
-
|
|
107
|
+
An earlier event is still available when you subscribe after startup. `client.events`
|
|
108
|
+
holds every event since the client started:
|
|
88
109
|
|
|
89
110
|
```ts
|
|
90
111
|
const earlier = client.events
|
|
@@ -92,38 +113,49 @@ const earlier = client.events
|
|
|
92
113
|
.filter((entry) => entry.payload.type === 'feature_decide')
|
|
93
114
|
```
|
|
94
115
|
|
|
95
|
-
##
|
|
116
|
+
## Decide before you report
|
|
96
117
|
|
|
97
|
-
To
|
|
118
|
+
To be sure that the SDK decides a feature flag before a later event fires, `await` the feature flag
|
|
119
|
+
first:
|
|
98
120
|
|
|
99
121
|
```ts
|
|
100
|
-
const flag = client.features.getById('quote-experiment').getState()
|
|
122
|
+
const feature flag = client.features.getById('quote-experiment').getState()
|
|
101
123
|
|
|
102
|
-
if (flag) {
|
|
103
|
-
const { bucket } = await flag.getBucket()
|
|
124
|
+
if (feature flag) {
|
|
125
|
+
const { bucket } = await feature flag.getBucket()
|
|
104
126
|
trackViewQuote({ experiment: bucket })
|
|
105
127
|
}
|
|
106
128
|
```
|
|
107
129
|
|
|
108
|
-
|
|
130
|
+
An `await` on `getBucket()` resolves the feature flag when it is undecided, and returns the
|
|
131
|
+
stored value when it is decided. In both cases the bucket is known before the next
|
|
132
|
+
line runs.
|
|
109
133
|
|
|
110
|
-
## What Picked
|
|
134
|
+
## What Picked reports for you
|
|
111
135
|
|
|
112
|
-
|
|
136
|
+
Read this before you build a report of your own.
|
|
113
137
|
|
|
114
|
-
Every Picked analytics event carries a `feature_list` context
|
|
138
|
+
Every Picked analytics event carries a `feature_list` context. The context describes
|
|
139
|
+
each feature flag that this visitor resolved:
|
|
115
140
|
|
|
116
141
|
| Field | Description |
|
|
117
142
|
| --- | --- |
|
|
118
|
-
| `ab_feature` |
|
|
119
|
-
| `ab_name` |
|
|
120
|
-
| `ab_key` |
|
|
143
|
+
| `ab_feature` | The feature flag ID |
|
|
144
|
+
| `ab_name` | The feature flag name |
|
|
145
|
+
| `ab_key` | The version key, such as `v3` |
|
|
121
146
|
| `ab_bucket` | The resolved bucket |
|
|
122
147
|
|
|
123
|
-
Only resolved feature
|
|
148
|
+
Only a resolved feature flag appears there. The context therefore shows what the visitor was
|
|
149
|
+
exposed to at the time when the event fired.
|
|
124
150
|
|
|
125
|
-
|
|
151
|
+
The [Sentry plugin](https://www.npmjs.com/package/@picked/sentry) is optional. When
|
|
152
|
+
it is installed, each decision also tags the Sentry scope as
|
|
153
|
+
`picked.feature.<feature flag-id>`, with the bucket as the value. You can then filter your
|
|
154
|
+
errors by the variant.
|
|
126
155
|
|
|
127
156
|
## Preview mode
|
|
128
157
|
|
|
129
|
-
In [preview mode](/workspaces#preview)
|
|
158
|
+
In [preview mode](/workspaces#preview) the SDK stores a decision in session storage
|
|
159
|
+
and not in local storage. The decision does not stay after you close the tab, and
|
|
160
|
+
Picked can allocate a preview visitor differently from a production visitor. Treat a
|
|
161
|
+
preview allocation as disposable.
|
|
@@ -4,29 +4,40 @@ layout: docs
|
|
|
4
4
|
|
|
5
5
|
# Feature flags
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
A feature flag lets you run an A/B test and roll a change out in stages. Control a
|
|
8
|
+
feature flag from the dashboard, or define it in SDK code when you need your own decision
|
|
9
|
+
rules.
|
|
8
10
|
|
|
9
11
|
## Where feature flags come from
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
You control most feature flags from the dashboard. The
|
|
14
|
+
[dynamic experiences plugin](/sdk/plugins/dynamic-experiences) reads the options of
|
|
15
|
+
your project at runtime and registers every feature flag for you. You declare no feature flag in
|
|
16
|
+
code.
|
|
12
17
|
|
|
13
|
-
For a
|
|
18
|
+
For a feature flag that you control from the dashboard, the SDK makes the definition:
|
|
14
19
|
|
|
15
20
|
| Field | Value |
|
|
16
21
|
| --- | --- |
|
|
17
|
-
| `id` | The feature flag
|
|
18
|
-
| `key` | `v{version}
|
|
19
|
-
| `name` | The feature flag
|
|
22
|
+
| `id` | The dashboard key of the feature flag |
|
|
23
|
+
| `key` | `v{version}`, for example `v3` |
|
|
24
|
+
| `name` | The dashboard name of the feature flag |
|
|
20
25
|
| `enabled` | The dashboard **Enabled** toggle |
|
|
21
|
-
| `weight` | The dashboard **Weight**, 0
|
|
26
|
+
| `weight` | The dashboard **Weight**, 0 to 100 |
|
|
22
27
|
| `storage` | `local`, or `session` in [preview mode](/sdk/feature-allocation#preview-mode) |
|
|
23
|
-
| `decide` |
|
|
28
|
+
| `decide` | Made by the SDK. It compares the stable value of the visitor for this feature flag against `weight`. |
|
|
24
29
|
|
|
25
|
-
The
|
|
30
|
+
The `decide` function that the SDK makes derives from the visitor ID, the ID of the
|
|
31
|
+
feature flag, and the weight. A feature flag that you control from the dashboard
|
|
32
|
+
therefore cannot use the data of your application. Define the feature flag in SDK code
|
|
33
|
+
when you need that.
|
|
26
34
|
|
|
27
|
-
##
|
|
35
|
+
## Define a feature flag in SDK code
|
|
28
36
|
|
|
29
|
-
Define feature flags when
|
|
37
|
+
Define your feature flags when you create the Picked client. Pass a `features` object. Use
|
|
38
|
+
this when a decision needs data that only your application holds. Each feature flag needs a
|
|
39
|
+
unique identifier, a version key, a name that a person can read, and a `decide`
|
|
40
|
+
function:
|
|
30
41
|
|
|
31
42
|
```ts
|
|
32
43
|
import { createClient } from '@picked/client'
|
|
@@ -35,20 +46,20 @@ const client = createClient({
|
|
|
35
46
|
projectId: 'your-project-id',
|
|
36
47
|
features: {
|
|
37
48
|
'summer-sale': {
|
|
38
|
-
key: 'summer-sale-v1', //
|
|
39
|
-
name: 'Summer Sale Experiment', //
|
|
49
|
+
key: 'summer-sale-v1', // The version key. Change it to invalidate the cache.
|
|
50
|
+
name: 'Summer Sale Experiment', // A name for analytics that a person can read.
|
|
40
51
|
decide: async () => {
|
|
41
|
-
//
|
|
52
|
+
// Put your decision rules here.
|
|
42
53
|
const isEligible = await checkUserEligibility()
|
|
43
|
-
return isEligible // Returns boolean
|
|
54
|
+
return isEligible // Returns a boolean.
|
|
44
55
|
},
|
|
45
|
-
storage: 'local', // Optional: 'local'
|
|
56
|
+
storage: 'local', // Optional: 'local' is the default, or 'session'.
|
|
46
57
|
},
|
|
47
58
|
'homepage-variant': {
|
|
48
59
|
key: 'homepage-test-v2',
|
|
49
60
|
name: 'Homepage A/B Test',
|
|
50
61
|
decide: async () => {
|
|
51
|
-
// Return string for
|
|
62
|
+
// Return a string for a test of more than two variants.
|
|
52
63
|
return Math.random() < 0.5 ? 'variant-a' : 'variant-b'
|
|
53
64
|
},
|
|
54
65
|
},
|
|
@@ -56,7 +67,8 @@ const client = createClient({
|
|
|
56
67
|
})
|
|
57
68
|
```
|
|
58
69
|
|
|
59
|
-
|
|
70
|
+
A feature flag can also be a factory function, which starts the feature flag only when it is
|
|
71
|
+
first needed:
|
|
60
72
|
|
|
61
73
|
```ts
|
|
62
74
|
features: {
|
|
@@ -70,25 +82,33 @@ features: {
|
|
|
70
82
|
|
|
71
83
|
## How feature flags work
|
|
72
84
|
|
|
73
|
-
|
|
85
|
+
After you register a feature flag, the SDK follows this order:
|
|
74
86
|
|
|
75
|
-
1. **Registration** —
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
87
|
+
1. **Registration** — the client registers each feature flag at startup. A registration
|
|
88
|
+
allocates nobody.
|
|
89
|
+
2. **Cache check** — the SDK looks in browser storage for a decision from an earlier
|
|
90
|
+
visit. The cache key holds the feature flag `id` and `key`. When an entry is there, the
|
|
91
|
+
feature flag starts in a resolved state. This is why Picked allocates a visitor who
|
|
92
|
+
returns before anything reads the feature flag.
|
|
93
|
+
3. **Lazy evaluation** — with no cached decision, `decide` runs the first time that
|
|
94
|
+
something reads the bucket.
|
|
95
|
+
4. **Storage** — the SDK writes the resolved bucket to browser storage.
|
|
96
|
+
5. **Event emission** — the SDK sends a `feature_decide` event, but only for a
|
|
97
|
+
decision that it makes during this page view. A decision that step 2 restores
|
|
98
|
+
sends nothing.
|
|
80
99
|
|
|
81
|
-
See [Allocation and exposure](/sdk/feature-allocation) for what this means when
|
|
100
|
+
See [Allocation and exposure](/sdk/feature-allocation) for what this means when you
|
|
101
|
+
track an exposure.
|
|
82
102
|
|
|
83
|
-
## URL
|
|
103
|
+
## Overrides in the URL
|
|
84
104
|
|
|
85
|
-
You can override feature flag
|
|
105
|
+
You can override a feature flag decision from the URL search parameters, for a test:
|
|
86
106
|
|
|
87
107
|
```
|
|
88
108
|
https://example.com?picked-feature=my-feature:true
|
|
89
109
|
```
|
|
90
110
|
|
|
91
|
-
|
|
111
|
+
You can give more than one override value:
|
|
92
112
|
|
|
93
113
|
```
|
|
94
114
|
https://example.com?picked-feature=my-feature:true&picked-feature=homepage-variant:variant-a
|
|
@@ -96,11 +116,15 @@ https://example.com?picked-feature=my-feature:true&picked-feature=homepage-varia
|
|
|
96
116
|
|
|
97
117
|
The format is `?picked-feature=<feature-id>:<bucket-value>`.
|
|
98
118
|
|
|
99
|
-
This is useful for QA
|
|
119
|
+
This is useful for QA, because you can test one bucket value and change no code. The
|
|
120
|
+
SDK keeps the override value in session storage. The browser clears it when you close
|
|
121
|
+
the tab.
|
|
100
122
|
|
|
101
|
-
##
|
|
123
|
+
## Read a feature flag
|
|
102
124
|
|
|
103
|
-
|
|
125
|
+
After you define your feature flags, your application code can read the resolved
|
|
126
|
+
bucket value of each one. The SDK gives you a hook for each framework, and an event
|
|
127
|
+
bus that works with any framework.
|
|
104
128
|
|
|
105
129
|
:::code-group
|
|
106
130
|
|
|
@@ -108,7 +132,7 @@ Once feature flags are defined, you can read their resolved bucket values in you
|
|
|
108
132
|
import { createClient } from '@picked/client'
|
|
109
133
|
|
|
110
134
|
const client = createClient({
|
|
111
|
-
/*
|
|
135
|
+
/* Put your client options here. */
|
|
112
136
|
})
|
|
113
137
|
|
|
114
138
|
client.eventBus.watch((event) => {
|
|
@@ -122,7 +146,7 @@ client.eventBus.watch((event) => {
|
|
|
122
146
|
<script lang="ts">
|
|
123
147
|
import { getFeatureResult } from '@picked/svelte/hooks'
|
|
124
148
|
|
|
125
|
-
// A store
|
|
149
|
+
// A store that holds { id, bucket }, or undefined until the feature flag resolves.
|
|
126
150
|
const result = getFeatureResult('my-feature')
|
|
127
151
|
</script>
|
|
128
152
|
|
|
@@ -137,7 +161,7 @@ client.eventBus.watch((event) => {
|
|
|
137
161
|
import { useFeatureResult } from '@picked/react/hooks'
|
|
138
162
|
|
|
139
163
|
function MyComponent() {
|
|
140
|
-
// { id, bucket }, or undefined until the flag resolves
|
|
164
|
+
// { id, bucket }, or undefined until the feature flag resolves.
|
|
141
165
|
const result = useFeatureResult('my-feature')
|
|
142
166
|
|
|
143
167
|
return (
|
|
@@ -151,41 +175,46 @@ function MyComponent() {
|
|
|
151
175
|
|
|
152
176
|
:::
|
|
153
177
|
|
|
154
|
-
###
|
|
178
|
+
### Choose an accessor
|
|
155
179
|
|
|
156
|
-
|
|
180
|
+
Each accessor returns a different value. They also differ in whether a read resolves
|
|
181
|
+
the feature flag. A read that resolves the feature flag commits the visitor to a bucket and reports
|
|
182
|
+
the decision. See [Allocation and exposure](/sdk/feature-allocation).
|
|
157
183
|
|
|
158
|
-
| API | Returns |
|
|
184
|
+
| API | Returns | Does a read resolve the feature flag? |
|
|
159
185
|
| --- | --- | --- |
|
|
160
|
-
| `useFeature` / `getFeature` | `() => Promise<bucket>` | Only when you call the
|
|
161
|
-
| `useFeatureResult` / `getFeatureResult` | `{ id, bucket }`, `undefined` until
|
|
162
|
-
| `useFeatureEnabled` / `getFeatureEnabled` | `boolean \| undefined
|
|
163
|
-
| `client.features.listStore` | Every registered feature flag, each with an `isResolved`
|
|
164
|
-
| `client.features.getById(id)` | A store
|
|
186
|
+
| `useFeature` / `getFeature` | `() => Promise<bucket>` | Only when you call the function that it returns |
|
|
187
|
+
| `useFeatureResult` / `getFeatureResult` | `{ id, bucket }`, and `undefined` until it resolves | Yes, as soon as the component renders |
|
|
188
|
+
| `useFeatureEnabled` / `getFeatureEnabled` | `boolean \| undefined`, the dashboard **Enabled** toggle | No |
|
|
189
|
+
| `client.features.listStore` | Every registered feature flag, each with an `isResolved` feature flag | No |
|
|
190
|
+
| `client.features.getById(id)` | A store that holds one feature flag | No |
|
|
165
191
|
|
|
166
192
|
:::warning
|
|
167
|
-
`useFeatureResult` and `getFeatureResult` resolve the feature flag as soon as the component
|
|
193
|
+
`useFeatureResult` and `getFeatureResult` resolve the feature flag as soon as the component
|
|
194
|
+
renders. They commit the visitor to a bucket at that moment.
|
|
168
195
|
|
|
169
|
-
|
|
196
|
+
Some decisions must wait for data that you do not have yet, such as a cart, a quote,
|
|
197
|
+
or a profile of a signed-in user. For those, use `useFeature` or `getFeature`. Call
|
|
198
|
+
the function that they return after the data arrives.
|
|
170
199
|
:::
|
|
171
200
|
|
|
172
|
-
##
|
|
201
|
+
## Use a feature flag in an experience condition
|
|
173
202
|
|
|
174
|
-
|
|
203
|
+
You can use a feature flag in an experience condition through JSON logic:
|
|
175
204
|
|
|
176
205
|
```ts
|
|
177
206
|
const myExperience = createExperience({
|
|
178
|
-
//
|
|
207
|
+
// The other options go here.
|
|
179
208
|
condition: {
|
|
180
209
|
and: [
|
|
181
210
|
{
|
|
182
211
|
'==': [
|
|
183
212
|
{
|
|
184
213
|
invoke: {
|
|
185
|
-
var: 'features.summer-sale', //
|
|
214
|
+
var: 'features.summer-sale', // Point at the feature flag by its ID.
|
|
186
215
|
},
|
|
187
216
|
},
|
|
188
|
-
true, // Check
|
|
217
|
+
true, // Check that the bucket equals true.
|
|
189
218
|
],
|
|
190
219
|
},
|
|
191
220
|
],
|
|
@@ -193,13 +222,15 @@ const myExperience = createExperience({
|
|
|
193
222
|
})
|
|
194
223
|
```
|
|
195
224
|
|
|
196
|
-
The ID in `features.<id>` is the
|
|
225
|
+
The ID in `features.<id>` is the **Key** of the feature flag from the dashboard. It is the
|
|
226
|
+
same value that the detail page of the feature flag shows.
|
|
197
227
|
|
|
198
|
-
|
|
228
|
+
An evaluation of a condition reads the bucket. Picked therefore allocates a visitor
|
|
229
|
+
to a feature flag when the condition that points at it runs.
|
|
199
230
|
|
|
200
|
-
##
|
|
231
|
+
## Watch for feature flag decisions
|
|
201
232
|
|
|
202
|
-
Subscribe to the `feature_decide` event to track when feature
|
|
233
|
+
Subscribe to the `feature_decide` event to track when a feature flag resolves:
|
|
203
234
|
|
|
204
235
|
```ts
|
|
205
236
|
client.eventBus.watch((event) => {
|
|
@@ -207,9 +238,9 @@ client.eventBus.watch((event) => {
|
|
|
207
238
|
|
|
208
239
|
const { feature } = event
|
|
209
240
|
console.log(`Flag "${feature.id}" resolved to bucket:`, feature.bucket)
|
|
210
|
-
// Example output:
|
|
241
|
+
// Example output: the feature flag resolved to the bucket true.
|
|
211
242
|
|
|
212
|
-
// Send to analytics
|
|
243
|
+
// Send the decision to your analytics.
|
|
213
244
|
analytics.track('Feature Decision', {
|
|
214
245
|
featureId: feature.id,
|
|
215
246
|
bucket: feature.bucket,
|
|
@@ -217,50 +248,73 @@ client.eventBus.watch((event) => {
|
|
|
217
248
|
})
|
|
218
249
|
```
|
|
219
250
|
|
|
220
|
-
##
|
|
251
|
+
## Versions and cache invalidation
|
|
221
252
|
|
|
222
|
-
|
|
253
|
+
The SDK caches a feature flag decision in browser storage. It uses `localStorage`, or
|
|
254
|
+
`sessionStorage` in a preview. The cache key derives from the feature flag ID and the version,
|
|
255
|
+
so a visitor keeps the same decision across page loads.
|
|
223
256
|
|
|
224
257
|
:::warning
|
|
225
|
-
|
|
258
|
+
The SDK does not yet restore a string bucket value, such as `'variant-a'`, correctly
|
|
259
|
+
from storage. It treats the visitor as unallocated on the next page load. A boolean
|
|
260
|
+
bucket value is correct, and that includes every feature flag that you control from the
|
|
261
|
+
dashboard. See PICK-1574.
|
|
226
262
|
:::
|
|
227
263
|
|
|
228
|
-
### How
|
|
264
|
+
### How versions work
|
|
229
265
|
|
|
230
|
-
|
|
266
|
+
A feature flag that you create in the dashboard starts at version 1. The SDK uses the
|
|
267
|
+
version to build the cache key. A feature flag at version 3 caches under `v3`.
|
|
231
268
|
|
|
232
|
-
The version affects **
|
|
269
|
+
The version affects **the cache only**. It is not part of the allocation. For a feature flag
|
|
270
|
+
that you control from the dashboard, the allocation derives from the visitor ID and
|
|
271
|
+
the weight of the feature flag.
|
|
233
272
|
|
|
234
|
-
### What happens when the version
|
|
273
|
+
### What happens when you increment the version
|
|
235
274
|
|
|
236
|
-
When you increment a feature flag
|
|
275
|
+
When you increment the version of a feature flag in the dashboard:
|
|
237
276
|
|
|
238
|
-
1. **
|
|
239
|
-
|
|
240
|
-
|
|
277
|
+
1. **The cache key changes.** The old entry, `v3`, no longer matches the new key,
|
|
278
|
+
`v4`. The SDK therefore treats the feature flag as undecided.
|
|
279
|
+
2. **The `decide` function runs again.** On the next page load, the SDK calls
|
|
280
|
+
`decide()` again and caches the new result.
|
|
281
|
+
3. **Picked evaluates each visitor again and moves nobody.** For a feature flag that you
|
|
282
|
+
control from the dashboard the allocation is deterministic. The same visitor
|
|
283
|
+
against an unchanged weight gives the same bucket.
|
|
241
284
|
|
|
242
285
|
:::note
|
|
243
|
-
|
|
286
|
+
The new version clears the cached decisions and runs the allocation again. It moves
|
|
287
|
+
no visitor between groups. With an unchanged weight, everybody lands where they were.
|
|
288
|
+
Increment the version after you change a weight, so that Picked evaluates a visitor
|
|
289
|
+
with a cached decision against the new weight.
|
|
244
290
|
:::
|
|
245
291
|
|
|
246
|
-
### SDK `key`
|
|
292
|
+
### The SDK `key` and the dashboard version
|
|
247
293
|
|
|
248
|
-
|
|
294
|
+
For a feature flag that you define in SDK code, use the `key` field to set the version
|
|
295
|
+
yourself. This applies when you do not read the feature flag from the dashboard:
|
|
249
296
|
|
|
250
297
|
```ts
|
|
251
298
|
features: {
|
|
252
299
|
'summer-sale': {
|
|
253
|
-
key: 'summer-sale-v2', // Change to invalidate cache
|
|
254
|
-
//
|
|
300
|
+
key: 'summer-sale-v2', // Change this to invalidate the cache.
|
|
301
|
+
// The other options go here.
|
|
255
302
|
},
|
|
256
303
|
}
|
|
257
304
|
```
|
|
258
305
|
|
|
259
|
-
For feature
|
|
306
|
+
For a feature flag that you control from the dashboard, Picked sets the version for you. The
|
|
307
|
+
dashboard adds the version number to the end of the cache key that the SDK uses.
|
|
260
308
|
|
|
261
309
|
## Best practices
|
|
262
310
|
|
|
263
|
-
- **Use `session` storage for
|
|
264
|
-
|
|
265
|
-
- **
|
|
266
|
-
|
|
311
|
+
- **Use `session` storage for a short test**: set `storage: 'session'` for an
|
|
312
|
+
experiment that must not stay across browser sessions.
|
|
313
|
+
- **Keep each `decide` function fast**: everything that waits for the feature flag waits for
|
|
314
|
+
`decide` to finish. This includes an experience condition and a component that
|
|
315
|
+
reads the result. Do not make a slow network call inside it.
|
|
316
|
+
- **Allocate when the decision matters**: read a feature flag only after the data that it
|
|
317
|
+
needs arrives. Do not read every feature flag at startup. See
|
|
318
|
+
[Allocation and exposure](/sdk/feature-allocation).
|
|
319
|
+
- **Return one type**: do not return a boolean from a feature flag one time and a string the
|
|
320
|
+
next. Choose one type and keep it.
|