@picked/client 0.0.1-dev.1785305085063 → 0.0.1-dev.1785908062293
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 -0
- package/dist/api-03c5jvVk.js +38 -0
- package/dist/api-CQxCVIrm.cjs +1 -0
- package/dist/cjs/form/index.js +1 -1
- package/dist/cjs/index.js +2 -2
- package/dist/cjs/plugins/dom-observer/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/explanation/conditions-and-the-logic-engine.md +94 -0
- package/dist/docs/explanation/feature-flag-allocation.md +176 -0
- package/dist/docs/explanation/how-picked-works.md +124 -0
- package/dist/docs/explanation/how-pickedql-works.md +76 -0
- package/dist/docs/explanation/pages-and-flows.md +64 -0
- package/dist/docs/explanation/the-content-model.md +77 -0
- package/dist/docs/explanation/the-two-apis.md +68 -0
- package/dist/docs/explanation/why-server-side-rendering.md +76 -0
- package/dist/docs/explanation/workspaces-and-versions.md +71 -0
- package/dist/docs/how-to/add-a-step-field-or-action.md +43 -0
- package/dist/docs/{pickedql/examples.md → how-to/analytics/index.md} +53 -28
- package/dist/docs/how-to/archive-or-delete-a-workspace.md +46 -0
- package/dist/docs/how-to/authenticate-with-a-magic-link.md +40 -0
- package/dist/docs/how-to/authenticate-with-a-password.md +51 -0
- package/dist/docs/how-to/authenticate-with-an-api-key.md +51 -0
- package/dist/docs/how-to/authenticate-with-oauth.md +40 -0
- package/dist/docs/how-to/build-a-flow.md +83 -0
- package/dist/docs/how-to/connect-the-mcp-server.md +38 -0
- package/dist/docs/how-to/create-a-project.md +29 -0
- package/dist/docs/how-to/create-an-experience.md +42 -0
- package/dist/docs/how-to/create-and-revoke-an-api-key.md +37 -0
- package/dist/docs/how-to/debug-with-the-chrome-extension.md +57 -0
- package/dist/docs/how-to/define-a-page-layout.md +39 -0
- package/dist/docs/how-to/fill-and-publish-a-page.md +50 -0
- package/dist/docs/how-to/install-the-gtm-template.md +84 -0
- package/dist/docs/how-to/install-the-sdk.md +68 -0
- package/dist/docs/how-to/install-the-vite-plugin.md +56 -0
- package/dist/docs/how-to/invite-and-remove-members.md +34 -0
- package/dist/docs/how-to/preview-a-workspace.md +28 -0
- package/dist/docs/how-to/publish-a-workspace.md +30 -0
- package/dist/docs/how-to/read-a-feature-flag.md +101 -0
- package/dist/docs/how-to/register-a-component.md +55 -0
- package/dist/docs/how-to/register-an-action-handler.md +67 -0
- package/dist/docs/how-to/render-a-step-form.md +132 -0
- package/dist/docs/how-to/render-an-unpublished-preview.md +41 -0
- package/dist/docs/how-to/reuse-a-section.md +37 -0
- package/dist/docs/how-to/reuse-a-step-field-or-action.md +28 -0
- package/dist/docs/how-to/roll-back-a-published-version.md +26 -0
- package/dist/docs/how-to/run-an-ab-test.md +49 -0
- package/dist/docs/how-to/run-code-on-a-form-submission.md +125 -0
- package/dist/docs/how-to/server-render-a-page.md +81 -0
- package/dist/docs/how-to/set-a-content-security-policy.md +32 -0
- package/dist/docs/how-to/set-up-mfa.md +69 -0
- package/dist/docs/how-to/upload-and-use-an-asset.mdx +73 -0
- package/dist/docs/how-to/validate-a-field.md +52 -0
- package/dist/docs/how-to/write-a-condition.md +45 -0
- package/dist/docs/how-to/write-a-predicate.md +60 -0
- package/dist/docs/index.md +41 -59
- package/dist/docs/reference/dashboard/actions.mdx +53 -0
- package/dist/docs/reference/dashboard/api-keys.mdx +37 -0
- package/dist/docs/reference/dashboard/conditions.mdx +42 -0
- package/dist/docs/reference/dashboard/content/asset-library.mdx +63 -0
- package/dist/docs/reference/dashboard/content/content-entries.mdx +44 -0
- package/dist/docs/reference/dashboard/content/layouts.mdx +62 -0
- package/dist/docs/reference/dashboard/content/pages.mdx +35 -0
- package/dist/docs/reference/dashboard/content/sections.mdx +28 -0
- package/dist/docs/reference/dashboard/experiences.mdx +51 -0
- package/dist/docs/reference/dashboard/features.mdx +114 -0
- package/dist/docs/reference/dashboard/fields.mdx +100 -0
- package/dist/docs/reference/dashboard/flow-builder/canvas.mdx +58 -0
- package/dist/docs/reference/dashboard/flow-builder/condition-builder.md +57 -0
- package/dist/docs/reference/dashboard/flow-builder/keyboard-shortcuts.md +72 -0
- package/dist/docs/reference/dashboard/flow-builder/nodes.md +63 -0
- package/dist/docs/reference/dashboard/projects.mdx +44 -0
- package/dist/docs/reference/dashboard/reports.mdx +43 -0
- package/dist/docs/reference/dashboard/steps.mdx +37 -0
- package/dist/docs/reference/dashboard/team-management.md +29 -0
- package/dist/docs/reference/dashboard/workspace-versioning.mdx +30 -0
- package/dist/docs/reference/dashboard/workspaces.mdx +76 -0
- package/dist/docs/{delivery-api → reference/delivery-api}/experience-content.md +17 -5
- package/dist/docs/reference/delivery-api/index.md +45 -0
- package/dist/docs/reference/delivery-api/preview-caching-crawling.md +79 -0
- package/dist/docs/{delivery-api → reference/delivery-api}/project-config.md +15 -6
- package/dist/docs/reference/glossary.md +173 -0
- package/dist/docs/reference/management-api/authentication.md +74 -0
- package/dist/docs/{api → reference/management-api}/authorization-and-scopes.md +70 -47
- package/dist/docs/reference/management-api/error-handling.md +233 -0
- package/dist/docs/{api → reference/management-api}/pagination.md +36 -20
- package/dist/docs/reference/pickedql/data-sources.md +174 -0
- package/dist/docs/reference/pickedql/dimensions.md +87 -0
- package/dist/docs/{pickedql → reference/pickedql}/filtering.md +17 -6
- package/dist/docs/reference/pickedql/index.md +18 -0
- package/dist/docs/reference/pickedql/schema.md +172 -0
- package/dist/docs/reference/pickedql/virtual-functions.md +237 -0
- package/dist/docs/reference/sdk/actions.md +30 -0
- package/dist/docs/{sdk → reference/sdk}/conditions.md +15 -5
- package/dist/docs/reference/sdk/content-security-policy.md +21 -0
- package/dist/docs/reference/sdk/experiences.md +51 -0
- package/dist/docs/reference/sdk/features.md +233 -0
- package/dist/docs/reference/sdk/fields.md +209 -0
- package/dist/docs/reference/sdk/index.md +68 -0
- package/dist/docs/{sdk → reference/sdk}/plugins/create-experience.md +25 -34
- package/dist/docs/reference/sdk/plugins/dynamic-experiences.md +74 -0
- package/dist/docs/reference/sdk/plugins/index.md +29 -0
- package/dist/docs/{sdk → reference/sdk}/plugins/window-object.md +6 -0
- package/dist/docs/reference/sdk/predicates.md +43 -0
- package/dist/docs/reference/sdk/ssr/index.md +33 -0
- package/dist/docs/reference/sdk/ssr/rendering-and-hydration.md +103 -0
- package/dist/docs/reference/sdk/ssr/serialize.md +77 -0
- package/dist/docs/reference/sdk/ssr/slots-and-placements.md +59 -0
- package/dist/docs/reference/sdk/steps.md +87 -0
- package/dist/docs/reference/tools/chrome-extension.md +16 -0
- package/dist/docs/reference/tools/image-optimisation-service.md +104 -0
- package/dist/docs/reference/tools/mcp.md +88 -0
- package/dist/docs/reference/tools/vite-plugin.md +88 -0
- package/dist/docs/tutorials/first-api-call.md +228 -0
- package/dist/docs/tutorials/first-experience-in-the-dashboard.mdx +143 -0
- package/dist/docs/tutorials/first-experience.mdx +255 -0
- package/dist/docs/tutorials/first-server-rendered-page.mdx +186 -0
- package/dist/{dom-BwmTHeT7.js → dom-BqcToNL0.js} +1 -3
- package/dist/{dom-CDrHc_j9.cjs → dom-DlLH3FxV.cjs} +1 -1
- package/dist/esm/form/index.js +2 -4
- package/dist/esm/index.js +225 -186
- package/dist/esm/plugins/dom-observer/index.js +1 -1
- package/dist/esm/plugins/dynamic-experiences/index.js +3 -3
- package/dist/esm/preview/index.js +1 -1
- package/dist/esm/server/index.js +33 -31
- package/dist/{experience-instance-DOJShRk5.js → experience-instance-DGk11d8a.js} +3 -3
- package/dist/experience-instance-Dej3P1sE.cjs +1 -0
- package/dist/plugin-CHOVsx7Z.js +287 -0
- package/dist/plugin-DsaGXQHx.cjs +1 -0
- package/dist/{preview-channel-DW9J8t4B.js → preview-channel-CSr96XAo.js} +1 -1
- package/dist/{protocol-DSqOlW2r.js → protocol-C5KkkiZc.js} +1 -1
- package/dist/types/client.d.ts +7 -6
- package/dist/types/data-source/api.d.ts +60 -0
- package/dist/types/data-source/api.d.ts.map +1 -0
- package/dist/types/data-source/provider.d.ts +97 -0
- package/dist/types/data-source/provider.d.ts.map +1 -0
- package/dist/types/entities/action/action.d.ts +5 -4
- package/dist/types/entities/action/action.d.ts.map +1 -1
- package/dist/types/entities/component/component.d.ts +6 -5
- package/dist/types/entities/component/component.d.ts.map +1 -1
- package/dist/types/entities/condition/condition.d.ts +5 -5
- package/dist/types/entities/content/content.d.ts +4 -3
- package/dist/types/entities/experience/experience.d.ts +11 -10
- package/dist/types/entities/experience/experience.d.ts.map +1 -1
- package/dist/types/entities/experience-instance/experience-instance.d.ts +10 -9
- package/dist/types/entities/feature/feature.d.ts +19 -2
- package/dist/types/entities/feature/feature.d.ts.map +1 -1
- package/dist/types/entities/field/field.d.ts +7 -6
- package/dist/types/entities/framework/framework.d.ts +7 -6
- package/dist/types/entities/framework/framework.d.ts.map +1 -1
- package/dist/types/entities/history/history.d.ts +6 -6
- package/dist/types/entities/plugin/plugin.d.ts +3 -2
- package/dist/types/entities/plugin/plugin.d.ts.map +1 -1
- package/dist/types/entities/predicate/predicate.d.ts +4 -3
- package/dist/types/entities/predicate/predicate.d.ts.map +1 -1
- package/dist/types/entities/response/response-validation.d.ts +3 -2
- package/dist/types/entities/response/response.d.ts +12 -11
- package/dist/types/entities/session/session.d.ts +2 -1
- package/dist/types/entities/step/step.d.ts +6 -5
- package/dist/types/exports.d.ts +2 -1
- package/dist/types/exports.d.ts.map +1 -1
- package/dist/types/form/build-responses.d.ts +2 -1
- package/dist/types/form/create-field-evaluator.d.ts +4 -3
- package/dist/types/form/create-field-validation.d.ts +5 -4
- package/dist/types/form/create-form.d.ts +7 -6
- package/dist/types/form/create-submit-pipeline.d.ts +7 -6
- package/dist/types/form/get-step-fields.d.ts +3 -2
- package/dist/types/lifecycle/condition-selection.d.ts +14 -4
- package/dist/types/lifecycle/lifecycle.d.ts +4 -3
- package/dist/types/lifecycle/rules-logic.d.ts +4 -3
- package/dist/types/lifecycle/state-snapshot.d.ts +4 -3
- package/dist/types/picked.d.ts +79 -62
- package/dist/types/picked.d.ts.map +1 -1
- package/dist/types/ping.d.ts +1 -1
- package/dist/types/ping.d.ts.map +1 -1
- package/dist/types/plugins/create-experience/create-experience.d.ts +9 -8
- package/dist/types/plugins/create-experience/create-experience.d.ts.map +1 -1
- package/dist/types/plugins/create-experience/plugin.d.ts +3 -2
- package/dist/types/plugins/create-experience/plugin.d.ts.map +1 -1
- package/dist/types/plugins/create-experience/serialization.d.ts.map +1 -1
- package/dist/types/plugins/dom-observer/plugin.d.ts +1 -1
- package/dist/types/plugins/dynamic-experiences/hash.d.ts +40 -5
- package/dist/types/plugins/dynamic-experiences/hash.d.ts.map +1 -1
- package/dist/types/plugins/dynamic-experiences/index.d.ts +2 -0
- package/dist/types/plugins/dynamic-experiences/index.d.ts.map +1 -1
- package/dist/types/plugins/dynamic-experiences/plugin.d.ts +4 -36
- package/dist/types/plugins/dynamic-experiences/plugin.d.ts.map +1 -1
- package/dist/types/plugins/dynamic-experiences/types.d.ts +42 -11
- package/dist/types/plugins/dynamic-experiences/types.d.ts.map +1 -1
- package/dist/types/plugins/experience-browser-storage/plugin.d.ts +1 -1
- package/dist/types/plugins/experience-browser-storage/plugin.d.ts.map +1 -1
- package/dist/types/plugins/history-browser-storage/plugin.d.ts +1 -1
- package/dist/types/plugins/history-browser-storage/plugin.d.ts.map +1 -1
- package/dist/types/plugins/responses-browser-storage/plugin.d.ts +1 -1
- package/dist/types/plugins/responses-browser-storage/plugin.d.ts.map +1 -1
- package/dist/types/plugins/responses-indexeddb/plugin.d.ts +1 -1
- package/dist/types/plugins/single-page-app/plugin.d.ts +1 -1
- package/dist/types/plugins/single-page-app/plugin.d.ts.map +1 -1
- package/dist/types/plugins/web-components/plugin.d.ts +2 -1
- package/dist/types/plugins/window-object/plugin.d.ts +2 -2
- package/dist/types/preview-channel.d.ts +5 -1
- package/dist/types/preview-receiver/index.d.ts +18 -1
- package/dist/types/preview-receiver/protocol.d.ts +10 -1
- package/dist/types/server/index.d.ts +3 -3
- package/dist/types/server/serialize.d.ts +8 -2
- package/dist/types/server/serialize.d.ts.map +1 -1
- package/dist/types/utils/dom.d.ts +2 -1
- package/dist/types/utils/events.d.ts +6 -6
- package/dist/types/utils/events.d.ts.map +1 -1
- package/dist/types/utils/global-obj.d.ts +1 -1
- package/dist/types/utils/global-obj.d.ts.map +1 -1
- package/dist/types/utils/id.d.ts.map +1 -1
- package/dist/types/utils/logger.d.ts +2 -1
- package/dist/types/utils/logger.d.ts.map +1 -1
- package/dist/types/utils/node-tree/get-next-node-from-action.d.ts +2 -1
- package/dist/types/utils/node-tree/get-next-node-from-condition.d.ts +2 -1
- package/dist/types/utils/node-tree/get-next-node.d.ts +1 -1
- package/dist/types/utils/node-tree/get-next-node.d.ts.map +1 -1
- package/dist/types/utils/node-tree/get-origin-node.d.ts +1 -1
- package/dist/types/utils/node-tree/get-origin-node.d.ts.map +1 -1
- package/dist/types/utils/node-tree/min-max-steps-to-leaf.d.ts +1 -1
- package/dist/types/utils/node-tree/min-max-steps-to-leaf.d.ts.map +1 -1
- package/dist/types/utils/node-tree/min-max-steps-to-origin.d.ts +1 -1
- package/dist/types/utils/node-tree/min-max-steps-to-origin.d.ts.map +1 -1
- package/dist/types/utils/node-tree/next-reachable-step-nodes.d.ts +1 -1
- package/dist/types/utils/node-tree/next-reachable-step-nodes.d.ts.map +1 -1
- package/dist/types/utils/nodes.d.ts +1 -1
- package/dist/types/utils/picked-config.d.ts +2 -1
- package/dist/types/utils/rehydrate-scripts.d.ts +6 -0
- package/dist/types/utils/rehydrate-scripts.d.ts.map +1 -1
- package/dist/types/utils/rules-logic.d.ts +6 -2
- package/dist/types/utils/storage.d.ts +1 -1
- package/dist/types/utils/storage.d.ts.map +1 -1
- package/package.json +22 -23
- package/dist/api-CPL40b7A.js +0 -40
- package/dist/api-Dz5rgRgQ.cjs +0 -1
- package/dist/docs/actions.mdx +0 -78
- package/dist/docs/api/authentication.md +0 -376
- package/dist/docs/api/error-handling.md +0 -211
- package/dist/docs/api/index.md +0 -197
- package/dist/docs/api-keys.mdx +0 -40
- package/dist/docs/chrome-extension.md +0 -51
- package/dist/docs/conditions.mdx +0 -59
- package/dist/docs/content/asset-library.mdx +0 -93
- package/dist/docs/content/content-entries.mdx +0 -30
- package/dist/docs/content/layouts.mdx +0 -70
- package/dist/docs/content/pages.mdx +0 -67
- package/dist/docs/content/sections.mdx +0 -33
- package/dist/docs/delivery-api/index.md +0 -46
- package/dist/docs/delivery-api/preview-caching-crawling.md +0 -59
- package/dist/docs/experiences.mdx +0 -42
- package/dist/docs/features.mdx +0 -58
- package/dist/docs/fields.mdx +0 -79
- package/dist/docs/flow-builder/building-flows.md +0 -61
- package/dist/docs/flow-builder/condition-builder.md +0 -54
- package/dist/docs/flow-builder/keyboard-shortcuts.md +0 -66
- package/dist/docs/flow-builder/nodes.md +0 -43
- package/dist/docs/flow-builder/overview.mdx +0 -50
- package/dist/docs/gtm-template.md +0 -69
- package/dist/docs/guides/build-a-personalized-page.mdx +0 -143
- package/dist/docs/image-optimisation-service.md +0 -80
- package/dist/docs/mcp.md +0 -120
- package/dist/docs/pickedql/data-sources.md +0 -145
- package/dist/docs/pickedql/dimensions.md +0 -65
- package/dist/docs/pickedql/index.md +0 -36
- package/dist/docs/pickedql/schema.md +0 -156
- package/dist/docs/pickedql/virtual-functions.md +0 -204
- package/dist/docs/projects.mdx +0 -37
- package/dist/docs/reports.mdx +0 -18
- package/dist/docs/sdk/actions.md +0 -66
- package/dist/docs/sdk/content-security-policy.md +0 -32
- package/dist/docs/sdk/experiences.md +0 -46
- package/dist/docs/sdk/features.md +0 -212
- package/dist/docs/sdk/fields.md +0 -263
- package/dist/docs/sdk/index.md +0 -83
- package/dist/docs/sdk/plugins/dynamic-experiences.md +0 -58
- package/dist/docs/sdk/plugins/index.md +0 -9
- package/dist/docs/sdk/predicates.md +0 -112
- package/dist/docs/sdk/ssr/index.md +0 -62
- package/dist/docs/sdk/ssr/rendering-and-hydration.md +0 -141
- package/dist/docs/sdk/ssr/serialize.md +0 -86
- package/dist/docs/sdk/ssr/slots-and-placements.md +0 -35
- package/dist/docs/sdk/steps.md +0 -434
- package/dist/docs/steps.mdx +0 -44
- package/dist/docs/team-management.md +0 -40
- package/dist/docs/vite-plugin.md +0 -82
- package/dist/docs/workspace-versioning.mdx +0 -28
- package/dist/docs/workspaces.mdx +0 -102
- package/dist/experience-instance-DurC3pE-.cjs +0 -1
- package/dist/plugin-CFXwbXeM.js +0 -247
- package/dist/plugin-m4qekgtA.cjs +0 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: reference
|
|
3
|
+
layout: docs
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Virtual functions
|
|
7
|
+
|
|
8
|
+
A virtual function is a short form of an aggregate expression. Each one is
|
|
9
|
+
**table-aware**. The same function expands one way on `metrics`, where PickedQL
|
|
10
|
+
aggregates the data first. It expands another way on `events`, where there is one row
|
|
11
|
+
for each user.
|
|
12
|
+
|
|
13
|
+
## A/B test metrics
|
|
14
|
+
|
|
15
|
+
| Function | Description |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `control_users([condition])` | The different users in the Control group. A condition of your own can narrow them. |
|
|
18
|
+
| `variant_users([condition])` | The different users in the Variant group. A condition of your own can narrow them. |
|
|
19
|
+
| `all_users()` | Every different user |
|
|
20
|
+
| `control_sessions([condition])` | The different sessions in the Control group. A condition of your own can narrow them. |
|
|
21
|
+
| `variant_sessions([condition])` | The different sessions in the Variant group. A condition of your own can narrow them. |
|
|
22
|
+
| `all_sessions()` | Every different session |
|
|
23
|
+
|
|
24
|
+
### Your own filters on `events`
|
|
25
|
+
|
|
26
|
+
On the `events` table, each `control_*` function and each `variant_*` function takes
|
|
27
|
+
**one optional filter argument**. The filter counts only a user or a session that
|
|
28
|
+
matches your expression. It applies together with the `variant_type` filter that
|
|
29
|
+
PickedQL adds. Give the argument in the first place, or by the name `filter=`.
|
|
30
|
+
|
|
31
|
+
The filter uses the same `column=value` syntax as a conversion-rate condition. It
|
|
32
|
+
takes the `AND` and `OR` operators, and parentheses can hold a set of terms.
|
|
33
|
+
|
|
34
|
+
```sql
|
|
35
|
+
-- The default: every Control user.
|
|
36
|
+
SELECT control_users() FROM events
|
|
37
|
+
|
|
38
|
+
-- Your own filter, in the first place.
|
|
39
|
+
SELECT control_users('event_type=app_initialize') FROM events
|
|
40
|
+
SELECT variant_users('event_type=app_initialize AND step_id=xxx') FROM events
|
|
41
|
+
|
|
42
|
+
-- The AND and OR operators, with parentheses.
|
|
43
|
+
SELECT control_users('event_type=app_initialize OR event_type=app_load') FROM events
|
|
44
|
+
SELECT control_users('event_type=form_submit AND (step_id=lead-capture OR step_id=xxx)') FROM events
|
|
45
|
+
|
|
46
|
+
-- Sessions
|
|
47
|
+
SELECT control_sessions('event_type=step_enter') FROM events
|
|
48
|
+
SELECT variant_sessions('event_type=step_enter AND step_id=checkout') FROM events
|
|
49
|
+
|
|
50
|
+
-- Named form
|
|
51
|
+
SELECT control_users(filter='event_type=app_initialize AND step_id=xxx') FROM events
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
:::note
|
|
55
|
+
A filter of your own is **not supported on `metrics`**, because PickedQL aggregates
|
|
56
|
+
that data first. On `metrics` these functions ignore every argument. They use the
|
|
57
|
+
default Control filter and Variant filter only.
|
|
58
|
+
:::
|
|
59
|
+
|
|
60
|
+
## Conversion metrics
|
|
61
|
+
|
|
62
|
+
| Function | Description |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `all_purchase_users()` | Users who completed a purchase |
|
|
65
|
+
| `control_purchase_users()` | Control users who purchased (`metrics` only) |
|
|
66
|
+
| `variant_purchase_users()` | Variant users who purchased (`metrics` only) |
|
|
67
|
+
| `all_purchase_sessions()` | Sessions with a purchase (`metrics` only) |
|
|
68
|
+
| `all_purchase_orders()` | Unique order count (`metrics` only) |
|
|
69
|
+
| `all_cart_add_users()` | Users who added to cart |
|
|
70
|
+
|
|
71
|
+
## Conversion rates
|
|
72
|
+
|
|
73
|
+
| Function | Description |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `control_conversion_rate([numerator [, denominator]])` | The conversion rate of the Control group. By default it divides the users with a purchase by all users. |
|
|
76
|
+
| `variant_conversion_rate([numerator [, denominator]])` | The conversion rate of the Variant group. It takes the same arguments. |
|
|
77
|
+
| `conversion_rate_uplift([numerator [, denominator]])` | The gain of the Variant over the Control: `(variant CR - control CR) / control CR`. It takes the same arguments. |
|
|
78
|
+
|
|
79
|
+
### Your own conditions on `events`
|
|
80
|
+
|
|
81
|
+
On the `events` table, each conversion-rate function takes up to **two optional
|
|
82
|
+
arguments**. They set the numerator and the denominator. Each argument is either a
|
|
83
|
+
**condition expression string** or a **nested virtual function call**. See below.
|
|
84
|
+
Give the arguments in order, or by the names `numerator=` and `denominator=`.
|
|
85
|
+
|
|
86
|
+
Each condition expression uses `column=value`, with the `AND` and `OR` operators.
|
|
87
|
+
Parentheses can hold a set of terms.
|
|
88
|
+
|
|
89
|
+
```sql
|
|
90
|
+
-- Default: purchase users / all users
|
|
91
|
+
SELECT control_conversion_rate() FROM events
|
|
92
|
+
|
|
93
|
+
-- Custom numerator, default denominator (all users)
|
|
94
|
+
SELECT control_conversion_rate('event_type=form_submit_success') FROM events
|
|
95
|
+
|
|
96
|
+
-- The AND and OR operators in a condition.
|
|
97
|
+
SELECT control_conversion_rate('event_type=checkout_complete OR event_type=order_create') FROM events
|
|
98
|
+
SELECT control_conversion_rate('step_id=lead-capture AND event_type=form_submit_success') FROM events
|
|
99
|
+
|
|
100
|
+
-- Parentheses hold a set of terms.
|
|
101
|
+
SELECT control_conversion_rate('event_type=form_submit AND (step_id=lead-capture OR step_id=xxx)') FROM events
|
|
102
|
+
|
|
103
|
+
-- Custom numerator AND denominator (positional)
|
|
104
|
+
SELECT control_conversion_rate('event_type=form_submit', 'step_id=lead-capture') FROM events
|
|
105
|
+
|
|
106
|
+
-- Named arguments (explicit, any order)
|
|
107
|
+
SELECT control_conversion_rate(numerator='event_type=form_submit', denominator='step_id=lead-capture') FROM events
|
|
108
|
+
|
|
109
|
+
-- Named: denominator only (numerator defaults to purchase)
|
|
110
|
+
SELECT control_conversion_rate(denominator='step_id=lead-capture') FROM events
|
|
111
|
+
|
|
112
|
+
-- The uplift, with the arguments given by name.
|
|
113
|
+
SELECT conversion_rate_uplift(numerator='event_type=form_submit', denominator='step_id=lead-capture') FROM events
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### A nested virtual function as the numerator or the denominator
|
|
117
|
+
|
|
118
|
+
You can pass a **virtual function call** as the numerator or the denominator, in
|
|
119
|
+
place of a condition string. PickedQL puts the inner aggregate there directly. It
|
|
120
|
+
adds no `variant_type` filter above it, because the nested function sets its own
|
|
121
|
+
filter.
|
|
122
|
+
|
|
123
|
+
This form is the most flexible one when the numerator and the denominator come from
|
|
124
|
+
different segments. An example is the Control users who fired one event, over **all**
|
|
125
|
+
users of every variant:
|
|
126
|
+
|
|
127
|
+
```sql
|
|
128
|
+
-- The Control users who fired `app_initialize`, over all users.
|
|
129
|
+
SELECT control_conversion_rate(
|
|
130
|
+
control_users('event_type=app_initialize'),
|
|
131
|
+
all_users()
|
|
132
|
+
) FROM events
|
|
133
|
+
|
|
134
|
+
-- Mix string and nested forms
|
|
135
|
+
SELECT control_conversion_rate(
|
|
136
|
+
'event_type=form_submit',
|
|
137
|
+
all_users()
|
|
138
|
+
) FROM events
|
|
139
|
+
|
|
140
|
+
-- The uplift, with a nested function in each place.
|
|
141
|
+
SELECT conversion_rate_uplift(
|
|
142
|
+
control_users('event_type=app_initialize'),
|
|
143
|
+
all_users()
|
|
144
|
+
) FROM events
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
:::note
|
|
148
|
+
A condition of your own, and a nested virtual function, are both **not supported on
|
|
149
|
+
`metrics`**, because PickedQL aggregates that data first. On `metrics` these
|
|
150
|
+
functions always divide the users with a purchase by all users.
|
|
151
|
+
:::
|
|
152
|
+
|
|
153
|
+
## Visitor segmentation (`metrics` only)
|
|
154
|
+
|
|
155
|
+
| Function | Description |
|
|
156
|
+
|---|---|
|
|
157
|
+
| `new_users()` | First-time visitors |
|
|
158
|
+
| `returning_users()` | Returning visitors |
|
|
159
|
+
| `new_sessions()` | Sessions from new visitors |
|
|
160
|
+
| `returning_sessions()` | Sessions from returning visitors |
|
|
161
|
+
|
|
162
|
+
## Funnel metrics (`events` only)
|
|
163
|
+
|
|
164
|
+
:::note
|
|
165
|
+
We add to the funnel metrics all the time. Read this page again for a new function.
|
|
166
|
+
:::
|
|
167
|
+
|
|
168
|
+
| Function | Description |
|
|
169
|
+
|---|---|
|
|
170
|
+
| `step_enter_users()` | Users who entered a funnel step |
|
|
171
|
+
| `step_enter_sessions()` | Sessions that entered a funnel step |
|
|
172
|
+
|
|
173
|
+
## Cohort filters (`events` only)
|
|
174
|
+
|
|
175
|
+
Each of these functions has the shape of a predicate. It writes a
|
|
176
|
+
`user_canonical_id IN (SELECT … FROM events …)` subquery. Put one in a `WHERE` clause
|
|
177
|
+
to limit any analysis to a user with at least one event that matches a condition of
|
|
178
|
+
your own.
|
|
179
|
+
|
|
180
|
+
| Function | Description |
|
|
181
|
+
|---|---|
|
|
182
|
+
| `user_cohort_filter('<condition>')` | Limits the query to a user with one or more `events` rows that match `<condition>` |
|
|
183
|
+
| `user_cohort_filter(filter='<condition>')` | The same, with the argument given by name |
|
|
184
|
+
|
|
185
|
+
`<condition>` uses the same short `column=value [AND\|OR …]` form as `control_users`
|
|
186
|
+
and `variant_users`.
|
|
187
|
+
|
|
188
|
+
```sql
|
|
189
|
+
-- Funnel scoped to users who eventually submitted lead capture
|
|
190
|
+
SELECT all_users(), all_purchase_users()
|
|
191
|
+
FROM events
|
|
192
|
+
WHERE project_id = 'proj-abc'
|
|
193
|
+
AND user_cohort_filter('event_type=lead_capture')
|
|
194
|
+
|
|
195
|
+
-- Multi-condition cohort with OR-grouping
|
|
196
|
+
SELECT all_sessions()
|
|
197
|
+
FROM events
|
|
198
|
+
WHERE project_id = 'proj-abc'
|
|
199
|
+
AND user_cohort_filter('event_type=signup OR event_type=trial_start')
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The outer query MUST hold one `project_id` filter. The function puts that tenant into
|
|
203
|
+
the inner subquery. The read of the cohort therefore covers your project, and not the
|
|
204
|
+
whole warehouse.
|
|
205
|
+
|
|
206
|
+
## Feature functions, on `features` and on `events`
|
|
207
|
+
|
|
208
|
+
Parameterized functions that accept the feature name, bucket, and an optional feature key:
|
|
209
|
+
|
|
210
|
+
| Function | Description |
|
|
211
|
+
|---|---|
|
|
212
|
+
| `feature_users('name', 'bucket' [, 'key'])` | Unique users in a feature bucket |
|
|
213
|
+
| `feature_sessions('name', 'bucket' [, 'key'])` | Unique sessions in a feature bucket |
|
|
214
|
+
| `feature_events('name', 'bucket' [, 'key'])` | Unique events in a feature bucket |
|
|
215
|
+
|
|
216
|
+
```sql
|
|
217
|
+
-- Compare feature buckets
|
|
218
|
+
SELECT
|
|
219
|
+
feature_users('homepage_test', 'control') AS control,
|
|
220
|
+
feature_users('homepage_test', 'variant') AS variant
|
|
221
|
+
FROM features
|
|
222
|
+
|
|
223
|
+
-- With feature key filter
|
|
224
|
+
SELECT feature_users('homepage_test', 'control', 'v1') FROM features
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Other (`metrics` only)
|
|
228
|
+
|
|
229
|
+
| Function | Description |
|
|
230
|
+
|---|---|
|
|
231
|
+
| `event_count()` | Total event count (from pre-aggregation) |
|
|
232
|
+
|
|
233
|
+
## Related
|
|
234
|
+
|
|
235
|
+
- [Filters](/reference/pickedql/filtering) — the filter string a function takes.
|
|
236
|
+
- [Analytics queries](/how-to/analytics)
|
|
237
|
+
- [About PickedQL](/explanation/how-pickedql-works)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: reference
|
|
3
|
+
layout: docs
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Actions
|
|
7
|
+
|
|
8
|
+
Register an action handler on the Picked client. The SDK then invokes the handler
|
|
9
|
+
when an experience flow reaches an action node.
|
|
10
|
+
|
|
11
|
+
## Outcome and branch
|
|
12
|
+
|
|
13
|
+
| Handler outcome | Branch the flow takes |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| Throws, or its promise rejects | `onFail` |
|
|
16
|
+
| Resolves to any value, and that includes `false` | `onSuccess` |
|
|
17
|
+
|
|
18
|
+
An action node can also carry one plain connection, with no success or failure branch. The
|
|
19
|
+
flow then continues through it on either outcome.
|
|
20
|
+
|
|
21
|
+
An action does not interrupt your visitor. The current step stays on the screen while an
|
|
22
|
+
async action runs.
|
|
23
|
+
|
|
24
|
+
## Related
|
|
25
|
+
|
|
26
|
+
- [How to register an action handler](/how-to/register-an-action-handler) — the handler,
|
|
27
|
+
and when to throw.
|
|
28
|
+
- [About how Picked works](/explanation/how-picked-works#how-an-action-decides-which-way-to-go)
|
|
29
|
+
— why a returned `false` is a success.
|
|
30
|
+
- [Actions](/reference/dashboard/actions) — the dashboard options.
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
---
|
|
2
|
+
type: reference
|
|
2
3
|
layout: docs
|
|
3
4
|
---
|
|
4
5
|
|
|
5
6
|
# Conditions
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
A condition uses JSON Logic to evaluate the client state. Use a condition to control
|
|
9
|
+
when an experience starts, to choose the next step, and to show or hide a field.
|
|
8
10
|
|
|
9
11
|
## Conditions in experiences
|
|
10
12
|
|
|
11
|
-
A common pattern
|
|
13
|
+
A common pattern puts a condition between two steps. The flow then takes a branch
|
|
14
|
+
that the form responses decide:
|
|
12
15
|
|
|
13
16
|
```ts
|
|
14
17
|
import { createExperience } from '@picked/client/plugins/create-experience'
|
|
@@ -47,12 +50,13 @@ isSigningUpForNewsletter.addFalse(userDoesNotWantNewsletter)
|
|
|
47
50
|
|
|
48
51
|
## Conditional fields
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
A JSON Logic condition can show or hide a field. The condition can read an earlier
|
|
54
|
+
response or a [predicate](/reference/sdk/predicates):
|
|
51
55
|
|
|
52
56
|
```ts
|
|
53
57
|
import { createField } from '@picked/client/plugins/create-experience'
|
|
54
58
|
|
|
55
|
-
// Show email field only
|
|
59
|
+
// Show the email field only when the user chose "email" as a contact method.
|
|
56
60
|
export const emailField = createField({
|
|
57
61
|
id: 'email-input',
|
|
58
62
|
name: 'email-input',
|
|
@@ -70,7 +74,7 @@ export const emailField = createField({
|
|
|
70
74
|
},
|
|
71
75
|
})
|
|
72
76
|
|
|
73
|
-
// Show field
|
|
77
|
+
// Show the field when the predicate returns true.
|
|
74
78
|
export const mailingAddressField = createField({
|
|
75
79
|
id: 'mailing-address',
|
|
76
80
|
name: 'mailing-address',
|
|
@@ -135,3 +139,9 @@ export const contactMethodsField = createField({
|
|
|
135
139
|
],
|
|
136
140
|
})
|
|
137
141
|
```
|
|
142
|
+
|
|
143
|
+
## Related
|
|
144
|
+
|
|
145
|
+
- [About conditions and the logic engine](/explanation/conditions-and-the-logic-engine)
|
|
146
|
+
- [How to write a condition](/how-to/write-a-condition)
|
|
147
|
+
- [Predicates](/reference/sdk/predicates)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: reference
|
|
3
|
+
layout: docs
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Content Security Policy
|
|
7
|
+
|
|
8
|
+
The Picked SDK loads resources from the domains below. Add each domain to the CSP
|
|
9
|
+
directive that the table gives.
|
|
10
|
+
|
|
11
|
+
## Required domains
|
|
12
|
+
|
|
13
|
+
| Domain | Directive | Purpose |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `api.picked.app` | `connect-src` | API requests, project configuration, experiences, and the record of each session |
|
|
16
|
+
| `evnts.pckd.dev` | `connect-src` | Analytics events, which Snowplow collects |
|
|
17
|
+
| `img.picked-content.com` | `img-src` | Optimised images, which the [image optimisation service](/reference/tools/image-optimisation-service) serves |
|
|
18
|
+
|
|
19
|
+
## Related
|
|
20
|
+
|
|
21
|
+
- [How to set a Content Security Policy](/how-to/set-a-content-security-policy)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: reference
|
|
3
|
+
layout: docs
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Experiences
|
|
7
|
+
|
|
8
|
+
Define an experience in code with `createExperience()`. You can also load an
|
|
9
|
+
experience from the dashboard at runtime with the
|
|
10
|
+
[Dynamic Experiences Plugin](/reference/sdk/plugins/dynamic-experiences).
|
|
11
|
+
|
|
12
|
+
The flow below points at a DOM selector and renders into it. A Page experience
|
|
13
|
+
renders through a named slot instead. Point it at `.picked-slot-<name>` and render
|
|
14
|
+
`<ExperienceSlot name="<name>" />` on your server. Use the slot path for a Page that
|
|
15
|
+
the server renders and that search engines must read. Use the selector path for a
|
|
16
|
+
Flow that the browser renders. See [Server-Side Rendering](/reference/sdk/ssr) and
|
|
17
|
+
[Slots and Placements](/reference/sdk/ssr/slots-and-placements).
|
|
18
|
+
|
|
19
|
+
## Create an experience
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { createExperience } from '@picked/client/plugins/create-experience'
|
|
23
|
+
import { adaptSvelteComponent } from '@picked/svelte'
|
|
24
|
+
import appCss from '../app.css?inline'
|
|
25
|
+
|
|
26
|
+
export const productPageExperience = createExperience({
|
|
27
|
+
id: 'product-page-experience',
|
|
28
|
+
name: 'Product Page Experience',
|
|
29
|
+
condition: {
|
|
30
|
+
'==': [
|
|
31
|
+
{ var: 'location.pathname' },
|
|
32
|
+
'/product/',
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
targets: [{ selector: 'body', placement: 'after' }],
|
|
36
|
+
component: adaptSvelteComponent(
|
|
37
|
+
import('../ui/ProductPageExperience.svelte'),
|
|
38
|
+
{ css: [appCss] },
|
|
39
|
+
),
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
To register an experience that you define in code, use the
|
|
44
|
+
[Create Experience Plugin](/reference/sdk/plugins/create-experience). To read an experience from
|
|
45
|
+
the dashboard at runtime and control it there, use the
|
|
46
|
+
[Dynamic Experiences Plugin](/reference/sdk/plugins/dynamic-experiences).
|
|
47
|
+
|
|
48
|
+
## Related
|
|
49
|
+
|
|
50
|
+
- [About how Picked works](/explanation/how-picked-works) — the startup order.
|
|
51
|
+
- [How to register a component](/how-to/register-a-component)
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: reference
|
|
3
|
+
layout: docs
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Feature flags
|
|
7
|
+
|
|
8
|
+
A feature flag lets you run an A/B test and roll a change out in stages. Control a
|
|
9
|
+
feature flag from the dashboard, or define it in SDK code when you need your own decision
|
|
10
|
+
rules.
|
|
11
|
+
|
|
12
|
+
## Where feature flags come from
|
|
13
|
+
|
|
14
|
+
You control most feature flags from the dashboard. The
|
|
15
|
+
[dynamic experiences plugin](/reference/sdk/plugins/dynamic-experiences) reads the options of
|
|
16
|
+
your project at runtime and registers every feature flag for you. You declare no feature flag in
|
|
17
|
+
code.
|
|
18
|
+
|
|
19
|
+
For a feature flag that you control from the dashboard, the SDK makes the definition:
|
|
20
|
+
|
|
21
|
+
| Field | Value |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| `id` | The dashboard key of the feature flag |
|
|
24
|
+
| `key` | `v{version}`, for example `v3` |
|
|
25
|
+
| `name` | The dashboard name of the feature flag |
|
|
26
|
+
| `enabled` | The dashboard **Enabled** toggle |
|
|
27
|
+
| `weight` | The dashboard **Weight**, 0 to 100 |
|
|
28
|
+
| `storage` | `local`, or `session` in [preview mode](/explanation/feature-flag-allocation#preview-mode) |
|
|
29
|
+
| `decide` | Made by the SDK. It compares the stable value of the visitor for this feature flag against `weight`. |
|
|
30
|
+
|
|
31
|
+
The `decide` function that the SDK makes derives from the visitor ID, the ID of the
|
|
32
|
+
feature flag, and the weight. A feature flag that you control from the dashboard
|
|
33
|
+
therefore cannot use the data of your application. Define the feature flag in SDK code
|
|
34
|
+
when you need that.
|
|
35
|
+
|
|
36
|
+
## Define a feature flag in SDK code
|
|
37
|
+
|
|
38
|
+
Define your feature flags when you create the Picked client. Pass a `features` object. Use
|
|
39
|
+
this when a decision needs data that only your application holds. Each feature flag needs a
|
|
40
|
+
unique identifier, a version key, a name that a person can read, and a `decide`
|
|
41
|
+
function:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { createClient } from '@picked/client'
|
|
45
|
+
|
|
46
|
+
const client = createClient({
|
|
47
|
+
projectId: 'your-project-id',
|
|
48
|
+
features: {
|
|
49
|
+
'summer-sale': {
|
|
50
|
+
key: 'summer-sale-v1', // The version key. Change it to invalidate the cache.
|
|
51
|
+
name: 'Summer Sale Experiment', // A name for analytics that a person can read.
|
|
52
|
+
decide: async () => {
|
|
53
|
+
// Put your decision rules here.
|
|
54
|
+
const isEligible = await checkUserEligibility()
|
|
55
|
+
return isEligible // Returns a boolean.
|
|
56
|
+
},
|
|
57
|
+
storage: 'local', // Optional: 'local' is the default, or 'session'.
|
|
58
|
+
},
|
|
59
|
+
'homepage-variant': {
|
|
60
|
+
key: 'homepage-test-v2',
|
|
61
|
+
name: 'Homepage A/B Test',
|
|
62
|
+
decide: async () => {
|
|
63
|
+
// Return a string for a test of more than two variants.
|
|
64
|
+
return Math.random() < 0.5 ? 'variant-a' : 'variant-b'
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
A feature flag can also be a factory function, which starts the feature flag only when it is
|
|
72
|
+
first needed:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
features: {
|
|
76
|
+
'my-feature': () => ({
|
|
77
|
+
key: 'my-feature-v1',
|
|
78
|
+
name: 'My Feature',
|
|
79
|
+
decide: async () => true,
|
|
80
|
+
}),
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Overrides in the URL
|
|
85
|
+
|
|
86
|
+
You can override a feature flag decision from the URL search parameters, for a test:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
https://example.com?picked-feature=my-feature:true
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
You can give more than one override value:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
https://example.com?picked-feature=my-feature:true&picked-feature=homepage-variant:variant-a
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The format is `?picked-feature=<feature-id>:<bucket-value>`.
|
|
99
|
+
|
|
100
|
+
This is useful for QA, because you can test one bucket value and change no code. The
|
|
101
|
+
SDK keeps the override value in session storage. The browser clears it when you close
|
|
102
|
+
the tab.
|
|
103
|
+
|
|
104
|
+
## Use a feature flag in an experience condition
|
|
105
|
+
|
|
106
|
+
You can use a feature flag in an experience condition through JSON logic:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const myExperience = createExperience({
|
|
110
|
+
// The other options go here.
|
|
111
|
+
condition: {
|
|
112
|
+
and: [
|
|
113
|
+
{
|
|
114
|
+
'==': [
|
|
115
|
+
{
|
|
116
|
+
invoke: {
|
|
117
|
+
var: 'features.summer-sale', // Point at the feature flag by its ID.
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
true, // Check that the bucket equals true.
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The ID in `features.<id>` is the **Key** of the feature flag from the dashboard. It is the
|
|
129
|
+
same value that the detail page of the feature flag shows.
|
|
130
|
+
|
|
131
|
+
An evaluation of a condition reads the bucket. Picked therefore allocates a visitor
|
|
132
|
+
to a feature flag when the condition that points at it runs.
|
|
133
|
+
|
|
134
|
+
## Watch for feature flag decisions
|
|
135
|
+
|
|
136
|
+
Subscribe to the `feature_decide` event to track when a feature flag resolves:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
client.eventBus.watch((event) => {
|
|
140
|
+
if (event.type !== 'feature_decide') return
|
|
141
|
+
|
|
142
|
+
const { feature } = event
|
|
143
|
+
console.log(`Flag "${feature.id}" resolved to bucket:`, feature.bucket)
|
|
144
|
+
// Example output: the feature flag resolved to the bucket true.
|
|
145
|
+
|
|
146
|
+
// Send the decision to your analytics.
|
|
147
|
+
analytics.track('Feature Decision', {
|
|
148
|
+
featureId: feature.id,
|
|
149
|
+
bucket: feature.bucket,
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
A watch on `eventBus` reports only what happens after you subscribe.
|
|
155
|
+
|
|
156
|
+
## Read the events that already happened
|
|
157
|
+
|
|
158
|
+
`client.events` is a store of every event since the client started, in order. Each entry
|
|
159
|
+
holds an `id`, the time `at`, and the `payload` that went on the bus.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
const alreadyAllocated = client.events
|
|
163
|
+
.getState()
|
|
164
|
+
.filter((entry) => entry.payload.type === 'feature_decide')
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Use it when your code starts after Picked does, and a `feature_decide` that you needed
|
|
168
|
+
already happened before you subscribed.
|
|
169
|
+
|
|
170
|
+
## Versions and cache invalidation
|
|
171
|
+
|
|
172
|
+
The SDK caches a feature flag decision in browser storage. It uses `localStorage`, or
|
|
173
|
+
`sessionStorage` in a preview. The cache key derives from the feature flag ID and the version,
|
|
174
|
+
so a visitor keeps the same decision across page loads.
|
|
175
|
+
|
|
176
|
+
:::note
|
|
177
|
+
The SDK keeps a bucket value in storage as text. It reads `'true'` and `'false'` back as
|
|
178
|
+
booleans. A variant with the name `true` or `false` therefore comes back as a boolean.
|
|
179
|
+
Give each variant a different name.
|
|
180
|
+
:::
|
|
181
|
+
|
|
182
|
+
### How versions work
|
|
183
|
+
|
|
184
|
+
A feature flag that you create in the dashboard starts at version 1. The SDK uses the
|
|
185
|
+
version to build the cache key. A feature flag at version 3 caches under `v3`.
|
|
186
|
+
|
|
187
|
+
The version affects **the cache only**. It is not part of the allocation. For a feature flag
|
|
188
|
+
that you control from the dashboard, the allocation derives from the visitor ID and
|
|
189
|
+
the weight of the feature flag.
|
|
190
|
+
|
|
191
|
+
### What happens when you increment the version
|
|
192
|
+
|
|
193
|
+
When you increment the version of a feature flag in the dashboard:
|
|
194
|
+
|
|
195
|
+
1. **The cache key changes.** The old entry, `v3`, no longer matches the new key,
|
|
196
|
+
`v4`. The SDK therefore treats the feature flag as undecided.
|
|
197
|
+
2. **The `decide` function runs again.** On the next page load, the SDK calls
|
|
198
|
+
`decide()` again and caches the new result.
|
|
199
|
+
3. **Picked evaluates each visitor again and moves nobody.** For a feature flag that you
|
|
200
|
+
control from the dashboard the allocation is deterministic. The same visitor
|
|
201
|
+
against an unchanged weight gives the same bucket.
|
|
202
|
+
|
|
203
|
+
:::note
|
|
204
|
+
The new version clears the cached decisions and runs the allocation again. It moves
|
|
205
|
+
no visitor between groups. With an unchanged weight, everybody lands where they were.
|
|
206
|
+
Increment the version after you change a weight, so that Picked evaluates a visitor
|
|
207
|
+
with a cached decision against the new weight.
|
|
208
|
+
:::
|
|
209
|
+
|
|
210
|
+
### The SDK `key` and the dashboard version
|
|
211
|
+
|
|
212
|
+
For a feature flag that you define in SDK code, use the `key` field to set the version
|
|
213
|
+
yourself. This applies when you do not read the feature flag from the dashboard:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
features: {
|
|
217
|
+
'summer-sale': {
|
|
218
|
+
key: 'summer-sale-v2', // Change this to invalidate the cache.
|
|
219
|
+
// The other options go here.
|
|
220
|
+
},
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
For a feature flag that you control from the dashboard, Picked sets the version for you. The
|
|
225
|
+
dashboard adds the version number to the end of the cache key that the SDK uses.
|
|
226
|
+
|
|
227
|
+
## Related
|
|
228
|
+
|
|
229
|
+
- [How to read a feature flag](/how-to/read-a-feature-flag) — the accessors in use.
|
|
230
|
+
- [About feature flag allocation](/explanation/feature-flag-allocation) — when a visitor
|
|
231
|
+
enters a test.
|
|
232
|
+
- [Feature flags](/reference/dashboard/features) — the dashboard controls.
|
|
233
|
+
|