1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-02 01:17:58 +02:00
unleash.unleash/src/lib/util/feature-evaluator/repository/index.ts
Thomas Heartman e55ad1a21e
feat(#1873/playground): Return detailed information on feature toggle evaluation (#1839)
* Feat: return reasons why a feature evaluated to true or false

Note: this is very rough and just straight ripped from the nodejs
client. It will need a lot of work, but is a good place to start

* Feat: add suggested shape for new payload

* Chore: minor cleanup

* Wip: make server compile again

* Remove unused schema ref

* Export new schemas

* Chore: fix some tests to use sub property

* Fix: fix some tests

* Refactor: rename some variables, uncomment some stuff

* Add segments type to bootstrap options

* Add segments capability to offline feature evaluator

* Fix function calls after turning params into an option abject

* Feat: test strategy order, etc

* Feat: add test to check that all strats are returned correctly

* Feat: allow you to include strategy ids in clients

* Wip: hook up segments in the offline client.

Note: compared to regular clients, they still fail

* Feat: add segments validation

* Fix: fix test case invariant.

* Chore: revert to returning only `boolean` from strategies.

This _should_ make it work with custom strategies too 🤞

* Feat: make more properties of the returned feature required

* Wip: add some comments and unfinished tests for edge cases

* Feat: add `isEnabledInCurrentEnvironment` prop

* Feat: consider more strategy failure cases

* Feat: test that isenabledinenvironment matches expectations

* Feat: add unknown strategies

* Fix: fix property access typo

* Feat: add unknown strategy for fallback purposes

* Feat: test edge case: all unknown strategies

* Feat: add custom strategy to arbitrary

* Feat: test that features can be true, even if not enabled in env

* Chore: add some comments

* Wip: fix sdk tests

* Remove comments, improve test logging

* Feat: add descriptions and examples to playground feature schema

* Switch `examples` for `example`

* Update schemas with descriptions and examples

* Fix: update snapshot

* Fix: openapi example

* Fix: merge issues

* Fix: fix issue where feature evaluation state was wrong

* Chore: update openapi spec

* Fix: fix broken offline client tests

* Refactor: move schemas into separate files

* Refactor: remove "reason" for incomplete evaluation.

The only instances where evaluation is incomplete is when we don't
know what the strategy is.

* Refactor: move unleash node client into test and dev dependencies

* Wip: further removal of stuff

* Chore: remove a bunch of code that we don't use

* Chore: remove comment

* Chore: remove unused code

* Fix: fix some prettier errors

* Type parameters in strategies to avoid `any`

* Fix: remove commented out code

* Feat: make `id` required on playground strategies

* Chore: remove redundant type

* Fix: remove redundant if and fix fallback evaluation

* Refactor: reduce nesting and remove duplication

* Fix: remove unused helper function

* Refactor: type `parameters` as `unknown`

* Chore: remove redundant comment

* Refactor: move constraint code into a separate file

* Refactor: rename `unleash` -> `feature-evaluator`

* Rename class `Unleash` -> `FeatureEvaluator`

* Refactor: remove this.ready and sync logic from feature evaluator

* Refactor: remove unused code, rename config type

* Refactor: remove event emission from the Unleash client

* Remove unlistened-for events in feature evaluator

* Refactor: make offline client synchronous; remove code

* Fix: update openapi snapshot after adding required strategy ids

* Feat: change `strategies` format.

This commit changes the format of a playground feature's `strategies`
properties from a list of strategies to an object with properties
`result` and `data`. It looks a bit like this:

```ts
type Strategies = {
  result: boolean | "unknown",
  data: Strategy[]
}
```

The reason is that this allows us to avoid the breaking change that
was previously suggested in the PR:

`feature.isEnabled` used to be a straight boolean. Then, when we found
out we couldn't necessarily evaluate all strategies (custom strats are
hard!) we changed it to `boolean | 'unevaluated'`. However, this is
confusing on a few levels as the playground results are no longer the
same as the SDK would be, nor are they strictly boolean anymore.

This change reverts the `isEnabled` functionality to what it was
before (so it's always a mirror of what the SDK would show).
The equivalent of `feature.isEnabled === 'unevaluated'` now becomes
`feature.isEnabled && strategy.result === 'unknown'`.

* Fix: Fold long string descriptions over multiple lines.

* Fix: update snapshot after adding line breaks to descriptions
2022-08-04 15:41:52 +02:00

115 lines
3.1 KiB
TypeScript

import { ClientFeaturesResponse, FeatureInterface } from '../feature';
import { BootstrapProvider } from './bootstrap-provider';
import { StorageProvider } from './storage-provider';
import { Segment } from '../strategy/strategy';
export interface RepositoryInterface {
getToggle(name: string): FeatureInterface;
getToggles(): FeatureInterface[];
getSegment(id: number): Segment | undefined;
stop(): void;
start(): Promise<void>;
}
export interface RepositoryOptions {
appName: string;
bootstrapProvider: BootstrapProvider;
storageProvider: StorageProvider<ClientFeaturesResponse>;
}
interface FeatureToggleData {
[key: string]: FeatureInterface;
}
export default class Repository {
private timer: NodeJS.Timer | undefined;
private appName: string;
private bootstrapProvider: BootstrapProvider;
private storageProvider: StorageProvider<ClientFeaturesResponse>;
private data: FeatureToggleData = {};
private segments: Map<number, Segment>;
constructor({
appName,
bootstrapProvider,
storageProvider,
}: RepositoryOptions) {
this.appName = appName;
this.bootstrapProvider = bootstrapProvider;
this.storageProvider = storageProvider;
this.segments = new Map();
}
start(): Promise<void> {
return this.loadBootstrap();
}
createSegmentLookup(segments: Segment[] | undefined): Map<number, Segment> {
if (!segments) {
return new Map();
}
return new Map(segments.map((segment) => [segment.id, segment]));
}
async save(response: ClientFeaturesResponse): Promise<void> {
this.data = this.convertToMap(response.features);
this.segments = this.createSegmentLookup(response.segments);
await this.storageProvider.set(this.appName, response);
}
notEmpty(content: ClientFeaturesResponse): boolean {
return content.features.length > 0;
}
async loadBootstrap(): Promise<void> {
try {
const content = await this.bootstrapProvider.readBootstrap();
if (content && this.notEmpty(content)) {
await this.save(content);
}
} catch (err: any) {
// intentionally left empty
}
}
private convertToMap(features: FeatureInterface[]): FeatureToggleData {
const obj = features.reduce(
(
o: { [s: string]: FeatureInterface },
feature: FeatureInterface,
) => {
const a = { ...o };
a[feature.name] = feature;
return a;
},
{} as { [s: string]: FeatureInterface },
);
return obj;
}
stop(): void {
if (this.timer) {
clearTimeout(this.timer);
}
}
getSegment(segmentId: number): Segment | undefined {
return this.segments.get(segmentId);
}
getToggle(name: string): FeatureInterface {
return this.data[name];
}
getToggles(): FeatureInterface[] {
return Object.keys(this.data).map((key) => this.data[key]);
}
}