@picked/client 0.0.1-dev.1786675074931 → 0.0.1-dev.1787537193674
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/.tsbuildinfo +1 -1
- package/dist/api-4YpcWP-x.js +33 -0
- package/dist/api-DRTduQVm.cjs +1 -0
- package/dist/cjs/index.js +2 -2
- package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
- package/dist/cjs/plugins/telemetry/index.js +1 -0
- package/dist/cjs/server/index.js +1 -1
- package/dist/docs/_root.css +101 -0
- package/dist/docs/explanation/feature-flag-allocation.md +43 -1
- package/dist/docs/how-to/analytics/index.md +2 -0
- package/dist/docs/how-to/authenticate-the-cli.md +15 -13
- package/dist/docs/how-to/create-and-revoke-an-api-key.md +7 -2
- package/dist/docs/how-to/debug-with-the-chrome-extension.md +1 -1
- package/dist/docs/how-to/install-the-sdk.md +30 -12
- package/dist/docs/how-to/install-the-vite-plugin.md +22 -9
- package/dist/docs/how-to/read-a-feature-flag.md +23 -11
- package/dist/docs/how-to/read-a-referenced-entry.md +4 -6
- package/dist/docs/how-to/register-a-component.md +2 -2
- package/dist/docs/how-to/register-an-action-handler.md +1 -1
- package/dist/docs/how-to/render-a-step-form.md +3 -4
- package/dist/docs/how-to/server-render-a-page.md +1 -1
- package/dist/docs/how-to/switch-workspaces.md +1 -2
- package/dist/docs/how-to/write-a-predicate.md +1 -1
- package/dist/docs/reference/dashboard/api-keys.mdx +5 -4
- package/dist/docs/reference/dashboard/features.mdx +45 -5
- package/dist/docs/reference/delivery-api/workspace-schema.md +12 -1
- package/dist/docs/reference/pickedql/dimensions.md +16 -0
- package/dist/docs/reference/pickedql/virtual-functions.md +16 -5
- package/dist/docs/reference/sdk/analytics-events.md +445 -0
- package/dist/docs/reference/sdk/content.md +7 -10
- package/dist/docs/reference/sdk/features.md +18 -7
- package/dist/docs/reference/sdk/plugins/dynamic-experiences.md +1 -1
- package/dist/docs/reference/sdk/plugins/window-object.md +1 -1
- package/dist/docs/reference/sdk/ssr/serialize.md +1 -1
- package/dist/docs/reference/sdk/steps.md +3 -4
- package/dist/docs/reference/sdk/type-generation.md +65 -26
- package/dist/docs/reference/tools/cli.md +19 -14
- package/dist/docs/reference/tools/vite-plugin.md +28 -6
- package/dist/docs/tutorials/first-experience.mdx +6 -7
- package/dist/docs/tutorials/first-server-rendered-page.mdx +3 -3
- package/dist/emit-D-tLiWL1.cjs +1 -0
- package/dist/emit-Ds2EG7CB.js +14 -0
- package/dist/esm/index.js +255 -232
- package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
- package/dist/esm/plugins/telemetry/index.js +94 -0
- package/dist/esm/server/index.js +1 -1
- package/dist/plugin-BPZV7SJg.cjs +1 -0
- package/dist/{plugin-ClKkYGTd.js → plugin-DTN7fsyp.js} +44 -29
- package/dist/{provider-D7msjLQ1.cjs → provider-BxhVIcsp.cjs} +1 -1
- package/dist/{provider-BEpf8z6A.js → provider-DLebj-AW.js} +1 -1
- package/dist/request-observer-BXDfOU0V.cjs +1 -0
- package/dist/request-observer-DtG0cRfz.js +19 -0
- package/dist/types/data-source/api.d.ts.map +1 -1
- package/dist/types/data-source/provider.d.ts +11 -1
- package/dist/types/data-source/provider.d.ts.map +1 -1
- package/dist/types/data-source/request-observer.d.ts +46 -0
- package/dist/types/data-source/request-observer.d.ts.map +1 -0
- package/dist/types/entities/feature/feature.d.ts +10 -3
- package/dist/types/entities/feature/feature.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lifecycle/lifecycle.d.ts.map +1 -1
- package/dist/types/picked.d.ts +12 -2
- package/dist/types/picked.d.ts.map +1 -1
- package/dist/types/plugins/dynamic-experiences/hash.d.ts +53 -1
- package/dist/types/plugins/dynamic-experiences/hash.d.ts.map +1 -1
- package/dist/types/plugins/dynamic-experiences/index.d.ts +1 -0
- package/dist/types/plugins/dynamic-experiences/index.d.ts.map +1 -1
- package/dist/types/plugins/dynamic-experiences/plugin.d.ts +1 -1
- package/dist/types/plugins/dynamic-experiences/plugin.d.ts.map +1 -1
- package/dist/types/plugins/dynamic-experiences/types.d.ts +3 -4
- package/dist/types/plugins/dynamic-experiences/types.d.ts.map +1 -1
- package/dist/types/plugins/telemetry/emit.d.ts +18 -0
- package/dist/types/plugins/telemetry/emit.d.ts.map +1 -0
- package/dist/types/plugins/telemetry/index.d.ts +4 -0
- package/dist/types/plugins/telemetry/index.d.ts.map +1 -0
- package/dist/types/plugins/telemetry/plugin-summary.d.ts +10 -0
- package/dist/types/plugins/telemetry/plugin-summary.d.ts.map +1 -0
- package/dist/types/plugins/telemetry/plugin.d.ts +29 -0
- package/dist/types/plugins/telemetry/plugin.d.ts.map +1 -0
- package/dist/types/schema-registry.d.ts +16 -0
- package/dist/types/schema-registry.d.ts.map +1 -1
- package/dist/types/utils/user-timing.d.ts +18 -0
- package/dist/types/utils/user-timing.d.ts.map +1 -0
- package/dist/version-BlahSDlG.cjs +1 -0
- package/dist/{version-CTwGfdI4.js → version-BmBn0dXR.js} +1 -1
- package/package.json +16 -4
- package/dist/api-CSNjsqCv.js +0 -22
- package/dist/api-yuvsQhNy.cjs +0 -1
- package/dist/plugin-D4-caxZN.cjs +0 -1
- package/dist/version-CRxCADU7.cjs +0 -1
|
@@ -65,6 +65,43 @@ your application in its decision. The value comes from the visitor ID, the featu
|
|
|
65
65
|
ID, and the weight, and from nothing else. Define the feature flag in SDK code when the
|
|
66
66
|
decision needs a cart, a quote, or a profile.
|
|
67
67
|
|
|
68
|
+
## More than two groups
|
|
69
|
+
|
|
70
|
+
A Text feature flag has a table of variants, and each variant is one group. Picked
|
|
71
|
+
allocates a visitor to exactly one of them, and the control group is one of the rows.
|
|
72
|
+
|
|
73
|
+
The two types decide by different routes, and the difference matters if you compare them:
|
|
74
|
+
|
|
75
|
+
| Type | Picked compares | Against |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| Boolean | The stable value of the visitor, out of 100 | The weight |
|
|
78
|
+
| Text | One stable value for each variant | The weight of that variant |
|
|
79
|
+
|
|
80
|
+
A Text feature flag gives each variant a score. Picked makes the score from the visitor
|
|
81
|
+
ID, the feature flag ID, and the key of the variant. The weight of the variant then scales
|
|
82
|
+
that score. The variant with the best score receives the visitor.
|
|
83
|
+
|
|
84
|
+
The weight of a variant therefore changes the score of that variant alone. No other
|
|
85
|
+
variant moves because of it.
|
|
86
|
+
|
|
87
|
+
A Boolean feature flag makes the same comparison it always has. A feature flag that you
|
|
88
|
+
made before variants existed therefore keeps every visitor where it put them.
|
|
89
|
+
|
|
90
|
+
Picked derives the stable value from the visitor ID and from the ID of the feature flag,
|
|
91
|
+
for both types. The two types are therefore independent of each other in the same way that
|
|
92
|
+
two feature flags are.
|
|
93
|
+
|
|
94
|
+
**The order of the rows has no effect.** Picked scores each variant from its key, and the
|
|
95
|
+
key of a variant never changes. Move a row in the dashboard and the
|
|
96
|
+
visitors stay where they are. The order that you see is the order that you chose to read
|
|
97
|
+
them in, and nothing more.
|
|
98
|
+
|
|
99
|
+
Rename a variant and nothing changes. The name travels no further than the dashboard.
|
|
100
|
+
|
|
101
|
+
Change the string that a variant serves and the visitors stay in the same group, but your
|
|
102
|
+
site and your reports receive the new string. A visitor with a decision in browser storage
|
|
103
|
+
keeps the old string until you increment the version.
|
|
104
|
+
|
|
68
105
|
## Each feature flag is independent
|
|
69
106
|
|
|
70
107
|
Picked builds that value from the visitor ID together with the ID of the feature flag.
|
|
@@ -81,7 +118,8 @@ then never sees the mixed groups that tell one result from the other.
|
|
|
81
118
|
|
|
82
119
|
## The allocation ratchets
|
|
83
120
|
|
|
84
|
-
Inside one feature flag, a larger
|
|
121
|
+
The ratchet applies to both types of feature flag. Inside one feature flag, a larger
|
|
122
|
+
weight only adds visitors. Everybody allocated at
|
|
85
123
|
weight 30 is still allocated at weight 50. The allocation is a **ratchet**: it turns one
|
|
86
124
|
way, and a larger weight never moves a visitor back out.
|
|
87
125
|
|
|
@@ -89,6 +127,10 @@ That property is what makes a staged release safe. A visitor who sees the change
|
|
|
89
127
|
still sees it at 20%. A smaller weight is the reverse turn. It removes visitors from the
|
|
90
128
|
top of the range, and every visitor below the new weight keeps what they had.
|
|
91
129
|
|
|
130
|
+
A Text feature flag has the same ratchet, one variant at a time. A larger weight on one variant only
|
|
131
|
+
adds visitors to that variant. A smaller weight only removes them. Two variants that you
|
|
132
|
+
do not change never exchange visitors with each other.
|
|
133
|
+
|
|
92
134
|
## A disabled feature flag records nothing
|
|
93
135
|
|
|
94
136
|
A disabled feature flag allocates nobody, and the difference from a weight of 0
|
|
@@ -311,4 +311,6 @@ WHERE project_id = '{{ your project ID }}'
|
|
|
311
311
|
- [About PickedQL](/explanation/how-pickedql-works) — what the layer expands for you.
|
|
312
312
|
- [Virtual functions](/reference/pickedql/virtual-functions) — every function.
|
|
313
313
|
- [Dimensions](/reference/pickedql/dimensions) — every dimension column.
|
|
314
|
+
- [Analytics events](/reference/sdk/analytics-events) — every `event_type` value that a
|
|
315
|
+
filter can use.
|
|
314
316
|
- [Reports](/reference/dashboard/reports) — the same data as an embedded dashboard.
|
|
@@ -5,24 +5,24 @@ layout: docs
|
|
|
5
5
|
|
|
6
6
|
# How to authenticate the CLI
|
|
7
7
|
|
|
8
|
-
This guide shows you how to install
|
|
9
|
-
organizations, projects, and workspaces then know who you are. It expects a Picked
|
|
10
|
-
and a project directory to work in.
|
|
8
|
+
This guide shows you how to install the `picked` command and sign it in. Commands that read
|
|
9
|
+
your organizations, projects, and workspaces then know who you are. It expects a Picked
|
|
10
|
+
account and a project directory to work in.
|
|
11
11
|
|
|
12
12
|
## 1. Install the CLI
|
|
13
13
|
|
|
14
14
|
:::code-group
|
|
15
15
|
|
|
16
16
|
```bash [npm]
|
|
17
|
-
npm install
|
|
17
|
+
npm install picked
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
```bash [pnpm]
|
|
21
|
-
pnpm add
|
|
21
|
+
pnpm add picked
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
```bash [yarn]
|
|
25
|
-
yarn add
|
|
25
|
+
yarn add picked
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
:::
|
|
@@ -44,8 +44,7 @@ https://api.picked.app/…/oauth/authorize?…
|
|
|
44
44
|
Logged in as you@example.com (https://api.picked.app)
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
The CLI stores the session in `~/.picked/auth/<origin>.json
|
|
48
|
-
file mode `600`. It refreshes the session as it nears expiry.
|
|
47
|
+
The CLI stores the session in `~/.picked/auth/<origin>.json`. It refreshes the session as it nears expiry.
|
|
49
48
|
|
|
50
49
|
## 3. Confirm the session
|
|
51
50
|
|
|
@@ -70,15 +69,18 @@ Logged out of https://api.picked.app.
|
|
|
70
69
|
|
|
71
70
|
## Run the CLI in CI
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
`picked generate types` reads the workspace schema, not your account, so CI needs no
|
|
73
|
+
session for it. A workspace you read with `source: 'preview'` needs that workspace's token.
|
|
74
|
+
Run `picked workspace use` once on your machine, then give CI the two values it wrote:
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
|
-
|
|
77
|
+
PICKED_PROJECT_ID=your-project-id \
|
|
78
|
+
PICKED_PREVIEW_TOKEN=$YOUR_CI_SECRET \
|
|
79
|
+
picked generate types --check
|
|
78
80
|
```
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
`picked workspace list` and `picked workspace use` read your organizations and projects, so
|
|
83
|
+
those two do need a session. Run them on your machine, where a browser can sign you in.
|
|
82
84
|
|
|
83
85
|
:::note
|
|
84
86
|
This is a developer session for local tooling. It is not the same as an
|
|
@@ -5,8 +5,13 @@ layout: docs
|
|
|
5
5
|
|
|
6
6
|
# How to create and revoke an API key
|
|
7
7
|
|
|
8
|
-
This guide shows you how to give your code access to the Picked API. Use a key
|
|
9
|
-
|
|
8
|
+
This guide shows you how to give your code access to the Picked API. Use a key where your
|
|
9
|
+
own code calls the API, such as a server-side integration.
|
|
10
|
+
|
|
11
|
+
An API key does not sign the [CLI](/reference/tools/cli) in. The CLI reads a session, which
|
|
12
|
+
`picked login` writes. See
|
|
13
|
+
[How to authenticate the CLI](/how-to/authenticate-the-cli) for the commands that CI can
|
|
14
|
+
run without one.
|
|
10
15
|
|
|
11
16
|
## Create a key
|
|
12
17
|
|
|
@@ -16,7 +16,7 @@ Install it from the
|
|
|
16
16
|
## 2. Add the devtools plugin
|
|
17
17
|
|
|
18
18
|
```typescript
|
|
19
|
-
import { createClient } from '
|
|
19
|
+
import { createClient } from 'picked'
|
|
20
20
|
import { svelteDevtoolsPlugin } from '@picked/svelte/plugins/devtools'
|
|
21
21
|
|
|
22
22
|
const picked = createClient({
|
|
@@ -13,28 +13,41 @@ ID from the dashboard.
|
|
|
13
13
|
:::code-group
|
|
14
14
|
|
|
15
15
|
```bash [React]
|
|
16
|
-
npm install
|
|
16
|
+
npm install picked @picked/react
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
```bash [Svelte]
|
|
20
|
-
npm install
|
|
20
|
+
npm install picked @picked/svelte
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
:::
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
`picked` gives you the client, the `picked.config` helpers, and the `picked` command. Your
|
|
26
|
+
package manager adds `@picked/client` with it, which carries the runtime and holds the
|
|
27
|
+
generated types.
|
|
28
|
+
|
|
29
|
+
## 2. Add `picked.config.ts`
|
|
30
|
+
|
|
31
|
+
Create this file in the root of your project. The `picked` command and the
|
|
32
|
+
[Vite plugin](/reference/tools/vite-plugin) read your project identity from it:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { defineConfig } from 'picked/config'
|
|
36
|
+
|
|
37
|
+
export default defineConfig({
|
|
38
|
+
projectId: 'your-project-id',
|
|
39
|
+
})
|
|
40
|
+
```
|
|
31
41
|
|
|
32
|
-
|
|
42
|
+
[Type generation](/reference/sdk/type-generation) reads this file, and gives your fields,
|
|
43
|
+
features and steps the types the workspace declares.
|
|
44
|
+
|
|
45
|
+
## 3. Create and start the client
|
|
33
46
|
|
|
34
47
|
:::code-group
|
|
35
48
|
|
|
36
49
|
```tsx [React]
|
|
37
|
-
import { createClient } from '
|
|
50
|
+
import { createClient } from 'picked'
|
|
38
51
|
import { dynamicExperiencesPlugin } from '@picked/client/plugins/dynamic-experiences'
|
|
39
52
|
import { reactFrameworkPlugin } from '@picked/react/plugins/framework'
|
|
40
53
|
|
|
@@ -47,7 +60,7 @@ picked.start()
|
|
|
47
60
|
```
|
|
48
61
|
|
|
49
62
|
```ts [Svelte]
|
|
50
|
-
import { createClient } from '
|
|
63
|
+
import { createClient } from 'picked'
|
|
51
64
|
import { dynamicExperiencesPlugin } from '@picked/client/plugins/dynamic-experiences'
|
|
52
65
|
import { svelteFrameworkPlugin } from '@picked/svelte/plugins/framework'
|
|
53
66
|
|
|
@@ -61,9 +74,13 @@ picked.start()
|
|
|
61
74
|
|
|
62
75
|
:::
|
|
63
76
|
|
|
77
|
+
`picked` carries the client's root entry. Each deeper import path, such as
|
|
78
|
+
`@picked/client/plugins/dynamic-experiences` and `@picked/client/server`, keeps its own
|
|
79
|
+
name.
|
|
80
|
+
|
|
64
81
|
`start()` is idempotent. A second call changes nothing on a client that started already.
|
|
65
82
|
|
|
66
|
-
##
|
|
83
|
+
## 4. Add the optional plugins
|
|
67
84
|
|
|
68
85
|
- `windowObjectPlugin()` puts the client on `window.Picked`, which helps when you debug.
|
|
69
86
|
- `singlePageAppPlugin()` starts the client again on a history event.
|
|
@@ -72,4 +89,5 @@ picked.start()
|
|
|
72
89
|
|
|
73
90
|
- [Add Picked to your application](/tutorials/first-experience) — the whole path.
|
|
74
91
|
- [SDK installation](/reference/sdk) — every client option.
|
|
92
|
+
- [Type generation](/reference/sdk/type-generation) — types from your workspace.
|
|
75
93
|
- [How to register a component](/how-to/register-a-component).
|
|
@@ -13,37 +13,49 @@ API request at runtime.
|
|
|
13
13
|
:::code-group
|
|
14
14
|
|
|
15
15
|
```bash [npm]
|
|
16
|
-
npm install @picked/vite-plugin-picked
|
|
16
|
+
npm install @picked/vite-plugin-picked picked
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
```bash [pnpm]
|
|
20
|
-
pnpm add @picked/vite-plugin-picked
|
|
20
|
+
pnpm add @picked/vite-plugin-picked picked
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
```bash [yarn]
|
|
24
|
-
yarn add @picked/vite-plugin-picked
|
|
24
|
+
yarn add @picked/vite-plugin-picked picked
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
:::
|
|
28
28
|
|
|
29
|
-
## 2.
|
|
29
|
+
## 2. Name your project
|
|
30
|
+
|
|
31
|
+
Create `picked.config.ts` in the root of your project. The plugin reads your project
|
|
32
|
+
identity from this file:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { defineConfig } from 'picked/config'
|
|
36
|
+
|
|
37
|
+
export default defineConfig({
|
|
38
|
+
projectId: 'your-project-id',
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 3. Add the plugin to your Vite config
|
|
30
43
|
|
|
31
44
|
```typescript
|
|
32
45
|
import { defineConfig } from 'vite'
|
|
33
46
|
import { picked } from '@picked/vite-plugin-picked'
|
|
34
47
|
|
|
35
48
|
export default defineConfig({
|
|
36
|
-
plugins: [
|
|
37
|
-
picked({
|
|
38
|
-
projectId: 'your-project-id',
|
|
39
|
-
}),
|
|
40
|
-
],
|
|
49
|
+
plugins: [picked()],
|
|
41
50
|
})
|
|
42
51
|
```
|
|
43
52
|
|
|
44
53
|
When Vite starts, the plugin reads your project configuration and embeds them in your bundle.
|
|
45
54
|
The SDK then reads the embedded configuration and makes no network request.
|
|
46
55
|
|
|
56
|
+
The plugin also writes `picked-env.d.ts`, which types your fields, features and steps
|
|
57
|
+
against the workspace. See [Type generation](/reference/sdk/type-generation).
|
|
58
|
+
|
|
47
59
|
:::note
|
|
48
60
|
When a preview token is there at runtime, the SDK ignores the embedded configuration and reads
|
|
49
61
|
fresh data from the API.
|
|
@@ -52,5 +64,6 @@ fresh data from the API.
|
|
|
52
64
|
## Related
|
|
53
65
|
|
|
54
66
|
- [Vite plugin](/reference/tools/vite-plugin) — every option.
|
|
67
|
+
- [Type generation](/reference/sdk/type-generation) — the `picked.config.ts` options.
|
|
55
68
|
- [About how Picked works](/explanation/how-picked-works) — where build-time configuration sit
|
|
56
69
|
in the startup order.
|
|
@@ -22,8 +22,8 @@ decision.
|
|
|
22
22
|
| --- | --- | --- |
|
|
23
23
|
| `useFeature` / `getFeature` | `() => Promise<bucket>` | Only when you call the function that it returns |
|
|
24
24
|
| `useFeatureResult` / `getFeatureResult` | `{ id, bucket }`, and `undefined` until it resolves | Yes, as soon as the component renders |
|
|
25
|
-
| `useFeatureEnabled` / `getFeatureEnabled` | `boolean \| undefined`, the dashboard **Enabled**
|
|
26
|
-
| `client.features.listStore` | Every registered feature flag, each with an `isResolved`
|
|
25
|
+
| `useFeatureEnabled` / `getFeatureEnabled` | `boolean \| undefined`, the dashboard **Enabled** setting | No |
|
|
26
|
+
| `client.features.listStore` | Every registered feature flag, each with an `isResolved` property | No |
|
|
27
27
|
| `client.features.getById(id)` | A store that holds one feature flag | No |
|
|
28
28
|
|
|
29
29
|
Some decisions must wait for data that you do not have yet, such as a cart, a quote, or
|
|
@@ -32,6 +32,9 @@ function they return after the data arrives.
|
|
|
32
32
|
|
|
33
33
|
## 2. Read the bucket
|
|
34
34
|
|
|
35
|
+
The bucket is what your site receives. A Boolean feature flag gives `true` or `false`. A
|
|
36
|
+
Text feature flag gives the value of one variant, or `false` for the control group.
|
|
37
|
+
|
|
35
38
|
:::code-group
|
|
36
39
|
|
|
37
40
|
```tsx [React]
|
|
@@ -39,12 +42,13 @@ import { useFeatureResult } from '@picked/react/hooks'
|
|
|
39
42
|
|
|
40
43
|
function MyComponent() {
|
|
41
44
|
// { id, bucket }, or undefined until the feature flag resolves.
|
|
42
|
-
const result = useFeatureResult('
|
|
45
|
+
const result = useFeatureResult('hero')
|
|
43
46
|
|
|
44
47
|
return (
|
|
45
48
|
<div>
|
|
46
|
-
{result?.bucket ===
|
|
47
|
-
{result?.bucket === '
|
|
49
|
+
{result?.bucket === false && <p>Control</p>}
|
|
50
|
+
{result?.bucket === 'hero-a' && <p>Hero A</p>}
|
|
51
|
+
{result?.bucket === 'hero-b' && <p>Hero B</p>}
|
|
48
52
|
</div>
|
|
49
53
|
)
|
|
50
54
|
}
|
|
@@ -55,13 +59,15 @@ function MyComponent() {
|
|
|
55
59
|
import { getFeatureResult } from '@picked/svelte/hooks'
|
|
56
60
|
|
|
57
61
|
// A store that holds { id, bucket }, or undefined until the feature flag resolves.
|
|
58
|
-
const result = getFeatureResult('
|
|
62
|
+
const result = getFeatureResult('hero')
|
|
59
63
|
</script>
|
|
60
64
|
|
|
61
|
-
{#if $result?.bucket ===
|
|
65
|
+
{#if $result?.bucket === false}
|
|
62
66
|
<p>Control</p>
|
|
63
|
-
{:else if $result?.bucket === '
|
|
64
|
-
<p>
|
|
67
|
+
{:else if $result?.bucket === 'hero-a'}
|
|
68
|
+
<p>Hero A</p>
|
|
69
|
+
{:else if $result?.bucket === 'hero-b'}
|
|
70
|
+
<p>Hero B</p>
|
|
65
71
|
{/if}
|
|
66
72
|
```
|
|
67
73
|
|
|
@@ -72,12 +78,18 @@ function MyComponent() {
|
|
|
72
78
|
renders, and they commit the visitor to a bucket at that moment.
|
|
73
79
|
:::
|
|
74
80
|
|
|
81
|
+
:::tip
|
|
82
|
+
Run [type generation](/reference/sdk/type-generation) and `bucket` narrows to the values
|
|
83
|
+
that this Text feature flag declares. Your editor then shows each value, and it reports a
|
|
84
|
+
comparison against a value that the feature flag cannot serve.
|
|
85
|
+
:::
|
|
86
|
+
|
|
75
87
|
## 3. Read it without a framework
|
|
76
88
|
|
|
77
89
|
The event bus works with any framework, and with none:
|
|
78
90
|
|
|
79
91
|
```ts
|
|
80
|
-
import { createClient } from '
|
|
92
|
+
import { createClient } from 'picked'
|
|
81
93
|
|
|
82
94
|
const client = createClient({
|
|
83
95
|
/* Put your client options here. */
|
|
@@ -85,7 +97,7 @@ const client = createClient({
|
|
|
85
97
|
|
|
86
98
|
client.eventBus.watch((event) => {
|
|
87
99
|
if (event.type === 'feature_decide' && event.feature.id === 'my-feature') {
|
|
88
|
-
console.log(event.feature) // { id: 'my-feature', key: 'v1', bucket: '
|
|
100
|
+
console.log(event.feature) // { id: 'my-feature', key: 'v1', bucket: 'hero-a' }
|
|
89
101
|
}
|
|
90
102
|
})
|
|
91
103
|
```
|
|
@@ -20,12 +20,11 @@ Pass the raw field value to `useReference()` in React, or to `getReference()` in
|
|
|
20
20
|
:::code-group
|
|
21
21
|
|
|
22
22
|
```tsx [React]
|
|
23
|
-
import {
|
|
24
|
-
import { useReference, useStepContent } from '@picked/react/hooks'
|
|
23
|
+
import { useReference, useStepContent, useStoreGlobal } from '@picked/react/hooks'
|
|
25
24
|
|
|
26
25
|
export function ProductCategory() {
|
|
27
26
|
const content = useStepContent()
|
|
28
|
-
const category =
|
|
27
|
+
const category = useStoreGlobal(useReference(content?.category))
|
|
29
28
|
|
|
30
29
|
if (!category) return null
|
|
31
30
|
|
|
@@ -58,12 +57,11 @@ Use `useReferences()` or `getReferences()`. They give a list in field order.
|
|
|
58
57
|
:::code-group
|
|
59
58
|
|
|
60
59
|
```tsx [React]
|
|
61
|
-
import {
|
|
62
|
-
import { useReferences, useStepContent } from '@picked/react/hooks'
|
|
60
|
+
import { useReferences, useStepContent, useStoreGlobal } from '@picked/react/hooks'
|
|
63
61
|
|
|
64
62
|
export function ProductCategories() {
|
|
65
63
|
const content = useStepContent()
|
|
66
|
-
const categories =
|
|
64
|
+
const categories = useStoreGlobal(useReferences(content?.categories))
|
|
67
65
|
|
|
68
66
|
return (
|
|
69
67
|
<ul>
|
|
@@ -17,7 +17,7 @@ the dashboard.
|
|
|
17
17
|
:::code-group
|
|
18
18
|
|
|
19
19
|
```tsx [React]
|
|
20
|
-
import { createClient } from '
|
|
20
|
+
import { createClient } from 'picked'
|
|
21
21
|
import { adaptReactComponent } from '@picked/react'
|
|
22
22
|
import { MyWidget } from './MyWidget'
|
|
23
23
|
|
|
@@ -30,7 +30,7 @@ const picked = createClient({
|
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
```ts [Svelte]
|
|
33
|
-
import { createClient } from '
|
|
33
|
+
import { createClient } from 'picked'
|
|
34
34
|
import { adaptSvelteComponent } from '@picked/svelte'
|
|
35
35
|
import MyWidget from './MyWidget.svelte'
|
|
36
36
|
|
|
@@ -15,7 +15,7 @@ Pass an `actions` object to `createClient()`. Each key must match an action key
|
|
|
15
15
|
dashboard:
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
|
-
import { createClient } from '
|
|
18
|
+
import { createClient } from 'picked'
|
|
19
19
|
|
|
20
20
|
const picked = createClient({
|
|
21
21
|
projectId: 'your-project-id',
|
|
@@ -59,13 +59,12 @@ Bind `form.props` to your `<form>` element and map over the fields.
|
|
|
59
59
|
:::code-group
|
|
60
60
|
|
|
61
61
|
```tsx [React]
|
|
62
|
-
import { useStep } from '@picked/react/hooks'
|
|
63
|
-
import { useUnit } from 'effector-react'
|
|
62
|
+
import { useStep, useStoreGlobal } from '@picked/react/hooks'
|
|
64
63
|
|
|
65
64
|
function StepForm() {
|
|
66
65
|
const { fields, form } = useStep()
|
|
67
|
-
const currentFields =
|
|
68
|
-
const isSubmitting =
|
|
66
|
+
const currentFields = useStoreGlobal(fields)
|
|
67
|
+
const isSubmitting = useStoreGlobal(form.submitting)
|
|
69
68
|
|
|
70
69
|
return (
|
|
71
70
|
<form {...form.props}>
|
|
@@ -13,7 +13,7 @@ browser. You need a published Page and a server that renders React or Svelte.
|
|
|
13
13
|
Create one client for each request, then call `serialize()`:
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
import { createClient } from '
|
|
16
|
+
import { createClient } from 'picked'
|
|
17
17
|
import { serialize } from '@picked/client/server'
|
|
18
18
|
|
|
19
19
|
const client = createClient(options)
|
|
@@ -22,7 +22,7 @@ picked login
|
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
This opens your browser for a one-time OAuth sign-in and stores a session under
|
|
25
|
-
`~/.picked/`. Confirm it with `picked whoami`.
|
|
25
|
+
`~/.picked/`. Confirm it with `picked whoami`. See
|
|
26
26
|
[CLI authentication](/how-to/authenticate-the-cli).
|
|
27
27
|
|
|
28
28
|
## 2. Find a workspace
|
|
@@ -99,7 +99,6 @@ Active selection: none (using the config default).
|
|
|
99
99
|
workspace in CI with no selection file, set the environment directly:
|
|
100
100
|
|
|
101
101
|
```bash
|
|
102
|
-
PICKED_TOKEN="$TOKEN" \
|
|
103
102
|
PICKED_PROJECT_ID=proj_a1b2 \
|
|
104
103
|
PICKED_PREVIEW_TOKEN="$DRAFT_TOKEN" \
|
|
105
104
|
picked generate types --check
|
|
@@ -13,7 +13,7 @@ such as a cart total or an account level.
|
|
|
13
13
|
Pass a `predicates` object to `createClient()`:
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
import { createClient } from '
|
|
16
|
+
import { createClient } from 'picked'
|
|
17
17
|
|
|
18
18
|
const client = createClient({
|
|
19
19
|
projectId: 'your-project-id',
|
|
@@ -7,8 +7,9 @@ import { Screenshot } from '../../../../components/Screenshot'
|
|
|
7
7
|
|
|
8
8
|
# API keys
|
|
9
9
|
|
|
10
|
-
An API key lets your code reach the Picked API. Use one
|
|
11
|
-
|
|
10
|
+
An API key lets your code reach the Picked API. Use one where your own code calls the API,
|
|
11
|
+
such as a server-side integration. It does not sign the
|
|
12
|
+
[CLI](/reference/tools/cli) in.
|
|
12
13
|
|
|
13
14
|
<Screenshot
|
|
14
15
|
route="/{orgSlug}/api-keys"
|
|
@@ -17,11 +18,11 @@ or for a CI/CD workflow.
|
|
|
17
18
|
|
|
18
19
|
## Use an API key
|
|
19
20
|
|
|
20
|
-
Put your API key in the `
|
|
21
|
+
Put your API key in the `x-api-key` header of each request to the
|
|
21
22
|
[Picked API](/tutorials/first-api-call).
|
|
22
23
|
|
|
23
24
|
```
|
|
24
|
-
|
|
25
|
+
x-api-key: your-api-key
|
|
25
26
|
```
|
|
26
27
|
|
|
27
28
|
## Scopes
|
|
@@ -21,6 +21,19 @@ Each feature flag has a unique key. Your application uses this key to check whet
|
|
|
21
21
|
Picked allocates a visitor to the feature flag. You set the key when you create the
|
|
22
22
|
feature flag, and you cannot change it later.
|
|
23
23
|
|
|
24
|
+
## Types
|
|
25
|
+
|
|
26
|
+
Each feature flag has a type. You set the type when you create the feature flag, and you
|
|
27
|
+
cannot change it later.
|
|
28
|
+
|
|
29
|
+
| Type | What your site receives | Groups |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| **Boolean** | `true` or `false` | Two |
|
|
32
|
+
| **Text** | A string that you write, or `false` for the control group | Two or more |
|
|
33
|
+
|
|
34
|
+
Choose **Boolean** for one change against no change. Choose **Text** when you must test
|
|
35
|
+
several changes at the same time, and each change needs its own group.
|
|
36
|
+
|
|
24
37
|
## Set the allocation
|
|
25
38
|
|
|
26
39
|
Set the allocation from the detail page of the feature flag:
|
|
@@ -28,19 +41,46 @@ Set the allocation from the detail page of the feature flag:
|
|
|
28
41
|
| Control | What it does |
|
|
29
42
|
| --- | --- |
|
|
30
43
|
| **Enabled** | Whether Picked allocates a visitor to the feature flag. A disabled feature flag allocates nobody, and it records nothing. See [Disable a feature flag](#disable-a-feature-flag). |
|
|
31
|
-
| **Weight** | The percentage of visitors that Picked allocates to
|
|
44
|
+
| **Weight** | The percentage of visitors that Picked allocates to a Boolean feature flag, from 0 to 100. |
|
|
45
|
+
| **Variants** | The groups of a Text feature flag, and the traffic that each one receives. See [Variants](#variants). |
|
|
32
46
|
| **Description** | An internal note about what the feature flag tests. |
|
|
33
47
|
|
|
34
|
-
A feature flag has one weight. For each visitor it gives one of two outcomes:
|
|
48
|
+
A Boolean feature flag has one weight. For each visitor it gives one of two outcomes:
|
|
35
49
|
allocated, or not allocated. A weight of 30 allocates 30% of your visitors. The other
|
|
36
50
|
70% stay unallocated. A weight of 0 allocates nobody. A weight of 100 allocates
|
|
37
51
|
everybody.
|
|
38
52
|
|
|
53
|
+
## Variants
|
|
54
|
+
|
|
55
|
+
A Text feature flag has a table of variants. Each row is one group of visitors:
|
|
56
|
+
|
|
57
|
+
| Column | What it does |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| **Name** | A label for the variant. It appears in your reports. You can change it at any time. |
|
|
60
|
+
| **Value** | The string that your site receives for a visitor in this variant. |
|
|
61
|
+
| **Weight** | The share of traffic for this variant, against the other rows. |
|
|
62
|
+
|
|
63
|
+
The first row is **Control**. Picked always sends `false` to a visitor in the control
|
|
64
|
+
group, and you cannot remove the row, move it, or change its value. It takes a weight like
|
|
65
|
+
any other row.
|
|
66
|
+
|
|
67
|
+
The weights are shares, and not percentages. Picked reduces them to their smallest whole
|
|
68
|
+
numbers, so `10 : 10 : 20` and `1 : 1 : 2` are the same split. The percentage beside each
|
|
69
|
+
row shows the traffic that the variant receives.
|
|
70
|
+
|
|
71
|
+
Choose **Even split** to give every variant the same weight.
|
|
72
|
+
|
|
73
|
+
:::info
|
|
74
|
+
The order of the rows is the order that you read them in. It has no effect on the
|
|
75
|
+
allocation, so you can move a row and no visitor changes group.
|
|
76
|
+
:::
|
|
77
|
+
|
|
39
78
|
## How Picked allocates a visitor
|
|
40
79
|
|
|
41
|
-
The allocation is deterministic. It derives from the visitor ID
|
|
42
|
-
flag
|
|
43
|
-
|
|
80
|
+
The allocation is deterministic. It derives from the visitor ID and from the ID of the
|
|
81
|
+
feature flag. A Boolean feature flag then compares that value against the weight, and a
|
|
82
|
+
Text feature flag compares it against the range of each variant. Each feature flag gets its
|
|
83
|
+
own value, so tests that run at the same time are independent. See
|
|
44
84
|
[About feature flag allocation](/explanation/feature-flag-allocation).
|
|
45
85
|
|
|
46
86
|
## Disable a feature flag
|
|
@@ -28,6 +28,7 @@ The response is a [JSON Schema draft 2020-12](https://json-schema.org/draft/2020
|
|
|
28
28
|
| `x-picked-source` | `"published"` or `"preview"` — mirrors the surface you requested. |
|
|
29
29
|
| `x-picked-version-id` | Published workspace version id, or `null` on preview. |
|
|
30
30
|
| `x-picked-step-content-models` | Map of step key → content model key, which gives each layout its type. |
|
|
31
|
+
| `x-picked-feature-values` | Map of feature key → the values that the feature can serve. Picked adds this field only when the workspace has a Text feature flag. |
|
|
31
32
|
| `$defs.contentModels` | JSON Schema definitions for each content model. |
|
|
32
33
|
| `$defs.contentBlocks` | JSON Schema definitions for reusable content blocks. |
|
|
33
34
|
| `$defs.fields` | Object schema for project field values. |
|
|
@@ -41,6 +42,13 @@ On the published surface, the schema reflects your last published workspace. On
|
|
|
41
42
|
|
|
42
43
|
When no published schema exists, the published endpoint returns `404` and tells you to publish the workspace.
|
|
43
44
|
|
|
45
|
+
A Boolean feature flag serves `true` or `false`, and it contributes no entry to
|
|
46
|
+
`x-picked-feature-values`. A workspace of Boolean feature flags therefore omits the field.
|
|
47
|
+
A schema that Picked published before Text feature flags existed also omits it.
|
|
48
|
+
|
|
49
|
+
Picked sorts the values of each Text feature flag by the key of the variant. The document
|
|
50
|
+
therefore does not change when you rename a variant or move a row.
|
|
51
|
+
|
|
44
52
|
## Example
|
|
45
53
|
|
|
46
54
|
```bash
|
|
@@ -57,11 +65,14 @@ curl https://api.picked.app/api/v1/projects/proj_123/schema
|
|
|
57
65
|
"x-picked-step-content-models": {
|
|
58
66
|
"intro": "welcome-page"
|
|
59
67
|
},
|
|
68
|
+
"x-picked-feature-values": {
|
|
69
|
+
"hero": [false, "hero-a", "hero-b"]
|
|
70
|
+
},
|
|
60
71
|
"$defs": {
|
|
61
72
|
"contentModels": {},
|
|
62
73
|
"contentBlocks": {},
|
|
63
74
|
"fields": { "type": "object", "additionalProperties": false, "properties": {} },
|
|
64
|
-
"featureKeys": { "type": "string", "enum": ["default"] },
|
|
75
|
+
"featureKeys": { "type": "string", "enum": ["default", "hero"] },
|
|
65
76
|
"experienceKeys": { "type": "string", "enum": ["welcome-survey"] },
|
|
66
77
|
"stepKeys": { "type": "string", "enum": ["intro"] }
|
|
67
78
|
}
|