mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
implement proxy all endpoint (#2460)
Signed-off-by: andreas-unleash <andreas@getunleash.ai> <!-- Thanks for creating a PR! To make it easier for reviewers and everyone else to understand what your changes relate to, please add some relevant content to the headings below. Feel free to ignore or delete sections that you don't think are relevant. Thank you! ❤️ --> This PR implements the `all` endpoint of unleash-proxy, by adding an experimental flag that can control the behaviour ## About the changes <!-- Describe the changes introduced. What are they and why are they being introduced? Feel free to also add screenshots or steps to view the changes if they're visual. --> <!-- Does it close an issue? Multiple? --> Closes # <!-- (For internal contributors): Does it relate to an issue on public roadmap? --> <!-- Relates to [roadmap](https://github.com/orgs/Unleash/projects/10) item: # --> ### Important files <!-- PRs can contain a lot of changes, but not all changes are equally important. Where should a reviewer start looking to get an overview of the changes? Are any files particularly important? --> ## Discussion points <!-- Anything about the PR you'd like to discuss before it gets merged? Got any questions or doubts? --> Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
parent
efd47b72a8
commit
6e5b214475
@ -73,6 +73,7 @@ exports[`should create default config 1`] = `
|
||||
"cloneEnvironment": false,
|
||||
"embedProxy": false,
|
||||
"embedProxyFrontend": false,
|
||||
"proxyReturnAllToggles": false,
|
||||
"responseTimeWithAppName": false,
|
||||
"syncSSOGroups": false,
|
||||
"toggleTagFiltering": false,
|
||||
@ -88,6 +89,7 @@ exports[`should create default config 1`] = `
|
||||
"cloneEnvironment": false,
|
||||
"embedProxy": false,
|
||||
"embedProxyFrontend": false,
|
||||
"proxyReturnAllToggles": false,
|
||||
"responseTimeWithAppName": false,
|
||||
"syncSSOGroups": false,
|
||||
"toggleTagFiltering": false,
|
||||
|
@ -10,7 +10,7 @@ const ClientApi = require('./client-api');
|
||||
const Controller = require('./controller');
|
||||
import { HealthCheckController } from './health-check';
|
||||
import ProxyController from './proxy-api';
|
||||
import { conditionalMiddleware } from '../middleware/conditional-middleware';
|
||||
import { conditionalMiddleware } from '../middleware';
|
||||
import EdgeController from './edge-api';
|
||||
import { PublicInviteController } from './public-invite';
|
||||
|
||||
@ -47,7 +47,8 @@ class IndexRouter extends Controller {
|
||||
'/api/frontend',
|
||||
conditionalMiddleware(
|
||||
() => config.flagResolver.isEnabled('embedProxy'),
|
||||
new ProxyController(config, services).router,
|
||||
new ProxyController(config, services, config.flagResolver)
|
||||
.router,
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -1,21 +1,25 @@
|
||||
import { Response, Request } from 'express';
|
||||
import { Request, Response } from 'express';
|
||||
import Controller from '../controller';
|
||||
import { IUnleashConfig, IUnleashServices } from '../../types';
|
||||
import {
|
||||
IFlagResolver,
|
||||
IUnleashConfig,
|
||||
IUnleashServices,
|
||||
NONE,
|
||||
} from '../../types';
|
||||
import { Logger } from '../../logger';
|
||||
import { NONE } from '../../types/permissions';
|
||||
import ApiUser from '../../types/api-user';
|
||||
import {
|
||||
createRequestSchema,
|
||||
createResponseSchema,
|
||||
emptyResponse,
|
||||
ProxyClientSchema,
|
||||
proxyFeaturesSchema,
|
||||
ProxyFeaturesSchema,
|
||||
} from '../../openapi/spec/proxy-features-schema';
|
||||
ProxyMetricsSchema,
|
||||
} from '../../openapi';
|
||||
import { Context } from 'unleash-client';
|
||||
import { enrichContextWithIp } from '../../proxy/create-context';
|
||||
import { ProxyMetricsSchema } from '../../openapi/spec/proxy-metrics-schema';
|
||||
import { ProxyClientSchema } from '../../openapi/spec/proxy-client-schema';
|
||||
import { createResponseSchema } from '../../openapi/util/create-response-schema';
|
||||
import { createRequestSchema } from '../../openapi/util/create-request-schema';
|
||||
import { emptyResponse } from '../../openapi/util/standard-responses';
|
||||
import { corsOriginMiddleware } from '../../middleware/cors-origin-middleware';
|
||||
import { enrichContextWithIp } from '../../proxy';
|
||||
import { corsOriginMiddleware } from '../../middleware';
|
||||
|
||||
interface ApiUserRequest<
|
||||
PARAM = any,
|
||||
@ -36,10 +40,17 @@ export default class ProxyController extends Controller {
|
||||
|
||||
private services: Services;
|
||||
|
||||
constructor(config: IUnleashConfig, services: Services) {
|
||||
private flagResolver: IFlagResolver;
|
||||
|
||||
constructor(
|
||||
config: IUnleashConfig,
|
||||
services: Services,
|
||||
flagResolver: IFlagResolver,
|
||||
) {
|
||||
super(config);
|
||||
this.logger = config.getLogger('proxy-api/index.ts');
|
||||
this.services = services;
|
||||
this.flagResolver = flagResolver;
|
||||
|
||||
// Support CORS requests for the frontend endpoints.
|
||||
// Preflight requests are handled in `app.ts`.
|
||||
@ -133,10 +144,18 @@ export default class ProxyController extends Controller {
|
||||
req: ApiUserRequest,
|
||||
res: Response<ProxyFeaturesSchema>,
|
||||
) {
|
||||
const toggles = await this.services.proxyService.getProxyFeatures(
|
||||
req.user,
|
||||
ProxyController.createContext(req),
|
||||
);
|
||||
let toggles;
|
||||
if (this.flagResolver.isEnabled('proxyReturnAllToggles')) {
|
||||
toggles = await this.services.proxyService.getAllProxyFeatures(
|
||||
req.user,
|
||||
ProxyController.createContext(req),
|
||||
);
|
||||
} else {
|
||||
toggles = await this.services.proxyService.getProxyFeatures(
|
||||
req.user,
|
||||
ProxyController.createContext(req),
|
||||
);
|
||||
}
|
||||
this.services.openApiService.respondWithValidation(
|
||||
200,
|
||||
res,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { IUnleashConfig } from '../types/option';
|
||||
import { IUnleashConfig, IUnleashServices, IUnleashStores } from '../types';
|
||||
import { Logger } from '../logger';
|
||||
import { IUnleashServices, IUnleashStores } from '../types';
|
||||
import { ProxyFeatureSchema } from '../openapi/spec/proxy-feature-schema';
|
||||
import { ProxyFeatureSchema, ProxyMetricsSchema } from '../openapi';
|
||||
import ApiUser from '../types/api-user';
|
||||
import {
|
||||
Context,
|
||||
@ -10,10 +9,9 @@ import {
|
||||
Unleash,
|
||||
UnleashEvents,
|
||||
} from 'unleash-client';
|
||||
import { ProxyRepository } from '../proxy/proxy-repository';
|
||||
import { ProxyRepository } from '../proxy';
|
||||
import assert from 'assert';
|
||||
import { ApiTokenType } from '../types/models/api-token';
|
||||
import { ProxyMetricsSchema } from '../openapi/spec/proxy-metrics-schema';
|
||||
|
||||
type Config = Pick<IUnleashConfig, 'getLogger' | 'frontendApi'>;
|
||||
|
||||
@ -59,6 +57,21 @@ export class ProxyService {
|
||||
}));
|
||||
}
|
||||
|
||||
async getAllProxyFeatures(
|
||||
token: ApiUser,
|
||||
context: Context,
|
||||
): Promise<ProxyFeatureSchema[]> {
|
||||
const client = await this.clientForProxyToken(token);
|
||||
const definitions = client.getFeatureToggleDefinitions() || [];
|
||||
|
||||
return definitions.map((feature) => ({
|
||||
name: feature.name,
|
||||
enabled: Boolean(feature.enabled),
|
||||
variant: client.forceGetVariant(feature.name, context),
|
||||
impressionData: Boolean(feature.impressionData),
|
||||
}));
|
||||
}
|
||||
|
||||
async registerProxyMetrics(
|
||||
token: ApiUser,
|
||||
metrics: ProxyMetricsSchema,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { parseEnvVarBoolean } from '../util/parseEnvVar';
|
||||
import { parseEnvVarBoolean } from '../util';
|
||||
|
||||
export type IFlags = Partial<Record<string, boolean>>;
|
||||
|
||||
@ -38,6 +38,10 @@ export const defaultExperimentalOptions = {
|
||||
process.env.UNLEASH_EXPERIMENTAL_TOGGLE_TAG_FILTERING,
|
||||
false,
|
||||
),
|
||||
proxyReturnAllToggles: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_PROXY_RETURN_ALL_TOGGLES,
|
||||
false,
|
||||
),
|
||||
variantsPerEnvironment: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_VARIANTS_PER_ENVIRONMENT,
|
||||
false,
|
||||
@ -57,6 +61,7 @@ export interface IExperimentalOptions {
|
||||
syncSSOGroups?: boolean;
|
||||
changeRequests?: boolean;
|
||||
cloneEnvironment?: boolean;
|
||||
proxyReturnAllToggles?: boolean;
|
||||
variantsPerEnvironment?: boolean;
|
||||
};
|
||||
externalResolver: IExternalFlagResolver;
|
||||
|
@ -1,16 +1,19 @@
|
||||
import { IUnleashTest, setupAppWithAuth } from '../../helpers/test-helper';
|
||||
import dbInit, { ITestDb } from '../../helpers/database-init';
|
||||
import getLogger from '../../../fixtures/no-logger';
|
||||
import { randomId } from '../../../../lib/util/random-id';
|
||||
import { randomId } from '../../../../lib/util';
|
||||
import {
|
||||
ApiTokenType,
|
||||
IApiToken,
|
||||
IApiTokenCreate,
|
||||
} from '../../../../lib/types/models/api-token';
|
||||
import { startOfHour } from 'date-fns';
|
||||
import { IConstraint, IStrategyConfig } from '../../../../lib/types/model';
|
||||
import { ProxyRepository } from '../../../../lib/proxy/proxy-repository';
|
||||
import { FEATURE_UPDATED } from '../../../../lib/types/events';
|
||||
import {
|
||||
FEATURE_UPDATED,
|
||||
IConstraint,
|
||||
IStrategyConfig,
|
||||
} from '../../../../lib/types';
|
||||
import { ProxyRepository } from '../../../../lib/proxy';
|
||||
|
||||
let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
@ -929,3 +932,52 @@ test('Should not recursively set off timers on events', async () => {
|
||||
expect(spy.mock.calls.length < 3).toBe(true);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('should return all features when specified', async () => {
|
||||
app.config.experimental.flags.proxyReturnAllToggles = true;
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
await createFeatureToggle({
|
||||
name: 'enabledFeature1',
|
||||
enabled: true,
|
||||
strategies: [{ name: 'default', constraints: [], parameters: {} }],
|
||||
});
|
||||
await createFeatureToggle({
|
||||
name: 'enabledFeature2',
|
||||
enabled: true,
|
||||
strategies: [{ name: 'default', constraints: [], parameters: {} }],
|
||||
});
|
||||
await createFeatureToggle({
|
||||
name: 'disabledFeature',
|
||||
enabled: false,
|
||||
strategies: [{ name: 'default', constraints: [], parameters: {} }],
|
||||
});
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', frontendToken.secret)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body).toEqual({
|
||||
toggles: [
|
||||
{
|
||||
name: 'enabledFeature1',
|
||||
enabled: true,
|
||||
impressionData: false,
|
||||
variant: { enabled: false, name: 'disabled' },
|
||||
},
|
||||
{
|
||||
name: 'enabledFeature2',
|
||||
enabled: true,
|
||||
impressionData: false,
|
||||
variant: { enabled: false, name: 'disabled' },
|
||||
},
|
||||
{
|
||||
name: 'disabledFeature',
|
||||
enabled: false,
|
||||
impressionData: false,
|
||||
variant: { enabled: false, name: 'disabled' },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -4,7 +4,7 @@ import supertest from 'supertest';
|
||||
import EventEmitter from 'events';
|
||||
import getApp from '../../../lib/app';
|
||||
import { createTestConfig } from '../../config/test-config';
|
||||
import { IAuthType } from '../../../lib/types/option';
|
||||
import { IAuthType, IUnleashConfig } from '../../../lib/types/option';
|
||||
import { createServices } from '../../../lib/services';
|
||||
import sessionDb from '../../../lib/middleware/session-db';
|
||||
import { IUnleashStores } from '../../../lib/types';
|
||||
@ -16,6 +16,7 @@ export interface IUnleashTest {
|
||||
request: supertest.SuperAgentTest;
|
||||
destroy: () => Promise<void>;
|
||||
services: IUnleashServices;
|
||||
config: IUnleashConfig;
|
||||
}
|
||||
|
||||
async function createApp(
|
||||
@ -49,7 +50,7 @@ async function createApp(
|
||||
};
|
||||
|
||||
// TODO: use create from server-impl instead?
|
||||
return { request, destroy, services };
|
||||
return { request, destroy, services, config };
|
||||
}
|
||||
|
||||
export async function setupApp(stores: IUnleashStores): Promise<IUnleashTest> {
|
||||
|
Loading…
Reference in New Issue
Block a user