Skip to content

[FSSDK-10882] ProjectConfigManager SSR support #965

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions lib/index.node.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import * as loggerPlugin from './plugins/logger';
import optimizelyFactory from './index.node';
import configValidator from './utils/config_validator';
import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager';
import { createProjectConfig } from './project_config/project_config';

describe('optimizelyFactory', function() {
describe('APIs', function() {
Expand Down Expand Up @@ -88,6 +89,22 @@ describe('optimizelyFactory', function() {
assert.equal(optlyInstance.clientVersion, '5.3.4');
});

it('should create an instance of optimizely with ssr flag, project config must be available', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

could you please add the test in a new index.node.spec.ts file instead? We want to get rid of all JS tests. We will only update existing tests in these old tests file and will add all new tests in new ts test files.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This won't be required anymore due to the new index.spec.ts coverage

const optlyInstance = optimizelyFactory.createInstance({
projectConfigManager: getMockProjectConfigManager({
initConfig: createProjectConfig(testData.getTestProjectConfig()),
}),
errorHandler: fakeErrorHandler,
eventDispatcher: fakeEventDispatcher,
logger: fakeLogger,
isSsr: true,
});

assert.instanceOf(optlyInstance, Optimizely);
assert.equal(optlyInstance.projectConfigManager.isSsr, true);
assert.deepEqual(optlyInstance.getProjectConfig(), createProjectConfig(testData.getTestProjectConfig()));
});

// TODO: user will create and inject an event processor
// these tests will be refactored accordingly
// describe('event processor configuration', function() {
Expand Down
1 change: 1 addition & 0 deletions lib/optimizely/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export default class Optimizely implements Client {
this.updateOdpSettings();
});

this.projectConfigManager.setSsr(config.isSsr)
Copy link
Contributor

Choose a reason for hiding this comment

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

can we add a test that isSsr is passed to the projectConfigManager?

Please do this in a new lib/optimizely/index.spec.ts file instead of the old js test file. We will eventually get rid of the whole js test file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok. I did not know about that change.

this.projectConfigManager.start();
const projectConfigManagerRunningPromise = this.projectConfigManager.onRunning();

Expand Down
11 changes: 11 additions & 0 deletions lib/project_config/project_config_manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,17 @@ describe('ProjectConfigManagerImpl', () => {
await manager.onRunning();
expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig()));
});

it('should not start datafileManager if isSsr is true and return correct config', () => {
const datafileManager = getMockDatafileManager({});
vi.spyOn(datafileManager, 'start');
const manager = new ProjectConfigManagerImpl({ datafile: testData.getTestProjectConfig(), datafileManager });
manager.setSsr(true);
manager.start();

expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig()));
expect(datafileManager.start).not.toHaveBeenCalled();
});
});

describe('when datafile is invalid', () => {
Expand Down
16 changes: 16 additions & 0 deletions lib/project_config/project_config_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interface ProjectConfigManagerConfig {

export interface ProjectConfigManager extends Service {
setLogger(logger: LoggerFacade): void;
setSsr(isSsr?: boolean): void;
getConfig(): ProjectConfig | undefined;
getOptimizelyConfig(): OptimizelyConfig | undefined;
onUpdate(listener: Consumer<ProjectConfig>): Fn;
Expand All @@ -54,6 +55,7 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf
public datafileManager?: DatafileManager;
private eventEmitter: EventEmitter<{ update: ProjectConfig }> = new EventEmitter();
private logger?: LoggerFacade;
private isSsr?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

instead of optional boolean, we can use a boolean with default false

private isSsr = false;


constructor(config: ProjectConfigManagerConfig) {
super();
Expand All @@ -79,6 +81,11 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf
return;
}

if(this.isSsr) {
Copy link
Contributor

Choose a reason for hiding this comment

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

if isSsr is true no datafile is provided, but only a datafileManager is provided, then onRunning() will wait indefinitely cause we are dropping the datafile. We can move this condition before the previous if on line 78 and update the message inside for isSsr case

Copy link
Contributor

Choose a reason for hiding this comment

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

Also can we add a test that if isSsr is provided without a datafile, onRunning() is rejected?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. I have missed that edge case. Thanks for pointing it out..

// If isSsr is true, we don't need to poll for datafile updates
this.datafileManager = undefined
}

if (this.datafile) {
this.handleNewDatafile(this.datafile, true);
}
Expand Down Expand Up @@ -216,4 +223,13 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf
this.stopPromise.reject(err);
});
}

/**
* Set the isSsr flag to indicate if the project config manager is being used in a server side rendering environment
* @param {Boolean} isSsr
* @returns {void}
*/
setSsr(isSsr?: boolean): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we use boolean parameter instead of optional boolean?

this.isSsr = isSsr;
}
}
2 changes: 2 additions & 0 deletions lib/shared_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ export interface OptimizelyOptions {
sdkKey?: string;
userProfileService?: UserProfileService | null;
defaultDecideOptions?: OptimizelyDecideOption[];
isSsr?:boolean;
odpManager?: IOdpManager;
notificationCenter: NotificationCenterImpl;
}
Expand Down Expand Up @@ -425,6 +426,7 @@ export interface ConfigLite {
defaultDecideOptions?: OptimizelyDecideOption[];
clientEngine?: string;
clientVersion?: string;
isSsr?: boolean;
}

export type OptimizelyExperimentsMap = {
Expand Down
3 changes: 3 additions & 0 deletions lib/tests/mock/mock_project_config_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export const getMockProjectConfigManager = (opt: MockOpt = {}): ProjectConfigMan
return {
config: opt.initConfig,
start: () => {},
setSsr: function(isSsr?:boolean) {
this.isSsr = isSsr;
},
onRunning: () => opt.onRunning || Promise.resolve(),
stop: () => {},
onTerminated: () => opt.onTerminated || Promise.resolve(),
Expand Down
Loading