1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-06 00:07:44 +01:00
unleash.unleash/src/lib/openapi/spec/playground-feature-schema.test.ts
Thomas Heartman 24e9cf7c8f
feat: add "edit" link to playground strategies (#4027)
This change adds an "edit" link to all playground strategies when they
are returned from the API, allowing the user to jump directly to the
evaluated strategy edit screen.

This change applies to both old and new strategies, so it should even
work in the old playground.

This does not use this information in the frontend yet.

## Discussion points:

Should "links" be an object or a singular string? I know the
notifications service uses just "link", but using an object will make
it easier to potentially add more actions in the future (such as
"enable"/"disable", maybe?)

Do we need to supply basePath? I noticed that the notifications links
only ever use an empty string for base path, so it might not be
necessary and can potentially be left out.

## Changes

I've implemented the link building in a new view model file. Inspecting
the output after the result is fully ready requires some gnarly
introspection and mapping, but it's tested.

Further, I've done a little bit of work to stop the playground service
using the schema types directly as the schema types now contain extra
information.

This PR also updates the `urlFriendlyString` arbitrary to not produce
strings that contain only periods. This causes issues when parsing URLs
(and is also something we struggle with in the UI).
2023-06-22 07:19:35 +00:00

209 lines
6.6 KiB
TypeScript

import fc, { Arbitrary } from 'fast-check';
import {
strategyConstraint,
urlFriendlyString,
variants,
} from '../../../test/arbitraries.test';
import { validateSchema } from '../validate';
import { PlaygroundConstraintSchema } from './playground-constraint-schema';
import {
playgroundFeatureSchema,
PlaygroundFeatureSchema,
} from './playground-feature-schema';
import { PlaygroundSegmentSchema } from './playground-segment-schema';
import {
playgroundStrategyEvaluation,
PlaygroundStrategySchema,
} from './playground-strategy-schema';
const playgroundStrategyConstraint =
(): Arbitrary<PlaygroundConstraintSchema> =>
fc
.tuple(fc.boolean(), strategyConstraint())
.map(([result, constraint]) => ({
...constraint,
result,
}));
const playgroundStrategyConstraints = (): Arbitrary<
PlaygroundConstraintSchema[]
> => fc.array(playgroundStrategyConstraint());
const playgroundSegment = (): Arbitrary<PlaygroundSegmentSchema> =>
fc.record({
name: fc.string({ minLength: 1 }),
id: fc.nat(),
result: fc.boolean(),
constraints: playgroundStrategyConstraints(),
});
const playgroundStrategy = (
name: string,
parameters: Arbitrary<Record<string, string>>,
): Arbitrary<PlaygroundStrategySchema> =>
fc.record({
id: fc.uuid(),
name: fc.constant(name),
result: fc.oneof(
fc.record({
evaluationStatus: fc.constant(
playgroundStrategyEvaluation.evaluationComplete,
),
enabled: fc.boolean(),
}),
fc.record({
evaluationStatus: fc.constant(
playgroundStrategyEvaluation.evaluationIncomplete,
),
enabled: fc.constantFrom(
playgroundStrategyEvaluation.unknownResult,
false as false,
),
}),
),
parameters,
constraints: playgroundStrategyConstraints(),
segments: fc.array(playgroundSegment()),
disabled: fc.boolean(),
links: fc.constant({
edit: '/projects/some-project/features/some-feature/strategies/edit?environmentId=some-env&strategyId=some-strat',
}),
});
const playgroundStrategies = (): Arbitrary<PlaygroundStrategySchema[]> =>
fc.array(
fc.oneof(
playgroundStrategy('default', fc.constant({})),
playgroundStrategy(
'flexibleRollout',
fc.record({
groupId: fc.lorem({ maxCount: 1 }),
rollout: fc.nat({ max: 100 }).map(String),
stickiness: fc.constantFrom(
'default',
'userId',
'sessionId',
),
}),
),
playgroundStrategy(
'applicationHostname',
fc.record({
hostNames: fc
.uniqueArray(fc.domain())
.map((domains) => domains.join(',')),
}),
),
playgroundStrategy(
'userWithId',
fc.record({
userIds: fc
.uniqueArray(fc.emailAddress())
.map((ids) => ids.join(',')),
}),
),
playgroundStrategy(
'remoteAddress',
fc.record({
IPs: fc.uniqueArray(fc.ipV4()).map((ips) => ips.join(',')),
}),
),
),
);
export const generate = (): Arbitrary<PlaygroundFeatureSchema> =>
fc
.tuple(
variants(),
fc.nat(),
fc.record({
isEnabledInCurrentEnvironment: fc.boolean(),
projectId: urlFriendlyString(),
name: urlFriendlyString(),
strategies: playgroundStrategies(),
}),
)
.map(([generatedVariants, activeVariantIndex, feature]) => {
const strategyResult = () => {
const { strategies } = feature;
if (
strategies.some(
(strategy) => strategy.result.enabled === true,
)
) {
return true;
}
if (
strategies.some(
(strategy) => strategy.result.enabled === 'unknown',
)
) {
return 'unknown';
}
return false;
};
const isEnabled =
feature.isEnabledInCurrentEnvironment &&
strategyResult() === true;
// the active variant is the disabled variant if the feature is
// disabled or has no variants.
let activeVariant = { name: 'disabled', enabled: false } as {
name: string;
enabled: boolean;
payload?: {
type: 'string' | 'json' | 'csv';
value: string;
};
};
if (generatedVariants.length && isEnabled) {
const targetVariant =
generatedVariants[
activeVariantIndex % generatedVariants.length
];
const targetPayload = targetVariant.payload
? (targetVariant.payload as {
type: 'string' | 'json' | 'csv';
value: string;
})
: undefined;
activeVariant = {
enabled: true,
name: targetVariant.name,
payload: targetPayload,
};
}
return {
...feature,
isEnabled,
strategies: {
result: strategyResult(),
data: feature.strategies,
},
variants: generatedVariants,
variant: activeVariant,
};
});
test('playgroundFeatureSchema', () =>
fc.assert(
fc.property(
generate(),
fc.context(),
(data: PlaygroundFeatureSchema, ctx) => {
const results = validateSchema(
playgroundFeatureSchema.$id,
data,
);
ctx.log(JSON.stringify(results));
return results === undefined;
},
),
));