mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
chore: remove deprecated legacy features endpoint (#7129)
This PR is part of #4380 - Remove legacy `/api/feature` endpoint. ## About the changes ### Frontend - Removes the useFeatures hook - Removes the part of StrategyView that displays features using this strategy (not been working since v4.4) - Removes 2 unused features entries from routes ### Backend - Removes the /api/admin/features endpoint - Moves a couple of non-feature related tests (auth etc) to use /admin/projects endpoint instead - Removes a test that was directly related to the removed endpoint - Moves a couple of tests to the projects/features endpoint - Reworks some tests to fetch features from projects features endpoint and strategies from project strategies
This commit is contained in:
parent
6f2a10922d
commit
9ea66e8850
@ -1,33 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import { useFeatures } from 'hooks/api/getters/useFeatures/useFeatures';
|
||||
import { getTogglePath } from 'utils/routePathHelpers';
|
||||
import type { FeatureSchema } from 'openapi';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
|
||||
const RedirectFeatureView = () => {
|
||||
const featureId = useRequiredPathParam('featureId');
|
||||
const { features = [] } = useFeatures();
|
||||
const [featureToggle, setFeatureToggle] = useState<FeatureSchema>();
|
||||
|
||||
useEffect(() => {
|
||||
const toggle = features.find(
|
||||
(toggle: FeatureSchema) => toggle.name === featureId,
|
||||
);
|
||||
|
||||
setFeatureToggle(toggle);
|
||||
}, [features, featureId]);
|
||||
|
||||
if (!featureToggle?.project) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Navigate
|
||||
to={getTogglePath(featureToggle.project, featureToggle.name)}
|
||||
replace
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default RedirectFeatureView;
|
@ -73,14 +73,6 @@ exports[`returns all baseRoutes 1`] = `
|
||||
"title": "Create feature flag",
|
||||
"type": "protected",
|
||||
},
|
||||
{
|
||||
"component": [Function],
|
||||
"menu": {},
|
||||
"parent": "/features",
|
||||
"path": "/projects/:projectId/features2/:featureId",
|
||||
"title": ":featureId",
|
||||
"type": "protected",
|
||||
},
|
||||
{
|
||||
"component": {
|
||||
"$$typeof": Symbol(react.lazy),
|
||||
@ -106,14 +98,6 @@ exports[`returns all baseRoutes 1`] = `
|
||||
"title": "Projects",
|
||||
"type": "protected",
|
||||
},
|
||||
{
|
||||
"component": [Function],
|
||||
"menu": {},
|
||||
"parent": "/features",
|
||||
"path": "/features/:activeTab/:featureId",
|
||||
"title": ":featureId",
|
||||
"type": "protected",
|
||||
},
|
||||
{
|
||||
"component": [Function],
|
||||
"menu": {
|
||||
|
@ -18,7 +18,6 @@ import CreateTagType from 'component/tags/CreateTagType/CreateTagType';
|
||||
import CreateFeature from 'component/feature/CreateFeature/CreateFeature';
|
||||
import EditFeature from 'component/feature/EditFeature/EditFeature';
|
||||
import ContextList from 'component/context/ContextList/ContextList/ContextList';
|
||||
import RedirectFeatureView from 'component/feature/RedirectFeatureView/RedirectFeatureView';
|
||||
import { CreateIntegration } from 'component/integrations/CreateIntegration/CreateIntegration';
|
||||
import { EditIntegration } from 'component/integrations/EditIntegration/EditIntegration';
|
||||
import { CopyFeatureToggle } from 'component/feature/CopyFeature/CopyFeature';
|
||||
@ -110,14 +109,6 @@ export const routes: IRoute[] = [
|
||||
type: 'protected',
|
||||
menu: {},
|
||||
},
|
||||
{
|
||||
path: '/projects/:projectId/features2/:featureId',
|
||||
parent: '/features',
|
||||
title: ':featureId',
|
||||
component: RedirectFeatureView,
|
||||
type: 'protected',
|
||||
menu: {},
|
||||
},
|
||||
{
|
||||
path: '/projects/:projectId/*',
|
||||
parent: '/projects',
|
||||
@ -136,14 +127,6 @@ export const routes: IRoute[] = [
|
||||
},
|
||||
|
||||
// Features
|
||||
{
|
||||
path: '/features/:activeTab/:featureId',
|
||||
parent: '/features',
|
||||
title: ':featureId',
|
||||
component: RedirectFeatureView,
|
||||
type: 'protected',
|
||||
menu: {},
|
||||
},
|
||||
{
|
||||
path: '/search',
|
||||
title: 'Search',
|
||||
|
@ -12,20 +12,17 @@ import RadioButtonChecked from '@mui/icons-material/RadioButtonChecked';
|
||||
import { AppsLinkList } from 'component/common';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import styles from '../../strategies.module.scss';
|
||||
import { TogglesLinkList } from 'component/strategies/TogglesLinkList/TogglesLinkList';
|
||||
import type { IStrategy, IStrategyParameter } from 'interfaces/strategy';
|
||||
import type { ApplicationSchema, FeatureSchema } from 'openapi';
|
||||
import type { ApplicationSchema } from 'openapi';
|
||||
|
||||
interface IStrategyDetailsProps {
|
||||
strategy: IStrategy;
|
||||
applications: ApplicationSchema[];
|
||||
toggles: FeatureSchema[];
|
||||
}
|
||||
|
||||
export const StrategyDetails = ({
|
||||
strategy,
|
||||
applications,
|
||||
toggles,
|
||||
}: IStrategyDetailsProps) => {
|
||||
const theme = useTheme();
|
||||
const { parameters = [] } = strategy;
|
||||
@ -84,7 +81,7 @@ export const StrategyDetails = ({
|
||||
<List>{renderParameters(parameters)}</List>
|
||||
</Grid>
|
||||
|
||||
<Grid item sm={12} md={toggles.length > 0 ? 6 : 12}>
|
||||
<Grid item sm={12} md={12}>
|
||||
<h6>
|
||||
Applications using this strategy{' '}
|
||||
{applications.length >= 1000 && '(Capped at 1000)'}
|
||||
@ -92,17 +89,6 @@ export const StrategyDetails = ({
|
||||
<hr />
|
||||
<AppsLinkList apps={applications} />
|
||||
</Grid>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={toggles.length > 0}
|
||||
show={() => (
|
||||
<Grid item sm={12} md={6}>
|
||||
<h6>Toggles using this strategy</h6>
|
||||
<hr />
|
||||
<TogglesLinkList toggles={toggles} />
|
||||
</Grid>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
</div>
|
||||
);
|
||||
|
@ -3,7 +3,6 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { UPDATE_STRATEGY } from 'component/providers/AccessProvider/permissions';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
|
||||
import { useFeatures } from 'hooks/api/getters/useFeatures/useFeatures';
|
||||
import useApplications from 'hooks/api/getters/useApplications/useApplications';
|
||||
import { StrategyDetails } from './StrategyDetails/StrategyDetails';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
@ -11,24 +10,13 @@ import PermissionIconButton from 'component/common/PermissionIconButton/Permissi
|
||||
import Edit from '@mui/icons-material/Edit';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import type { FeatureSchema } from 'openapi/models';
|
||||
|
||||
export const StrategyView = () => {
|
||||
const name = useRequiredPathParam('name');
|
||||
const { strategies } = useStrategies();
|
||||
const { features = [] } = useFeatures();
|
||||
const { applications } = useApplications();
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Has been broken since the migration to environments. We need to create an
|
||||
// endpoint that returns all environments and strategies for all features to make this
|
||||
// work properly OR alternatively create an endpoint that abstracts this logic into the backend
|
||||
const toggles = features.filter((toggle: FeatureSchema) => {
|
||||
return toggle?.environments
|
||||
?.flatMap((env) => env.strategies)
|
||||
.some((strategy) => strategy && strategy.name === name);
|
||||
});
|
||||
|
||||
const strategy = strategies.find((strategy) => strategy.name === name);
|
||||
|
||||
const handleEdit = () => {
|
||||
@ -64,7 +52,6 @@ export const StrategyView = () => {
|
||||
<Grid item xs={12} sm={12}>
|
||||
<StrategyDetails
|
||||
strategy={strategy}
|
||||
toggles={toggles}
|
||||
applications={applications}
|
||||
/>
|
||||
</Grid>
|
||||
|
@ -1,27 +0,0 @@
|
||||
import type { FeaturesSchema } from 'openapi';
|
||||
import useSWR from 'swr';
|
||||
import { formatApiPath } from 'utils/formatPath';
|
||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||
|
||||
const fetcher = (path: string) => {
|
||||
return fetch(path)
|
||||
.then(handleErrorResponses('Feature flag'))
|
||||
.then((res) => res.json());
|
||||
};
|
||||
|
||||
export const useFeatures = () => {
|
||||
const { data, error, mutate } = useSWR<FeaturesSchema>(
|
||||
formatApiPath('api/admin/features'),
|
||||
fetcher,
|
||||
{
|
||||
refreshInterval: 15 * 1000, // ms
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
features: data?.features,
|
||||
loading: !error && !data,
|
||||
refetchFeatures: mutate,
|
||||
error,
|
||||
};
|
||||
};
|
@ -1099,41 +1099,6 @@ class FeatureToggleService {
|
||||
return features as FeatureConfigurationClient[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Legacy!
|
||||
*
|
||||
* Used to retrieve metadata of all feature flags defined in Unleash.
|
||||
* @param query - Allow you to limit search based on criteria such as project, tags, namePrefix. See @IFeatureToggleQuery
|
||||
* @param userId - Used to find / mark features as favorite based on users preferences
|
||||
* @param archived - Return archived or active flags
|
||||
* @returns
|
||||
*/
|
||||
async getFeatureToggles(
|
||||
query?: IFeatureToggleQuery,
|
||||
userId?: number,
|
||||
archived: boolean = false,
|
||||
): Promise<FeatureToggle[]> {
|
||||
// Remove with with feature flag
|
||||
const features = await this.featureToggleStore.getFeatureToggleList(
|
||||
query,
|
||||
userId,
|
||||
archived,
|
||||
);
|
||||
|
||||
if (userId) {
|
||||
const projectAccess =
|
||||
await this.privateProjectChecker.getUserAccessibleProjects(
|
||||
userId,
|
||||
);
|
||||
return projectAccess.mode === 'all'
|
||||
? features
|
||||
: features.filter((f) =>
|
||||
projectAccess.projects.includes(f.project),
|
||||
);
|
||||
}
|
||||
return features;
|
||||
}
|
||||
|
||||
async getFeatureOverview(
|
||||
params: IFeatureProjectUserParams,
|
||||
): Promise<IFeatureOverview[]> {
|
||||
@ -1949,10 +1914,6 @@ class FeatureToggleService {
|
||||
);
|
||||
}
|
||||
|
||||
async getArchivedFeatures(): Promise<FeatureToggle[]> {
|
||||
return this.getFeatureToggles({}, undefined, true);
|
||||
}
|
||||
|
||||
// TODO: add project id.
|
||||
async deleteFeature(
|
||||
featureName: string,
|
||||
|
@ -10,13 +10,8 @@ import type { IFeatureToggleQuery } from '../../../types/model';
|
||||
import type FeatureTagService from '../../../services/feature-tag-service';
|
||||
import type { IAuthRequest } from '../../../routes/unleash-types';
|
||||
import { DEFAULT_ENV } from '../../../util/constants';
|
||||
import {
|
||||
featuresSchema,
|
||||
type FeaturesSchema,
|
||||
} from '../../../openapi/spec/features-schema';
|
||||
import type { TagSchema } from '../../../openapi/spec/tag-schema';
|
||||
import type { TagsSchema } from '../../../openapi/spec/tags-schema';
|
||||
import { serializeDates } from '../../../types/serialize-dates';
|
||||
import type { OpenApiService } from '../../../services/openapi-service';
|
||||
import { createRequestSchema } from '../../../openapi/util/create-request-schema';
|
||||
import {
|
||||
@ -55,27 +50,6 @@ class FeatureController extends Controller {
|
||||
this.openApiService = openApiService;
|
||||
this.service = featureToggleServiceV2;
|
||||
|
||||
this.route({
|
||||
method: 'get',
|
||||
path: '',
|
||||
handler: this.getAllToggles,
|
||||
permission: NONE,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['Features'],
|
||||
operationId: 'getAllToggles',
|
||||
responses: {
|
||||
200: createResponseSchema('featuresSchema'),
|
||||
...getStandardResponses(401, 403),
|
||||
},
|
||||
summary: 'Get all feature flags (deprecated)',
|
||||
description:
|
||||
'Gets all feature flags with their full configuration. This endpoint is **deprecated**. You should use the project-based endpoint instead (`/api/admin/projects/<project-id>/features`).',
|
||||
deprecated: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
this.route({
|
||||
method: 'post',
|
||||
path: '/validate',
|
||||
@ -210,23 +184,6 @@ class FeatureController extends Controller {
|
||||
return query;
|
||||
}
|
||||
|
||||
async getAllToggles(
|
||||
req: IAuthRequest,
|
||||
res: Response<FeaturesSchema>,
|
||||
): Promise<void> {
|
||||
const query = await this.prepQuery(req.query);
|
||||
|
||||
const { user } = req;
|
||||
const features = await this.service.getFeatureToggles(query, user.id);
|
||||
|
||||
this.openApiService.respondWithValidation(
|
||||
200,
|
||||
res,
|
||||
featuresSchema.$id,
|
||||
{ version, features: serializeDates(features) },
|
||||
);
|
||||
}
|
||||
|
||||
async getToggle(
|
||||
req: Request<{ featureName: string }, any, any, any>,
|
||||
res: Response,
|
||||
|
@ -70,7 +70,7 @@ test('should return last seen at per env for /api/admin/features', async () => {
|
||||
await insertLastSeenAt('lastSeenAtPerEnv', db.rawDatabase, 'default');
|
||||
|
||||
const response = await app.request
|
||||
.get('/api/admin/features')
|
||||
.get('/api/admin/projects/default/features')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200);
|
||||
|
||||
@ -88,7 +88,7 @@ test('response should include last seen at per environment for multiple environm
|
||||
|
||||
await setupLastSeenAtTest(featureName);
|
||||
const { body } = await app.request
|
||||
.get('/api/admin/features')
|
||||
.get('/api/admin/projects/default/features')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200);
|
||||
|
||||
|
@ -3667,33 +3667,6 @@ test('should not be allowed to update with invalid strategy type name', async ()
|
||||
);
|
||||
});
|
||||
|
||||
test('should return correct data structure for /api/admin/features', async () => {
|
||||
await app.createFeature('refactor-features');
|
||||
|
||||
const result = await app.request.get('/api/admin/features').expect(200);
|
||||
|
||||
expect(result.body.features).toBeInstanceOf(Array);
|
||||
|
||||
const feature = result.body.features.find(
|
||||
(features) => features.name === 'refactor-features',
|
||||
);
|
||||
|
||||
expect(feature).toMatchObject({
|
||||
impressionData: false,
|
||||
enabled: false,
|
||||
name: 'refactor-features',
|
||||
description: null,
|
||||
project: 'default',
|
||||
stale: false,
|
||||
type: 'release',
|
||||
lastSeenAt: null,
|
||||
variants: [],
|
||||
favorite: false,
|
||||
createdAt: expect.anything(),
|
||||
strategies: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('can get evaluation metrics', async () => {
|
||||
await app.createFeature('metric-feature');
|
||||
|
||||
|
@ -239,7 +239,7 @@ test('should allow requests with an admin token', async () => {
|
||||
test('should not allow admin requests with a frontend token', async () => {
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
await app.request
|
||||
.get('/api/admin/features')
|
||||
.get('/api/admin/projects')
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(403);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { randomId } from '../../util/random-id';
|
||||
import type { IFeatureToggleClient, ISegment } from '../../types/model';
|
||||
import type { ISegment } from '../../types/model';
|
||||
import { collectIds } from '../../util/collect-ids';
|
||||
import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init';
|
||||
import getLogger from '../../../test/fixtures/no-logger';
|
||||
@ -12,13 +12,17 @@ import {
|
||||
setupAppWithCustomConfig,
|
||||
} from '../../../test/e2e/helpers/test-helper';
|
||||
import type { StrategiesUsingSegment } from './segment-service-interface';
|
||||
import type { IUser } from '../../types';
|
||||
import type { IFeatureOverview, IUser } from '../../types';
|
||||
|
||||
let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
|
||||
const SEGMENTS_BASE_PATH = '/api/admin/segments';
|
||||
const FEATURES_LIST_BASE_PATH = '/api/admin/features';
|
||||
const FEATURES_LIST_BASE_PATH = '/api/admin/projects/default/features';
|
||||
|
||||
const getFeatureStrategiesPath = (featureName: string) => {
|
||||
return `/api/admin/projects/default/features/${featureName}/environments/default/strategies`;
|
||||
};
|
||||
|
||||
// Recursively change all Date properties to string properties.
|
||||
type SerializeDatesDeep<T> = {
|
||||
@ -39,12 +43,18 @@ const fetchSegmentsByStrategy = (
|
||||
.expect(200)
|
||||
.then((res) => res.body.segments);
|
||||
|
||||
const fetchFeatures = (): Promise<IFeatureToggleClient[]> =>
|
||||
const fetchFeatures = (): Promise<IFeatureOverview[]> =>
|
||||
app.request
|
||||
.get(FEATURES_LIST_BASE_PATH)
|
||||
.expect(200)
|
||||
.then((res) => res.body.features);
|
||||
|
||||
const fetchFeatureStrategies = (featureName: string) =>
|
||||
app.request
|
||||
.get(getFeatureStrategiesPath(featureName))
|
||||
.expect(200)
|
||||
.then((res) => res.body);
|
||||
|
||||
const fetchSegmentStrategies = (
|
||||
segmentId: number,
|
||||
): Promise<StrategiesUsingSegment> =>
|
||||
@ -288,8 +298,9 @@ test('should not delete segments used by strategies', async () => {
|
||||
flag.name,
|
||||
);
|
||||
const [feature] = await fetchFeatures();
|
||||
const [strategy] = await fetchFeatureStrategies(feature.name);
|
||||
//@ts-ignore
|
||||
await addSegmentsToStrategy([segment.id], feature.strategies[0].id);
|
||||
await addSegmentsToStrategy([segment.id], strategy.id);
|
||||
const segments = await fetchSegments();
|
||||
expect(segments.length).toEqual(1);
|
||||
|
||||
@ -316,8 +327,9 @@ test('should delete segments used by strategies in archived feature flags', asyn
|
||||
flag.name,
|
||||
);
|
||||
const [feature] = await fetchFeatures();
|
||||
const [strategy] = await fetchFeatureStrategies(feature.name);
|
||||
//@ts-ignore
|
||||
await addSegmentsToStrategy([segment.id], feature.strategies[0].id);
|
||||
await addSegmentsToStrategy([segment.id], strategy.id);
|
||||
const segments = await fetchSegments();
|
||||
expect(segments.length).toEqual(1);
|
||||
|
||||
@ -372,36 +384,40 @@ test('should list strategies by segment', async () => {
|
||||
const [feature1, feature2, feature3] = await fetchFeatures();
|
||||
const [segment1, segment2, segment3] = await fetchSegments();
|
||||
|
||||
const feature1Strategies = await fetchFeatureStrategies(feature1.name);
|
||||
const feature2Strategies = await fetchFeatureStrategies(feature2.name);
|
||||
const feature3Strategies = await fetchFeatureStrategies(feature3.name);
|
||||
|
||||
await addSegmentsToStrategy(
|
||||
[segment1.id, segment2.id, segment3.id],
|
||||
//@ts-ignore
|
||||
feature1.strategies[0].id,
|
||||
feature1Strategies[0].id,
|
||||
);
|
||||
await addSegmentsToStrategy(
|
||||
[segment2.id, segment3.id],
|
||||
//@ts-ignore
|
||||
feature2.strategies[0].id,
|
||||
feature2Strategies[0].id,
|
||||
);
|
||||
//@ts-ignore
|
||||
await addSegmentsToStrategy([segment3.id], feature3.strategies[0].id);
|
||||
await addSegmentsToStrategy([segment3.id], feature3Strategies[0].id);
|
||||
|
||||
const segmentStrategies1 = await fetchSegmentStrategies(segment1.id);
|
||||
const segmentStrategies2 = await fetchSegmentStrategies(segment2.id);
|
||||
const segmentStrategies3 = await fetchSegmentStrategies(segment3.id);
|
||||
|
||||
expect(collectIds(segmentStrategies1.strategies)).toEqual(
|
||||
collectIds(feature1.strategies),
|
||||
collectIds(feature1Strategies),
|
||||
);
|
||||
|
||||
expect(collectIds(segmentStrategies2.strategies)).toEqual(
|
||||
collectIds([...feature1.strategies, ...feature2.strategies]),
|
||||
collectIds([...feature1Strategies, ...feature2Strategies]),
|
||||
);
|
||||
|
||||
expect(collectIds(segmentStrategies3.strategies)).toEqual(
|
||||
collectIds([
|
||||
...feature1.strategies,
|
||||
...feature2.strategies,
|
||||
...feature3.strategies,
|
||||
...feature1Strategies,
|
||||
...feature2Strategies,
|
||||
...feature3Strategies,
|
||||
]),
|
||||
);
|
||||
});
|
||||
@ -448,30 +464,34 @@ test('should list segments by strategy', async () => {
|
||||
const [feature1, feature2, feature3] = await fetchFeatures();
|
||||
const [segment1, segment2, segment3] = await fetchSegments();
|
||||
|
||||
const [feature1Strategy] = await fetchFeatureStrategies(feature1.name);
|
||||
const [feature2Strategy] = await fetchFeatureStrategies(feature2.name);
|
||||
const [feature3Strategy] = await fetchFeatureStrategies(feature3.name);
|
||||
|
||||
await addSegmentsToStrategy(
|
||||
[segment1.id, segment2.id, segment3.id],
|
||||
//@ts-ignore
|
||||
feature1.strategies[0].id,
|
||||
feature1Strategy.id,
|
||||
);
|
||||
await addSegmentsToStrategy(
|
||||
[segment2.id, segment3.id],
|
||||
//@ts-ignore
|
||||
feature2.strategies[0].id,
|
||||
feature2Strategy.id,
|
||||
);
|
||||
//@ts-ignore
|
||||
await addSegmentsToStrategy([segment3.id], feature3.strategies[0].id);
|
||||
await addSegmentsToStrategy([segment3.id], feature3Strategy.id);
|
||||
|
||||
const strategySegments1 = await fetchSegmentsByStrategy(
|
||||
//@ts-ignore
|
||||
feature1.strategies[0].id,
|
||||
feature1Strategy.id,
|
||||
);
|
||||
const strategySegments2 = await fetchSegmentsByStrategy(
|
||||
//@ts-ignore
|
||||
feature2.strategies[0].id,
|
||||
feature2Strategy.id,
|
||||
);
|
||||
const strategySegments3 = await fetchSegmentsByStrategy(
|
||||
//@ts-ignore
|
||||
feature3.strategies[0].id,
|
||||
feature3Strategy.id,
|
||||
);
|
||||
|
||||
expect(collectIds(strategySegments1)).toEqual(
|
||||
@ -581,8 +601,9 @@ test('Should show usage in features and projects', async () => {
|
||||
flag.name,
|
||||
);
|
||||
const [feature] = await fetchFeatures();
|
||||
const [strategy] = await fetchFeatureStrategies(feature.name);
|
||||
//@ts-ignore
|
||||
await addSegmentsToStrategy([segment.id], feature.strategies[0].id);
|
||||
await addSegmentsToStrategy([segment.id], strategy.id);
|
||||
|
||||
const segments = await fetchSegments();
|
||||
expect(segments).toMatchObject([
|
||||
@ -768,8 +789,9 @@ describe('detect strategy usage in change requests', () => {
|
||||
);
|
||||
|
||||
const [feature] = await fetchFeatures();
|
||||
const [strategy] = await fetchFeatureStrategies(feature.name);
|
||||
|
||||
const strategyId = feature.strategies[0].id;
|
||||
const strategyId = strategy.id;
|
||||
|
||||
await db.rawDatabase.table('change_request_events').insert({
|
||||
feature: flag.name,
|
||||
@ -827,8 +849,9 @@ describe('detect strategy usage in change requests', () => {
|
||||
);
|
||||
|
||||
const [feature] = await fetchFeatures();
|
||||
const [strategy] = await fetchFeatureStrategies(feature.name);
|
||||
|
||||
const strategyId = feature.strategies[0].id;
|
||||
const strategyId = strategy.id;
|
||||
await addSegmentsToStrategy([segment.id], strategyId!);
|
||||
|
||||
await db.rawDatabase.table('change_request_events').insert({
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
} from '../../../test/e2e/helpers/test-helper';
|
||||
import type {
|
||||
IConstraint,
|
||||
IFeatureOverview,
|
||||
IFeatureToggleClient,
|
||||
ISegment,
|
||||
} from '../../types/model';
|
||||
@ -35,13 +36,23 @@ const fetchSegments = (): Promise<ISegment[]> => {
|
||||
return app.services.segmentService.getAll();
|
||||
};
|
||||
|
||||
const fetchFeatures = (): Promise<IFeatureToggleClient[]> => {
|
||||
const fetchFeatures = (): Promise<IFeatureOverview[]> => {
|
||||
return app.request
|
||||
.get(`/api/admin/features`)
|
||||
.get(`/api/admin/projects/default/features`)
|
||||
.expect(200)
|
||||
.then((res) => res.body.features);
|
||||
};
|
||||
|
||||
const getFeatureStrategiesPath = (featureName: string) => {
|
||||
return `/api/admin/projects/default/features/${featureName}/environments/default/strategies`;
|
||||
};
|
||||
|
||||
const fetchFeatureStrategies = (featureName: string) =>
|
||||
app.request
|
||||
.get(getFeatureStrategiesPath(featureName))
|
||||
.expect(200)
|
||||
.then((res) => res.body);
|
||||
|
||||
const fetchClientFeatures = (): Promise<IFeatureToggleClient[]> => {
|
||||
return app.request
|
||||
.get(FEATURES_CLIENT_BASE_PATH)
|
||||
@ -276,8 +287,11 @@ test('should clone feature strategy segments', async () => {
|
||||
await createFeatureToggle(mockFeatureToggle());
|
||||
|
||||
const [feature1, feature2] = await fetchFeatures();
|
||||
const strategy1 = feature1.strategies[0].id;
|
||||
const strategy2 = feature2.strategies[0].id;
|
||||
const [feature1Strategy] = await fetchFeatureStrategies(feature1.name);
|
||||
const [feature2Strategy] = await fetchFeatureStrategies(feature2.name);
|
||||
|
||||
const strategy1 = feature1Strategy.id;
|
||||
const strategy2 = feature2Strategy.id;
|
||||
|
||||
let segments1 = await app.services.segmentService.getByStrategy(strategy1!);
|
||||
let segments2 = await app.services.segmentService.getByStrategy(strategy2!);
|
||||
@ -322,9 +336,14 @@ test('should inline segment constraints into features by default', async () => {
|
||||
|
||||
// add segment3 to all features
|
||||
for (const feature of [feature1, feature2, feature3]) {
|
||||
const [strt] = await fetchFeatureStrategies(feature.name);
|
||||
const strategy = {
|
||||
...feature.strategies[0],
|
||||
segments: feature.strategies[0].segments ?? [],
|
||||
id: strt.id,
|
||||
name: strt.name,
|
||||
constraints: strt.constraints,
|
||||
parameters: strt.parameters,
|
||||
variants: strt.variants,
|
||||
segments: strt.segments ?? [],
|
||||
};
|
||||
await updateFeatureStrategy(feature.name, {
|
||||
...strategy,
|
||||
|
@ -162,7 +162,7 @@ test('should be favorited in admin endpoint', async () => {
|
||||
await favoriteFeature(featureName);
|
||||
|
||||
const { body } = await app.request
|
||||
.get('/api/admin/features')
|
||||
.get('/api/admin/projects/default/features')
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
|
||||
|
@ -42,6 +42,6 @@ test('creates new feature flag with createdBy', async () => {
|
||||
test('should require authenticated user', async () => {
|
||||
expect.assertions(0);
|
||||
const { request, destroy } = await setupAppWithAuth(db.stores);
|
||||
await request.get('/api/admin/features').expect(401);
|
||||
await request.get('/api/admin/projects/default/features').expect(401);
|
||||
await destroy();
|
||||
});
|
||||
|
@ -36,7 +36,7 @@ test('should require authenticated user', async () => {
|
||||
);
|
||||
};
|
||||
const { request, destroy } = await setupAppWithCustomAuth(stores, preHook);
|
||||
await request.get('/api/admin/features').expect(401);
|
||||
await request.get('/api/admin/projects/default/features').expect(401);
|
||||
await destroy();
|
||||
});
|
||||
|
||||
|
@ -206,7 +206,7 @@ test('Calling validate endpoint with already existing session should destroy ses
|
||||
email: 'user@mail.com',
|
||||
})
|
||||
.expect(200);
|
||||
await request.get('/api/admin/features').expect(200);
|
||||
await request.get('/api/admin/projects').expect(200);
|
||||
const url = await resetTokenService.createResetPasswordUrl(
|
||||
user.id,
|
||||
adminUser.username!,
|
||||
@ -214,7 +214,7 @@ test('Calling validate endpoint with already existing session should destroy ses
|
||||
const relative = getBackendResetUrl(url);
|
||||
|
||||
await request.get(relative).expect(200).expect('Content-Type', /json/);
|
||||
await request.get('/api/admin/features').expect(401); // we no longer should have a valid session
|
||||
await request.get('/api/admin/projects').expect(401); // we no longer should have a valid session
|
||||
await destroy();
|
||||
});
|
||||
|
||||
@ -240,7 +240,7 @@ test('Calling reset endpoint with already existing session should logout/destroy
|
||||
email: 'user@mail.com',
|
||||
})
|
||||
.expect(200);
|
||||
await request.get('/api/admin/features').expect(200); // If we login we can access features endpoint
|
||||
await request.get('/api/admin/projects').expect(200); // If we login we can access projects endpoint
|
||||
await request
|
||||
.post('/auth/reset/password')
|
||||
.send({
|
||||
@ -248,7 +248,7 @@ test('Calling reset endpoint with already existing session should logout/destroy
|
||||
password,
|
||||
})
|
||||
.expect(200);
|
||||
await request.get('/api/admin/features').expect(401); // we no longer have a valid session after using the reset password endpoint
|
||||
await request.get('/api/admin/projects').expect(401); // we no longer have a valid session after using the reset password endpoint
|
||||
await destroy();
|
||||
});
|
||||
|
||||
|
@ -43,7 +43,7 @@ test('Using custom auth type without defining custom middleware causes default D
|
||||
undefined,
|
||||
);
|
||||
await request
|
||||
.get('/api/admin/features')
|
||||
.get('/api/admin/projects')
|
||||
.expect(401)
|
||||
.expect((res) => {
|
||||
expect(res.body.error).toBe(
|
||||
@ -56,6 +56,6 @@ test('Using custom auth type without defining custom middleware causes default D
|
||||
test('If actually configuring a custom middleware should configure the middleware', async () => {
|
||||
expect.assertions(0);
|
||||
const { request, destroy } = await setupAppWithCustomAuth(stores, preHook);
|
||||
await request.get('/api/admin/features').expect(200);
|
||||
await request.get('/api/admin/projects').expect(200);
|
||||
await destroy();
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user