Skip to content

Commit 14c8ea2

Browse files
committed
docs: new fractional features
* update public facing schema * update fractional docs * update playground demos Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
1 parent 7190878 commit 14c8ea2

10 files changed

Lines changed: 323 additions & 108 deletions

File tree

docs/playground/playground.js

Lines changed: 45 additions & 45 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/custom-operations/fractional-operation.md

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ The bucketing value expression can be omitted, in which case a concatenation of
7272
The `fractional` operation is a custom JsonLogic operation which deterministically selects a variant based on
7373
the defined distribution of each variant (as a relative weight).
7474
This works by hashing ([murmur3](https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp))
75-
the given data point, converting it into an int in the range [0, 99].
76-
Whichever range this int falls in decides which variant
77-
is selected.
75+
the given data point and using an algorithm leveraging pure integer arithmetic, with `math.MaxInt32` (2,147,483,647) as the maximum weight sum.
76+
This provides consistent, efficient, sub-percent granularity (down to ~0.00000005%) for high-throughput environments.
77+
Whichever bucket this falls in decides which variant is selected.
7878
As hashing is deterministic we can be sure to get the same result every time for the same data point.
7979

8080
The `fractional` operation can be added as part of a targeting definition.
@@ -86,7 +86,7 @@ The other elements in the array are nested arrays with the first element represe
8686
There is no limit to the number of elements.
8787

8888
> [!NOTE]
89-
> Older versions of the `fractional` operation were percentage based, and required all variants weights to sum to 100.
89+
> Previous versions of the `fractional` operation used percentage-based weights that had to sum to 100 and were limited to 1% precision.
9090
9191
## Example
9292

@@ -160,6 +160,117 @@ Result:
160160
Notice that rerunning either curl command will always return the same variant and value.
161161
The only way to get a different value is to change the email or update the `fractional` configuration.
162162

163+
## Nested Fractional Evaluation
164+
165+
The `fractional` operation supports nested JSONLogic expressions within its arguments, enabling advanced use cases.
166+
167+
### Nested Conditional Variants
168+
169+
Conditional logic within each bucket:
170+
171+
```json
172+
"fractional": [
173+
[
174+
{
175+
"if": [
176+
{ "in": [{ "var": "locale" }, ["us", "ca"]] },
177+
"red",
178+
"grey"
179+
]
180+
},
181+
25
182+
],
183+
[
184+
"blue",
185+
25
186+
],
187+
[
188+
"green",
189+
25
190+
],
191+
[
192+
"grey",
193+
25
194+
]
195+
]
196+
```
197+
198+
### Computed Weights with JSONLogic
199+
200+
Weight values can be JSONLogic expressions, enabling progressive rollouts and dynamic traffic splitting without a dedicated operator.
201+
This allows you to use arbitrary JSONLogic expressions to compute weights at runtime.
202+
This is especially useful for time-based weight calculations.
203+
204+
#### Time-Based Rollout Example
205+
206+
Use flagd's built-in `$flagd.timestamp` variable to create time-based progressive rollouts.
207+
The timestamp is in Unix epoch seconds.
208+
209+
```jsonc
210+
{
211+
"fractional": [
212+
["on", { "-": [{ "var": "$flagd.timestamp" }, 1743360000] }],
213+
["off", { "-": [1743964800, { "var": "$flagd.timestamp" }] }]
214+
]
215+
}
216+
```
217+
218+
As time advances, the weight of `"on"` grows and `"off"` shrinks, producing a deterministic progressive rollout.
219+
This example:
220+
221+
- Starts on Mar 30, 2025 (timestamp `1743360000`)
222+
- Completes on Apr 6, 2025 (timestamp `1743964800`, 7 days later)
223+
224+
#### Environment-Based Rollout Example
225+
226+
Roll out differently based on environment:
227+
228+
```json
229+
"fractional": [
230+
{ "var": "email" },
231+
["new-feature", {
232+
"if": [
233+
{ "==": [{ "var": "environment" }, "production"] },
234+
10,
235+
{
236+
"if": [
237+
{ "==": [{ "var": "environment" }, "staging"] },
238+
50,
239+
100
240+
]
241+
}
242+
]
243+
}],
244+
["control", {
245+
"if": [
246+
{ "==": [{ "var": "environment" }, "production"] },
247+
90,
248+
{
249+
"if": [
250+
{ "==": [{ "var": "environment" }, "staging"] },
251+
50,
252+
0
253+
]
254+
}
255+
]
256+
}]
257+
]
258+
```
259+
260+
#### High Precision Example
261+
262+
Use large weight values for sub-percent granularity in high-traffic environments:
263+
264+
```json
265+
"fractional": [
266+
{ "var": "user_id" },
267+
["canary", 1],
268+
["control", 1000000]
269+
]
270+
```
271+
272+
This splits approximately 1/1,000,000 of traffic to `canary` and the remaining to `control`.
273+
163274
### Migrating from legacy "fractionalEvaluation"
164275

165276
If you are using a legacy fractional evaluation (`fractionalEvaluation`), it's recommended you migrate to `fractional`.

docs/reference/specifications/protos.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,9 @@ FetchAllFlagsRequest is the request to fetch all flags. Clients send this reques
321321
| Field | Type | Label | Description |
322322
| ----- | ---- | ----- | ----------- |
323323
| provider_id | [string](#string) | | Optional: A unique identifier for clients initiating the request. The server implementations may utilize this identifier to uniquely identify, validate(ex:- enforce authentication/authorization) and filter flag configurations that it can expose to this request. This field is intended to be optional. However server implementations may enforce it. ex:- provider_id: flagd-weatherapp-sidecar |
324-
| selector | [string](#string) | | Optional: A selector for the flag configuration request. The server implementation may utilize this to select flag configurations from a collection, select the source of the flag or combine this to any desired underlying filtering mechanism. ex:- selector: &#39;source=database,app=weatherapp&#39; |
324+
| selector | [string](#string) | | **Deprecated.** Optional: A selector for the flag configuration request. The server implementation may utilize this to select flag configurations from a collection, select the source of the flag or combine this to any desired underlying filtering mechanism. ex:- selector: &#39;source=database,app=weatherapp&#39;
325+
326+
Deprecated: Use the &#39;Flagd-Selector&#39; header instead. Remember to reserve field number 2 if this is removed; |
325327

326328

327329

@@ -379,7 +381,9 @@ Implementations of Flagd providers and Flagd itself send this request, acting as
379381
| Field | Type | Label | Description |
380382
| ----- | ---- | ----- | ----------- |
381383
| provider_id | [string](#string) | | Optional: A unique identifier for flagd(grpc client) initiating the request. The server implementations may utilize this identifier to uniquely identify, validate(ex:- enforce authentication/authorization) and filter flag configurations that it can expose to this request. This field is intended to be optional. However server implementations may enforce it. ex:- provider_id: flagd-weatherapp-sidecar |
382-
| selector | [string](#string) | | Optional: A selector for the flag configuration request. The server implementation may utilize this to select flag configurations from a collection, select the source of the flag or combine this to any desired underlying filtering mechanism. ex:- selector: &#39;source=database,app=weatherapp&#39; |
384+
| selector | [string](#string) | | **Deprecated.** Optional: A selector for the flag configuration request. The server implementation may utilize this to select flag configurations from a collection, select the source of the flag or combine this to any desired underlying filtering mechanism. ex:- selector: &#39;source=database,app=weatherapp&#39;
385+
386+
Deprecated: Use the &#39;Flagd-Selector&#39; header instead. Remember to reserve field number 2 if this is removed; |
383387

384388

385389

docs/schema/v0/targeting.json

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"type": "string"
3636
},
3737
{
38-
"description": "When returned from rules, strings are used to as keys to retrieve the associated value from the \"variants\" object. Be sure that the returned string is present as a key in the variants!.",
38+
"description": "When returned from rules, the behavior of arrays is not defined.",
3939
"type": "array"
4040
}
4141
]
@@ -461,18 +461,26 @@
461461
"maxItems": 2,
462462
"items": [
463463
{
464-
"description": "If this bucket is randomly selected, this string is used to as a key to retrieve the associated value from the \"variants\" object.",
465-
"type": "string"
464+
"description": "If this bucket is randomly selected, this JSONLogic will be evaluated, and the result will be used as the variant key to return from the variants map.",
465+
"$ref": "#/definitions/args"
466466
},
467467
{
468-
"description": "Weighted distribution for this variant key.",
469-
"type": "number"
468+
"description": "Weighted distribution for this variant key. Must be a non-negative integer. Can be a JSONLogic expression that evaluates to a number (e.g. for time-based progressive rollouts); computed negative weights are clamped to 0 at evaluation time. The total weight sum across all variants must not exceed 2,147,483,647.",
469+
"oneOf": [
470+
{
471+
"type": "integer",
472+
"minimum": 0
473+
},
474+
{
475+
"$ref": "#/definitions/anyRule"
476+
}
477+
]
470478
}
471479
]
472480
},
473481
"fractionalOp": {
474482
"type": "array",
475-
"minItems": 3,
483+
"minItems": 1,
476484
"$comment": "there seems to be a bug here, where ajv gives a warning (not an error) because maxItems doesn't equal the number of entries in items, though this is valid in this case",
477485
"items": [
478486
{
@@ -492,7 +500,7 @@
492500
},
493501
"fractionalShorthandOp": {
494502
"type": "array",
495-
"minItems": 2,
503+
"minItems": 1,
496504
"items": {
497505
"$ref": "#/definitions/fractionalWeightArg"
498506
}

playground-app/package-lock.json

Lines changed: 31 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

playground-app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
},
1212
"dependencies": {
1313
"@monaco-editor/react": "^4.7.0-rc.0",
14-
"@openfeature/flagd-core": "^2.0.0",
14+
"@openfeature/flagd-core": "^3.0.0",
1515
"js-yaml": "^4.1.1",
1616
"react": "^19.0.0",
1717
"react-dom": "^19.0.0",

0 commit comments

Comments
 (0)