1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-18 00:19:49 +01:00

fix: backport lifecycle and playground fixes (#7396)

This commit is contained in:
Mateusz Kwasniewski 2024-06-14 10:39:29 +02:00 committed by GitHub
parent 29e0167001
commit 406f906d7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 73 additions and 42 deletions

View File

@ -6,5 +6,5 @@ export const comparisonModerator = (
): DeepOmit<IFeatureToggle, keyof IFeatureToggle> => { ): DeepOmit<IFeatureToggle, keyof IFeatureToggle> => {
const tempData = { ...data }; const tempData = { ...data };
return deepOmit(tempData, 'lastSeenAt', 'yes', 'no'); return deepOmit(tempData, 'lastSeenAt', 'yes', 'no', 'lifecycle');
}; };

View File

@ -8,6 +8,7 @@ import {
DELETE_FEATURE, DELETE_FEATURE,
UPDATE_FEATURE, UPDATE_FEATURE,
} from 'component/providers/AccessProvider/permissions'; } from 'component/providers/AccessProvider/permissions';
import { Route, Routes } from 'react-router-dom';
const currentTime = '2024-04-25T08:05:00.000Z'; const currentTime = '2024-04-25T08:05:00.000Z';
const twoMinutesAgo = '2024-04-25T08:03:00.000Z'; const twoMinutesAgo = '2024-04-25T08:03:00.000Z';
@ -22,16 +23,24 @@ const renderOpenTooltip = (
loading = false, loading = false,
) => { ) => {
render( render(
<FeatureLifecycleTooltip <Routes>
stage={stage} <Route
onArchive={onArchive} path={'/projects/:projectId'}
onComplete={onComplete} element={
onUncomplete={onUncomplete} <FeatureLifecycleTooltip
loading={loading} stage={stage}
> onArchive={onArchive}
<span>child</span> onComplete={onComplete}
</FeatureLifecycleTooltip>, onUncomplete={onUncomplete}
loading={loading}
>
<span>child</span>
</FeatureLifecycleTooltip>
}
/>
</Routes>,
{ {
route: '/projects/default',
permissions: [ permissions: [
{ permission: DELETE_FEATURE }, { permission: DELETE_FEATURE },
{ permission: UPDATE_FEATURE }, { permission: UPDATE_FEATURE },

View File

@ -25,6 +25,7 @@ import { isSafeToArchive } from './isSafeToArchive';
import { useLocationSettings } from 'hooks/useLocationSettings'; import { useLocationSettings } from 'hooks/useLocationSettings';
import { formatDateYMDHMS } from 'utils/formatDate'; import { formatDateYMDHMS } from 'utils/formatDate';
import { formatDistanceToNow, parseISO } from 'date-fns'; import { formatDistanceToNow, parseISO } from 'date-fns';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const TimeLabel = styled('span')(({ theme }) => ({ const TimeLabel = styled('span')(({ theme }) => ({
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
@ -264,6 +265,8 @@ const LiveStageDescription: FC<{
onComplete: () => void; onComplete: () => void;
loading: boolean; loading: boolean;
}> = ({ children, onComplete, loading }) => { }> = ({ children, onComplete, loading }) => {
const projectId = useRequiredPathParam('projectId');
return ( return (
<> <>
<BoldTitle>Is this feature complete?</BoldTitle> <BoldTitle>Is this feature complete?</BoldTitle>
@ -282,6 +285,7 @@ const LiveStageDescription: FC<{
size='small' size='small'
onClick={onComplete} onClick={onComplete}
disabled={loading} disabled={loading}
projectId={projectId}
> >
Mark completed Mark completed
</PermissionButton> </PermissionButton>
@ -300,6 +304,8 @@ const SafeToArchive: FC<{
onUncomplete: () => void; onUncomplete: () => void;
loading: boolean; loading: boolean;
}> = ({ onArchive, onUncomplete, loading }) => { }> = ({ onArchive, onUncomplete, loading }) => {
const projectId = useRequiredPathParam('projectId');
return ( return (
<> <>
<BoldTitle>Safe to archive</BoldTitle> <BoldTitle>Safe to archive</BoldTitle>
@ -316,6 +322,7 @@ const SafeToArchive: FC<{
sx={{ sx={{
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
flexWrap: 'wrap',
gap: 2, gap: 2,
}} }}
> >
@ -326,6 +333,7 @@ const SafeToArchive: FC<{
size='small' size='small'
onClick={onUncomplete} onClick={onUncomplete}
disabled={loading} disabled={loading}
projectId={projectId}
> >
Revert to live Revert to live
</PermissionButton> </PermissionButton>
@ -336,6 +344,7 @@ const SafeToArchive: FC<{
size='small' size='small'
sx={{ mb: 2 }} sx={{ mb: 2 }}
onClick={onArchive} onClick={onArchive}
projectId={projectId}
> >
Archive feature Archive feature
</PermissionButton> </PermissionButton>

View File

@ -82,9 +82,7 @@
"testTimeout": 10000, "testTimeout": 10000,
"globalSetup": "./scripts/jest-setup.js", "globalSetup": "./scripts/jest-setup.js",
"transform": { "transform": {
"^.+\\.tsx?$": [ "^.+\\.tsx?$": ["@swc/jest"]
"@swc/jest"
]
}, },
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"testPathIgnorePatterns": [ "testPathIgnorePatterns": [
@ -93,13 +91,7 @@
"/frontend/", "/frontend/",
"/website/" "/website/"
], ],
"moduleFileExtensions": [ "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json"],
"ts",
"tsx",
"js",
"jsx",
"json"
],
"coveragePathIgnorePatterns": [ "coveragePathIgnorePatterns": [
"/node_modules/", "/node_modules/",
"/dist/", "/dist/",
@ -240,14 +232,8 @@
"tough-cookie": "4.1.4" "tough-cookie": "4.1.4"
}, },
"lint-staged": { "lint-staged": {
"*.{js,ts}": [ "*.{js,ts}": ["biome check --write --no-errors-on-unmatched"],
"biome check --write --no-errors-on-unmatched" "*.{jsx,tsx}": ["biome check --write --no-errors-on-unmatched"],
], "*.json": ["biome format --write --no-errors-on-unmatched"]
"*.{jsx,tsx}": [
"biome check --write --no-errors-on-unmatched"
],
"*.json": [
"biome format --write --no-errors-on-unmatched"
]
} }
} }

View File

@ -95,7 +95,7 @@ test('advanced playground evaluation with no toggles', async () => {
}); });
}); });
test('advanced playground evaluation with parent dependency', async () => { test('advanced playground evaluation with unsatisfied parent dependency', async () => {
await createFeatureToggle('test-parent'); await createFeatureToggle('test-parent');
await createFeatureToggle('test-child'); await createFeatureToggle('test-child');
await enableToggle('test-child'); await enableToggle('test-child');
@ -126,6 +126,35 @@ test('advanced playground evaluation with parent dependency', async () => {
expect(parent.isEnabled).toBe(false); expect(parent.isEnabled).toBe(false);
}); });
test('advanced playground evaluation with satisfied disabled parent dependency', async () => {
await createFeatureToggle('test-parent');
await createFeatureToggle('test-child');
await enableToggle('test-child');
await app.addDependency('test-child', {
feature: 'test-parent',
enabled: false,
variants: [],
});
const { body: result } = await app.request
.post('/api/admin/playground/advanced')
.send({
environments: ['default'],
projects: ['default'],
context: { appName: 'test' },
})
.set('Content-Type', 'application/json')
.expect(200);
const child = result.features[0].environments.default[0];
const parent = result.features[1].environments.default[0];
expect(child.hasUnsatisfiedDependency).toBe(false);
expect(child.isEnabled).toBe(true);
expect(parent.hasUnsatisfiedDependency).toBe(false);
expect(parent.isEnabled).toBe(false);
});
test('advanced playground evaluation happy path', async () => { test('advanced playground evaluation happy path', async () => {
await createFeatureToggleWithStrategy('test-playground-feature'); await createFeatureToggleWithStrategy('test-playground-feature');
await enableToggle('test-playground-feature'); await enableToggle('test-playground-feature');

View File

@ -77,9 +77,6 @@ export default class UnleashClient {
} }
if (parent.enabled !== false) { if (parent.enabled !== false) {
if (!parentToggle.enabled) {
return false;
}
if (parent.variants?.length) { if (parent.variants?.length) {
return parent.variants.includes( return parent.variants.includes(
this.getVariant(parent.feature, context).name, this.getVariant(parent.feature, context).name,
@ -91,12 +88,9 @@ export default class UnleashClient {
); );
} }
return ( return !(
!parentToggle.enabled && this.isEnabled(parent.feature, context, () => false).result ===
!( true
this.isEnabled(parent.feature, context, () => false)
.result === true
)
); );
}); });
} }

View File

@ -15,6 +15,7 @@ import type { Db } from '../../../lib/db/db';
import type { IContextFieldDto } from '../../../lib/types/stores/context-field-store'; import type { IContextFieldDto } from '../../../lib/types/stores/context-field-store';
import { DEFAULT_ENV } from '../../../lib/util'; import { DEFAULT_ENV } from '../../../lib/util';
import type { import type {
CreateDependentFeatureSchema,
CreateFeatureSchema, CreateFeatureSchema,
CreateFeatureStrategySchema, CreateFeatureStrategySchema,
ImportTogglesSchema, ImportTogglesSchema,
@ -102,7 +103,10 @@ export interface IUnleashHttpAPI {
expectedResponseCode?: number, expectedResponseCode?: number,
): supertest.Test; ): supertest.Test;
addDependency(child: string, parent: string): supertest.Test; addDependency(
child: string,
parent: string | CreateDependentFeatureSchema,
): supertest.Test;
addTag( addTag(
feature: string, feature: string,
@ -224,7 +228,7 @@ function httpApis(
addDependency( addDependency(
child: string, child: string,
parent: string, parent: string | CreateDependentFeatureSchema,
project = DEFAULT_PROJECT, project = DEFAULT_PROJECT,
expectedResponseCode: number = 200, expectedResponseCode: number = 200,
): supertest.Test { ): supertest.Test {
@ -232,7 +236,7 @@ function httpApis(
.post( .post(
`/api/admin/projects/${project}/features/${child}/dependencies`, `/api/admin/projects/${project}/features/${child}/dependencies`,
) )
.send({ feature: parent }) .send(typeof parent === 'string' ? { feature: parent } : parent)
.set('Content-Type', 'application/json') .set('Content-Type', 'application/json')
.expect(expectedResponseCode); .expect(expectedResponseCode);
}, },