Skip to content

Commit 8cbfb3d

Browse files
[FSSDK-12009] configurable prediction endpoint (#1102)
1 parent 3ae698b commit 8cbfb3d

File tree

4 files changed

+57
-4
lines changed

4 files changed

+57
-4
lines changed

lib/client_factory.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ export const getOptimizelyInstance = (config: OptimizelyFactoryConfig): Optimize
5757
retryConfig: {
5858
maxRetries: DEFAULT_CMAB_RETRIES,
5959
backoffProvider: () => new ConstantBackoff(DEFAULT_CMAB_BACKOFF_MS),
60-
}
60+
},
61+
predictionEndpointTemplate: config.cmab?.predictionEndpointTemplate,
6162
});
6263

6364
const cmabCache: CacheWithRemove<CmabCacheValue> = config.cmab?.cache ?

lib/core/decision_service/cmab/cmab_client.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,4 +354,50 @@ describe('DefaultCmabClient', () => {
354354

355355
await expect(cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid)).rejects.toThrow('error');
356356
});
357+
358+
it('should use custom prediction endpoint template when provided', async () => {
359+
const requestHandler = getMockRequestHandler();
360+
361+
const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest;
362+
mockMakeRequest.mockReturnValue(getMockAbortableRequest(mockSuccessResponse('var456')));
363+
364+
const customEndpoint = 'https://custom.example.com/predict/%s';
365+
const cmabClient = new DefaultCmabClient({
366+
requestHandler,
367+
predictionEndpointTemplate: customEndpoint,
368+
});
369+
const ruleId = '789';
370+
const userId = 'user789';
371+
const attributes = {
372+
browser: 'firefox',
373+
};
374+
const cmabUuid = 'uuid789';
375+
const variation = await cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid);
376+
const [requestUrl] = mockMakeRequest.mock.calls[0];
377+
378+
expect(variation).toBe('var456');
379+
expect(mockMakeRequest.mock.calls.length).toBe(1);
380+
expect(requestUrl).toBe('https://custom.example.com/predict/789');
381+
});
382+
383+
it('should use default prediction endpoint template when not provided', async () => {
384+
const requestHandler = getMockRequestHandler();
385+
const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest;
386+
mockMakeRequest.mockReturnValue(getMockAbortableRequest(mockSuccessResponse('var999')));
387+
const cmabClient = new DefaultCmabClient({
388+
requestHandler,
389+
});
390+
const ruleId = '555';
391+
const userId = 'user555';
392+
const attributes = {
393+
browser: 'safari',
394+
};
395+
const cmabUuid = 'uuid555';
396+
const variation = await cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid);
397+
const [requestUrl] = mockMakeRequest.mock.calls[0];
398+
399+
expect(variation).toBe('var999');
400+
expect(mockMakeRequest.mock.calls.length).toBe(1);
401+
expect(requestUrl).toBe('https://prediction.cmab.optimizely.com/predict/555');
402+
});
357403
});

lib/core/decision_service/cmab/cmab_client.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,25 +33,30 @@ export interface CmabClient {
3333
): Promise<string>
3434
}
3535

36-
const CMAB_PREDICTION_ENDPOINT = 'https://prediction.cmab.optimizely.com/predict/%s';
36+
const DEFAULT_CMAB_PREDICTION_ENDPOINT = 'https://prediction.cmab.optimizely.com/predict/%s';
3737

3838
export type RetryConfig = {
3939
maxRetries: number,
4040
backoffProvider?: Producer<BackoffController>;
4141
}
4242

4343
export type CmabClientConfig = {
44-
requestHandler: RequestHandler,
44+
requestHandler: RequestHandler;
4545
retryConfig?: RetryConfig;
46+
predictionEndpointTemplate?: string;
4647
}
4748

4849
export class DefaultCmabClient implements CmabClient {
4950
private requestHandler: RequestHandler;
5051
private retryConfig?: RetryConfig;
52+
private predictionEndpointTemplate: string = DEFAULT_CMAB_PREDICTION_ENDPOINT;
5153

5254
constructor(config: CmabClientConfig) {
5355
this.requestHandler = config.requestHandler;
5456
this.retryConfig = config.retryConfig;
57+
if (config.predictionEndpointTemplate) {
58+
this.predictionEndpointTemplate = config.predictionEndpointTemplate;
59+
}
5560
}
5661

5762
async fetchDecision(
@@ -60,7 +65,7 @@ export class DefaultCmabClient implements CmabClient {
6065
attributes: UserAttributes,
6166
cmabUuid: string,
6267
): Promise<string> {
63-
const url = sprintf(CMAB_PREDICTION_ENDPOINT, ruleId);
68+
const url = sprintf(this.predictionEndpointTemplate, ruleId);
6469

6570
const cmabAttributes = Object.keys(attributes).map((key) => ({
6671
id: key,

lib/shared_types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ export interface Config {
402402
cacheSize?: number;
403403
cacheTtl?: number;
404404
cache?: CacheWithRemove<string>;
405+
predictionEndpointTemplate?: string;
405406
}
406407
}
407408

0 commit comments

Comments
 (0)