@picked/client 0.0.1-dev.1787019239195 → 0.0.1-dev.1787543060859
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-CahTmEWe.cjs +1 -0
- package/dist/api-DwTNwuWK.js +33 -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/explanation/feature-flag-allocation.md +43 -1
- package/dist/docs/how-to/install-the-sdk.md +2 -2
- package/dist/docs/how-to/read-a-feature-flag.md +22 -10
- package/dist/docs/how-to/read-a-referenced-entry.md +4 -6
- package/dist/docs/how-to/render-a-step-form.md +3 -4
- package/dist/docs/how-to/switch-workspaces.md +1 -2
- 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 +6 -0
- package/dist/docs/reference/pickedql/virtual-functions.md +16 -5
- package/dist/docs/reference/sdk/content.md +6 -9
- package/dist/docs/reference/sdk/features.md +24 -6
- package/dist/docs/reference/sdk/steps.md +3 -4
- package/dist/docs/reference/sdk/type-generation.md +18 -4
- package/dist/docs/reference/tools/cli.md +3 -5
- package/dist/docs/reference/tools/vite-plugin.md +1 -5
- package/dist/docs/tutorials/first-experience.mdx +4 -5
- package/dist/emit-D-tLiWL1.cjs +1 -0
- package/dist/emit-Ds2EG7CB.js +14 -0
- package/dist/esm/index.js +275 -490
- package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
- package/dist/esm/plugins/telemetry/index.js +101 -0
- package/dist/esm/server/index.js +1 -1
- package/dist/plugin-BcFzHPC7.cjs +1 -0
- package/dist/{plugin-Bb2fJVFr.js → plugin-L5y4NdFX.js} +83 -47
- package/dist/{provider-DZEDUlIk.cjs → provider-BLDm3i8g.cjs} +1 -1
- package/dist/{provider-B8vTLrFT.js → provider-Dx6VjvZg.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 +53 -5
- package/dist/types/entities/feature/feature.d.ts.map +1 -1
- package/dist/types/entities/plugin/plugin.d.ts +4 -0
- package/dist/types/entities/plugin/plugin.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 +13 -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 +36 -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-BqU6f22c.cjs +1 -0
- package/dist/version-DAFO6eas.js +256 -0
- package/package.json +16 -4
- package/dist/api-TWnRLh3X.cjs +0 -1
- package/dist/api-uttjU_Tg.js +0 -22
- package/dist/plugin-hFQIiHOB.cjs +0 -1
- package/dist/version-CfTz7TK0.cjs +0 -1
- package/dist/version-DjCWhJD5.js +0 -4
|
@@ -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,6 +78,12 @@ 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:
|
|
@@ -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>
|
|
@@ -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}>
|
|
@@ -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
|
|
@@ -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
|
}
|
|
@@ -22,6 +22,12 @@ point at one. You write no join.
|
|
|
22
22
|
| `experiences` | `experience_title` |
|
|
23
23
|
| `projects` | `project_name`, `project_timezone` |
|
|
24
24
|
|
|
25
|
+
:::warning
|
|
26
|
+
`dim_variant` describes whether the visitor is a known user. It does **not** hold the variants
|
|
27
|
+
of your feature flags. To count the visitors in one variant, use
|
|
28
|
+
[`feature_users`](/reference/pickedql/virtual-functions#feature-functions-on-features-and-on-events).
|
|
29
|
+
:::
|
|
30
|
+
|
|
25
31
|
The `steps`, `experiences`, and `projects` sources are reference tables, and they come
|
|
26
32
|
from the operational database. PickedQL joins them onto `events` and `fact_events`
|
|
27
33
|
when you point at one of their analyst-facing columns. Each one **also** needs its own
|
|
@@ -213,15 +213,26 @@ Parameterized functions that accept the feature name, bucket, and an optional fe
|
|
|
213
213
|
| `feature_sessions('name', 'bucket' [, 'key'])` | Unique sessions in a feature bucket |
|
|
214
214
|
| `feature_events('name', 'bucket' [, 'key'])` | Unique events in a feature bucket |
|
|
215
215
|
|
|
216
|
+
The bucket is what the SDK sent to the site of the visitor. A Boolean feature flag gives
|
|
217
|
+
`'true'` or `'false'`. A Text feature flag gives the value of one variant, or `'false'` for
|
|
218
|
+
the control group. The key is the version of the feature flag, such as `'v1'`.
|
|
219
|
+
|
|
216
220
|
```sql
|
|
217
|
-
-- Compare feature
|
|
221
|
+
-- Compare the two buckets of a Boolean feature flag.
|
|
222
|
+
SELECT
|
|
223
|
+
feature_users('homepage_test', 'false') AS control,
|
|
224
|
+
feature_users('homepage_test', 'true') AS variant
|
|
225
|
+
FROM features
|
|
226
|
+
|
|
227
|
+
-- Compare the variants of a Text feature flag.
|
|
218
228
|
SELECT
|
|
219
|
-
feature_users('
|
|
220
|
-
feature_users('
|
|
229
|
+
feature_users('hero', 'false') AS control,
|
|
230
|
+
feature_users('hero', 'hero-a') AS hero_a,
|
|
231
|
+
feature_users('hero', 'hero-b') AS hero_b
|
|
221
232
|
FROM features
|
|
222
233
|
|
|
223
|
-
--
|
|
224
|
-
SELECT feature_users('homepage_test', '
|
|
234
|
+
-- Count one version of the feature flag alone.
|
|
235
|
+
SELECT feature_users('homepage_test', 'false', 'v1') FROM features
|
|
225
236
|
```
|
|
226
237
|
|
|
227
238
|
## Other (`metrics` only)
|
|
@@ -68,11 +68,10 @@ changes. `getStepContent()` returns a store, which you read with the `$` prefix.
|
|
|
68
68
|
:::code-group
|
|
69
69
|
|
|
70
70
|
```tsx [React]
|
|
71
|
-
import {
|
|
72
|
-
import { useContent } from '@picked/react/hooks'
|
|
71
|
+
import { useContent, useStoreGlobal } from '@picked/react/hooks'
|
|
73
72
|
|
|
74
73
|
export function Banner() {
|
|
75
|
-
const banner =
|
|
74
|
+
const banner = useStoreGlobal(useContent('entry_42'))
|
|
76
75
|
|
|
77
76
|
return <h1>{String(banner?.data.headline ?? '')}</h1>
|
|
78
77
|
}
|
|
@@ -99,12 +98,11 @@ index.
|
|
|
99
98
|
:::code-group
|
|
100
99
|
|
|
101
100
|
```tsx [React]
|
|
102
|
-
import {
|
|
103
|
-
import { useReference, useStepContent } from '@picked/react/hooks'
|
|
101
|
+
import { useReference, useStepContent, useStoreGlobal } from '@picked/react/hooks'
|
|
104
102
|
|
|
105
103
|
export function ProductCategory() {
|
|
106
104
|
const content = useStepContent()
|
|
107
|
-
const category =
|
|
105
|
+
const category = useStoreGlobal(useReference(content?.category))
|
|
108
106
|
|
|
109
107
|
return <span>{String(category?.data.name ?? '')}</span>
|
|
110
108
|
}
|
|
@@ -129,12 +127,11 @@ a list in field order.
|
|
|
129
127
|
:::code-group
|
|
130
128
|
|
|
131
129
|
```tsx [React]
|
|
132
|
-
import {
|
|
133
|
-
import { useReferences, useStepContent } from '@picked/react/hooks'
|
|
130
|
+
import { useReferences, useStepContent, useStoreGlobal } from '@picked/react/hooks'
|
|
134
131
|
|
|
135
132
|
export function ProductCategories() {
|
|
136
133
|
const content = useStepContent()
|
|
137
|
-
const categories =
|
|
134
|
+
const categories = useStoreGlobal(useReferences(content?.categories))
|
|
138
135
|
|
|
139
136
|
return (
|
|
140
137
|
<ul>
|
|
@@ -26,12 +26,22 @@ For a feature flag that you control from the dashboard, the SDK makes the defini
|
|
|
26
26
|
| `enabled` | The dashboard **Enabled** toggle |
|
|
27
27
|
| `weight` | The dashboard **Weight**, 0 to 100 |
|
|
28
28
|
| `storage` | `local`, or `session` in [preview mode](/explanation/feature-flag-allocation#preview-mode) |
|
|
29
|
-
| `decide` | Made by the SDK.
|
|
29
|
+
| `decide` | Made by the SDK. See the two routes below. |
|
|
30
30
|
|
|
31
|
-
The `decide` function that the SDK makes derives from the visitor ID
|
|
32
|
-
feature flag
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
The `decide` function that the SDK makes derives from the visitor ID and from the ID of
|
|
32
|
+
the feature flag. A feature flag that you control from the dashboard therefore cannot use
|
|
33
|
+
the data of your application. Define the feature flag in SDK code when you need that.
|
|
34
|
+
|
|
35
|
+
Which route `decide` takes depends on the type of the feature flag:
|
|
36
|
+
|
|
37
|
+
| Dashboard type | The bucket | How the SDK decides |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| Boolean | `true` or `false` | It compares the stable value of the visitor against `weight`. |
|
|
40
|
+
| Text | The value of one variant, or `false` | It compares the stable value of the visitor against the range that Picked gives to each variant. |
|
|
41
|
+
|
|
42
|
+
The SDK sorts the variants by their key before it allocates, so the order of the payload
|
|
43
|
+
has no effect. See
|
|
44
|
+
[More than two groups](/explanation/feature-flag-allocation#more-than-two-groups).
|
|
35
45
|
|
|
36
46
|
## Define a feature flag in SDK code
|
|
37
47
|
|
|
@@ -95,7 +105,15 @@ You can give more than one override value:
|
|
|
95
105
|
https://example.com?picked-feature=my-feature:true&picked-feature=homepage-variant:variant-a
|
|
96
106
|
```
|
|
97
107
|
|
|
98
|
-
The format is `?picked-feature=<feature-id>:<bucket-value>`.
|
|
108
|
+
The format is `?picked-feature=<feature-id>:<bucket-value>`. For a Text feature flag,
|
|
109
|
+
give the value that the variant serves.
|
|
110
|
+
|
|
111
|
+
A feature ID contains only lowercase letters, numbers, and hyphens. Therefore the SDK divides
|
|
112
|
+
the parameter at the first colon. The bucket value can contain more colons:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
https://example.com?picked-feature=opening-hours:09:00-17:00
|
|
116
|
+
```
|
|
99
117
|
|
|
100
118
|
This is useful for QA, because you can test one bucket value and change no code. The
|
|
101
119
|
SDK keeps the override value in session storage. The browser clears it when you close
|
|
@@ -16,13 +16,12 @@ Inside a `Step` component, or inside any component that a `Step` renders, call
|
|
|
16
16
|
:::code-group
|
|
17
17
|
|
|
18
18
|
```tsx [React]
|
|
19
|
-
import { useStep } from '@picked/react/hooks'
|
|
20
|
-
import { useUnit } from 'effector-react'
|
|
19
|
+
import { useStep, useStoreGlobal } from '@picked/react/hooks'
|
|
21
20
|
|
|
22
21
|
function ContactForm() {
|
|
23
22
|
const { step, fields } = useStep()
|
|
24
|
-
const currentStep =
|
|
25
|
-
const currentFields =
|
|
23
|
+
const currentStep = useStoreGlobal(step)
|
|
24
|
+
const currentFields = useStoreGlobal(fields)
|
|
26
25
|
|
|
27
26
|
return (
|
|
28
27
|
<>
|
|
@@ -65,9 +65,7 @@ If you use Vite, add `@picked/vite-plugin-picked`. Type generation runs automati
|
|
|
65
65
|
import { picked } from '@picked/vite-plugin-picked'
|
|
66
66
|
|
|
67
67
|
export default defineConfig({
|
|
68
|
-
plugins: [
|
|
69
|
-
picked({ disableLauncher: true }),
|
|
70
|
-
],
|
|
68
|
+
plugins: [picked()],
|
|
71
69
|
})
|
|
72
70
|
```
|
|
73
71
|
|
|
@@ -126,7 +124,7 @@ The generated `picked-env.d.ts` file looks like:
|
|
|
126
124
|
|
|
127
125
|
export interface WelcomePage { /* ... */ }
|
|
128
126
|
export interface FieldValues { /* ... */ }
|
|
129
|
-
export type FeatureKey = 'default' | 'winter-sale'
|
|
127
|
+
export type FeatureKey = 'default' | 'hero' | 'winter-sale'
|
|
130
128
|
|
|
131
129
|
declare module '@picked/client' {
|
|
132
130
|
interface PickedSchemaRegistry {
|
|
@@ -134,12 +132,28 @@ declare module '@picked/client' {
|
|
|
134
132
|
stepContentModels: { intro: 'welcome-page' }
|
|
135
133
|
fieldValues: FieldValues
|
|
136
134
|
featureKeys: FeatureKey
|
|
135
|
+
featureValues: { hero: false | 'hero-a' | 'hero-b' }
|
|
137
136
|
}
|
|
138
137
|
}
|
|
139
138
|
```
|
|
140
139
|
|
|
141
140
|
Commit this file to the version control system, or regenerate it in CI after you publish a workspace change.
|
|
142
141
|
|
|
142
|
+
### Feature flag values
|
|
143
|
+
|
|
144
|
+
A Text feature flag declares the values that it can serve, so `useFeatureResult` narrows
|
|
145
|
+
the bucket to those values:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
const result = useFeatureResult('hero')
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The `bucket` property then has the type `false | 'hero-a' | 'hero-b'`.
|
|
152
|
+
|
|
153
|
+
`featureValues` holds the Text feature flags alone. A Boolean feature flag, and a feature
|
|
154
|
+
flag that your application defines, both keep the general `boolean | string` type. The
|
|
155
|
+
`FeatureValueFor<'hero'>` type gives the same union on its own.
|
|
156
|
+
|
|
143
157
|
## Keys the application defines
|
|
144
158
|
|
|
145
159
|
An application can define a feature, an action, a field, a step, or an experience of its
|
|
@@ -30,7 +30,7 @@ package.
|
|
|
30
30
|
| --- | --- |
|
|
31
31
|
| `picked login` | Sign in with your browser (OAuth) to the API origin of the current directory. Stores a session under `~/.picked/auth/`. |
|
|
32
32
|
| `picked logout` | Clear the stored session for the current directory's API origin. |
|
|
33
|
-
| `picked whoami` | Print the identity signed in to the API origin of the current directory
|
|
33
|
+
| `picked whoami` | Print the identity signed in to the API origin of the current directory. |
|
|
34
34
|
| `picked status` | Print the resolved selection and the committed `picked.config` default. |
|
|
35
35
|
| `picked workspace list` | List each org, project, and workspace that you can read. Alias `ls`. `--json` gives machine output. |
|
|
36
36
|
| `picked workspace use` | Set the active project or workspace. Alias `picked use`. Interactive, or `--project` and `--workspace`. |
|
|
@@ -107,9 +107,8 @@ a session for another API.
|
|
|
107
107
|
| --- | --- |
|
|
108
108
|
| `~/.picked/auth/<origin>.json` | The OAuth session for one API origin — access token, refresh token, expiry — with the CLI's registered client id for that origin. File mode `600`. Refreshed as it nears expiry. |
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
no other flow.
|
|
110
|
+
An API deployment with no OAuth client registration rejects `picked login`, and has no
|
|
111
|
+
other flow.
|
|
113
112
|
|
|
114
113
|
A CLI session is a developer credential. It is not an
|
|
115
114
|
[API key](/reference/dashboard/api-keys), which authenticates server-to-server calls, and
|
|
@@ -120,7 +119,6 @@ not [dashboard sign-in](/reference/management-api/authentication).
|
|
|
120
119
|
| Variable | Effect |
|
|
121
120
|
| --- | --- |
|
|
122
121
|
| `PICKED_API_URL` | The API origin that the CLI reads. Supersedes `picked.config` `apiUrl` and the `https://api.picked.app` default. |
|
|
123
|
-
| `PICKED_TOKEN` | An access token in place of the stored session, for a CI or headless run. `picked login` is then not needed. |
|
|
124
122
|
| `PICKED_PROJECT_ID` | Supersedes the resolved project id, above the active selection and the committed configuration. |
|
|
125
123
|
| `PICKED_PREVIEW_TOKEN` | Supersedes the preview token. Applies only when the resolved source is `preview`. |
|
|
126
124
|
|
|
@@ -124,11 +124,7 @@ These two options moved to [`picked.config.ts`](/reference/sdk/type-generation),
|
|
|
124
124
|
|
|
125
125
|
The plugin still reads an option for a field that `picked.config.ts` leaves out, so a
|
|
126
126
|
project that has yet to move needs no change. It stops when the two values are different,
|
|
127
|
-
rather than choose one for you
|
|
128
|
-
|
|
129
|
-
```
|
|
130
|
-
picked plugin option `projectId` is "option-proj", and picked.config says "file-proj".
|
|
131
|
-
```
|
|
127
|
+
rather than choose one for you.
|
|
132
128
|
|
|
133
129
|
### generateTypes
|
|
134
130
|
|
|
@@ -102,11 +102,11 @@ Install the client and the adapter for your framework.
|
|
|
102
102
|
:::code-group
|
|
103
103
|
|
|
104
104
|
```bash [React]
|
|
105
|
-
npm install picked @picked/react
|
|
105
|
+
npm install picked @picked/react
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
```bash [Svelte]
|
|
109
|
-
npm install picked @picked/svelte
|
|
109
|
+
npm install picked @picked/svelte
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
:::
|
|
@@ -118,12 +118,11 @@ puts each one in a form.
|
|
|
118
118
|
:::code-group
|
|
119
119
|
|
|
120
120
|
```tsx [React]
|
|
121
|
-
import { useStep } from '@picked/react/hooks'
|
|
122
|
-
import { useUnit } from 'effector-react'
|
|
121
|
+
import { useStep, useStoreGlobal } from '@picked/react/hooks'
|
|
123
122
|
|
|
124
123
|
export function ColorSurvey() {
|
|
125
124
|
const { fields, form } = useStep()
|
|
126
|
-
const currentFields =
|
|
125
|
+
const currentFields = useStoreGlobal(fields)
|
|
127
126
|
|
|
128
127
|
return (
|
|
129
128
|
<form {...form.props}>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let e=require("@picked/telemetry");async function t(t,n,r){let i=(0,e.createTelemetryEmitter)({endpoint:t,resource:n,headers:{"Picked-Telemetry":`sdk`},maxBatchSize:r.length||1});for(let e of r)i.record(e);await i.flush()}exports.flushSpans=t;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createTelemetryEmitter as e } from "@picked/telemetry";
|
|
2
|
+
//#region src/plugins/telemetry/emit.ts
|
|
3
|
+
async function t(t, n, r) {
|
|
4
|
+
let i = e({
|
|
5
|
+
endpoint: t,
|
|
6
|
+
resource: n,
|
|
7
|
+
headers: { "Picked-Telemetry": "sdk" },
|
|
8
|
+
maxBatchSize: r.length || 1
|
|
9
|
+
});
|
|
10
|
+
for (let e of r) i.record(e);
|
|
11
|
+
await i.flush();
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { t as flushSpans };
|