Skip to content
65 changes: 0 additions & 65 deletions src/appstudio_strategy.ts

This file was deleted.

60 changes: 60 additions & 0 deletions src/strategy/appStudioMultiStrategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Strategy } from "unleash-client";
import { AppStudioMultiContext } from "../appstudio_context";
import { log } from "../logger";
import { AppStudioStrategy } from "./appStudioStrategies";

// The name of the str in the server
const APP_STUDIO_MULTI = "AppStudioMulti";

export interface AppStudioMultiParameters {
environments: string; // apps
infrastructures: string; //iaass
landscapes: string; // regions
subaccounts: string;
users: string;
wss: string;
tenantids: string;
}

export class AppStudioMultiStrategy extends Strategy {
private strategies: AppStudioStrategy[] = [];

constructor() {
super(APP_STUDIO_MULTI); // for testing the name is: StrategyMulti
}

public register(appStudioStrategies: AppStudioStrategy[]): void {
appStudioStrategies.forEach((appStudioStrategy) => {
const strategyName = appStudioStrategy.getName();
const isExist = this.strategies.some((strategy) => strategy.getName() === strategyName);
if (isExist) {
throw new Error(`Strategy ${strategyName} already registered`);
}

log(`Added strategy: ${strategyName}`);
this.strategies.push(appStudioStrategy);
});
}

// Check only the strategy parameters. the default strategy is calculated at the server side
/** Template method */
isEnabled(parameters: AppStudioMultiParameters, context: AppStudioMultiContext): boolean {
// Check if the user has any restrictions defined in the strategy (defined in the FT server)
const isNotDefined = this.strategies.every((strategy) => !strategy.isDefined(parameters));
if (isNotDefined) {
// No restriction from the strategy side
return true;
}

// context.currentEnvironment - The Environment that the extension is running on (calculated in this client)
// parameters.environments // The Environments that the extension is allowed to be enabled on (list is defined on the server)
// Check if strategy meet one of the terms
const isStrategyMet = this.strategies.some((strategy) => strategy.isEnabled(parameters, context));
if (isStrategyMet) {
return true;
}

// no matches -> send "disable the feature" to the FT server
return false;
}
}
18 changes: 18 additions & 0 deletions src/strategy/appStudioStrategies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { AppStudioMultiContext } from "../appstudio_context";
import { AppStudioMultiParameters, AppStudioMultiStrategy } from "./appStudioMultiStrategy";
import { EnvironmentsStrategy, InfrastructuresStrategy, LandscapesStrategy, SubAccountsStrategy, TenantIdsStrategy, UsersStrategy, WssStrategy } from "./strategies";

export interface AppStudioStrategy {
getName(): string;

// Check if the user has any restrictions defined in the strategy (defined in the FT server)
isDefined(parameters: AppStudioMultiParameters): boolean;

// context.currentEnvironment - The Environment that the extension is running on (calculated in this client)
// parameters.environments // The Environments that the extension is allowed to be enabled on (list is defined on the server)
isEnabled(parameters: AppStudioMultiParameters, context: AppStudioMultiContext): boolean;
}

export function registerStrategies(appStudioMultiStrategy: AppStudioMultiStrategy): void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method doesnt feel in the right place.
You define abstract class here for strategy and then comes this specific method which use instances of classes which implement this abstract class.
I would do it as instance method of AppStudioMultiStrategy class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppStudioMultiStrategy is a generic class that should not know the concrete implementation.
unleash_client_manager.ts manage AppStudioMultiStrategy, initialize and register the strategies

appStudioMultiStrategy.register([new EnvironmentsStrategy(), new InfrastructuresStrategy(), new LandscapesStrategy(), new SubAccountsStrategy(), new UsersStrategy(), new WssStrategy(), new TenantIdsStrategy()]);
}
122 changes: 122 additions & 0 deletions src/strategy/strategies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { AppStudioMultiContext } from "../appstudio_context";
import { AppStudioStrategy } from "./appStudioStrategies";
import { AppStudioMultiParameters } from "./appStudioMultiStrategy";

export class EnvironmentsStrategy implements AppStudioStrategy {
getName(): string {
return `Environments_Strategy`;
}

isDefined(parameters: AppStudioMultiParameters): boolean {
return !!parameters.environments;
}

isEnabled(parameters: AppStudioMultiParameters, context: AppStudioMultiContext): boolean {
if (!!context.currentEnvironment && parameters.environments && parameters.environments.indexOf(context.currentEnvironment) !== -1) {
return true;
}
return false;
}
}

export class InfrastructuresStrategy implements AppStudioStrategy {
getName(): string {
return `Infrastructures_Strategy`;
}

isDefined(parameters: AppStudioMultiParameters): boolean {
return !!parameters.infrastructures;
}

isEnabled(parameters: AppStudioMultiParameters, context: AppStudioMultiContext): boolean {
if (!!context.currentInfrastructure && parameters.infrastructures && parameters.infrastructures.indexOf(context.currentInfrastructure) !== -1) {
return true;
}
return false;
}
}

export class LandscapesStrategy implements AppStudioStrategy {
getName(): string {
return `Landscapes_Strategy`;
}

isDefined(parameters: AppStudioMultiParameters): boolean {
return !!parameters.landscapes;
}

isEnabled(parameters: AppStudioMultiParameters, context: AppStudioMultiContext): boolean {
if (!!context.currentLandscape && parameters.landscapes && parameters.landscapes.indexOf(context.currentLandscape) !== -1) {
return true;
}
return false;
}
}

export class SubAccountsStrategy implements AppStudioStrategy {
getName(): string {
return `SubAccounts_Strategy`;
}

isDefined(parameters: AppStudioMultiParameters): boolean {
return !!parameters.subaccounts;
}

isEnabled(parameters: AppStudioMultiParameters, context: AppStudioMultiContext): boolean {
if (!!context.currentCfSubAccount && parameters.subaccounts && parameters.subaccounts.indexOf(context.currentCfSubAccount) !== -1) {
return true;
}
return false;
}
}

export class UsersStrategy implements AppStudioStrategy {
getName(): string {
return `Users_Strategy`;
}

isDefined(parameters: AppStudioMultiParameters): boolean {
return !!parameters.users;
}

isEnabled(parameters: AppStudioMultiParameters, context: AppStudioMultiContext): boolean {
if (!!context.currentUser && parameters.users && parameters.users.indexOf(context.currentUser) !== -1) {
return true;
}
return false;
}
}

export class WssStrategy implements AppStudioStrategy {
getName(): string {
return `Wss_Strategy`;
}

isDefined(parameters: AppStudioMultiParameters): boolean {
return !!parameters.wss;
}

isEnabled(parameters: AppStudioMultiParameters, context: AppStudioMultiContext): boolean {
if (!!context.currentWs && parameters.wss && parameters.wss.indexOf(context.currentWs) !== -1) {
return true;
}
return false;
}
}

export class TenantIdsStrategy implements AppStudioStrategy {
getName(): string {
return `TenantIds_Strategy`;
}

isDefined(parameters: AppStudioMultiParameters): boolean {
return !!parameters.tenantids;
}

isEnabled(parameters: AppStudioMultiParameters, context: AppStudioMultiContext): boolean {
if (!!context.currentTenantId && parameters.tenantids && parameters.tenantids.indexOf(context.currentTenantId) !== -1) {
return true;
}
return false;
}
}
7 changes: 5 additions & 2 deletions src/unleash_client_manager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Unleash } from "unleash-client";
import * as ServerArgs from "./server_arguments";
import { initializeUnleashClient } from "./unleash_client_wrapper";
import { AppStudioMultiStrategy } from "./appstudio_strategy";
import { AppStudioMultiStrategy } from "./strategy/appStudioMultiStrategy";
import { registerStrategies } from "./strategy/appStudioStrategies";

// Map of Unleash clients.
// map key = extensionName
Expand All @@ -13,7 +14,9 @@ async function createNewUnleashClient(extensionName: string, unleashClientMap: M
const serverArgs = ServerArgs.getServerArgs();

//init client
const client = await initializeUnleashClient(extensionName, serverArgs, [new AppStudioMultiStrategy()]);
const appStudioMultiStrategy = new AppStudioMultiStrategy();
const client = await initializeUnleashClient(extensionName, serverArgs, [appStudioMultiStrategy]);
registerStrategies(appStudioMultiStrategy);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer instance method: appStudioMultiStrategy.registerStrategies()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppStudioMultiStrategy is a generic class that should not know the concrete implementation.
unleash_client_manager.ts manage AppStudioMultiStrategy, initialize and register the strategies


//add the client to the map
unleashClientMap.set(extensionName, client);
Expand Down
12 changes: 11 additions & 1 deletion test/appstudio_strategy.spec.ts → test/strategy.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { expect } from "chai";
import * as sinon from "sinon";
import { describe, afterEach, it } from "mocha";
import * as appstudioStrategy from "../src/appstudio_strategy";
import * as appstudioStrategy from "../src/strategy/appStudioMultiStrategy";
import * as appstudioContext from "../src/appstudio_context";
import { registerStrategies } from "../src/strategy/appStudioStrategies";
import { EnvironmentsStrategy } from "../src/strategy/strategies";

describe("Strategy unit tests", () => {
const appStudioMultiStrategy = new appstudioStrategy.AppStudioMultiStrategy();
registerStrategies(appStudioMultiStrategy);

afterEach(() => {
sinon.restore();
Expand All @@ -19,6 +22,7 @@ describe("Strategy unit tests", () => {
subaccounts: null,
users: "",
wss: "",
tenantids: "",
};

const isEnabled = appStudioMultiStrategy.isEnabled(serverParams, null);
Expand Down Expand Up @@ -275,4 +279,10 @@ describe("Strategy unit tests", () => {

expect(isEnabled).to.be.false;
});

it("register throw error when strategy already in the strategy list", () => {
expect(() => {
appStudioMultiStrategy.register([new EnvironmentsStrategy()]);
}).to.throw(`Strategy Environments_Strategy already registered`);
});
});
12 changes: 12 additions & 0 deletions test/unleash_client_manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { UnleashConfig } from "unleash-client/lib/unleash";
import * as clientManager from "../src/unleash_client_manager";
import * as serverArgs from "../src/server_arguments";
import * as unleashClientWrapper from "../src/unleash_client_wrapper";
import * as appStudioStrategies from "../src/strategy/appStudioStrategies";

describe("Test unleash client manager", () => {
const extensionNameA = "aaa";
Expand Down Expand Up @@ -79,4 +80,15 @@ describe("Test unleash client manager", () => {
const err = await clientManager.getUnleashClientFromMap(extensionNameA, unleashClientMap).catch((err) => err.message);
expect(err).to.equal("Failed to create Unleash client for extension aaa. Error message: TypeError: client.on is not a function");
});

it("registerStrategies is called when creating a new client", async () => {
prepGetUnleashClientTests();

const registerSpy = sinon.spy(appStudioStrategies, "registerStrategies");

// create a new client
await clientManager.getUnleashClientFromMap(extensionNameA, unleashClientMap);

expect(registerSpy.callCount).to.equal(1);
});
});