diff --git a/clientlibs/js/src/ApiService/ApiService.ts b/clientlibs/js/src/ApiService/ApiService.ts index ae88f72df9..d13a32d273 100644 --- a/clientlibs/js/src/ApiService/ApiService.ts +++ b/clientlibs/js/src/ApiService/ApiService.ts @@ -1,5 +1,5 @@ import { UpGradeClientEnums, UpGradeClientInterfaces, UpGradeClientRequests } from '../types'; -import { CaliperEnvelope, IExperimentAssignmentv5, ILogInput, IUserAliases, ILogRequestBody } from 'upgrade_types'; +import { CaliperEnvelope, IExperimentAssignment, ILogInput, IUserAliases, ILogRequestBody } from 'upgrade_types'; import { DataService } from 'DataService/DataService'; import { IApiServiceRequestParams, IEndpoints } from './ApiService.types'; @@ -210,12 +210,12 @@ export default class ApiService { }); } - public getAllExperimentConditions(): Promise { + public getAllExperimentConditions(): Promise { const requestBody: UpGradeClientRequests.IGetAllExperimentConditionsRequestBody = { context: this.context, }; - return this.sendRequest({ + return this.sendRequest({ path: this.api.getAllExperimentConditions, method: UpGradeClientEnums.REQUEST_METHOD.POST, body: requestBody, diff --git a/clientlibs/js/src/Assignment/Assignment.spec.ts b/clientlibs/js/src/Assignment/Assignment.spec.ts index 6d746356f9..3d886b33e7 100644 --- a/clientlibs/js/src/Assignment/Assignment.spec.ts +++ b/clientlibs/js/src/Assignment/Assignment.spec.ts @@ -1,6 +1,6 @@ import { EXPERIMENT_TYPE, - IExperimentAssignmentv5, + IExperimentAssignment, IPayload, MARKED_DECISION_POINT_STATUS, PAYLOAD_TYPE, @@ -8,7 +8,7 @@ import { import Assignment from './Assignment'; import { UpGradeClientInterfaces } from 'types/Interfaces'; -const defaultMockAssignment: IExperimentAssignmentv5 = { +const defaultMockAssignment: IExperimentAssignment = { site: 'site', target: 'target', assignedCondition: [ diff --git a/clientlibs/js/src/Assignment/Assignment.ts b/clientlibs/js/src/Assignment/Assignment.ts index 688965bf95..18ce9b9eee 100644 --- a/clientlibs/js/src/Assignment/Assignment.ts +++ b/clientlibs/js/src/Assignment/Assignment.ts @@ -1,5 +1,5 @@ import { - IExperimentAssignmentv5, + IExperimentAssignment, PAYLOAD_TYPE, EXPERIMENT_TYPE, IPayload, @@ -18,7 +18,7 @@ export default class Assignment { private _assignedFactor: Record; constructor( - { site, target, assignedCondition, assignedFactor, experimentType }: IExperimentAssignmentv5, + { site, target, assignedCondition, assignedFactor, experimentType }: IExperimentAssignment, private apiService: ApiService ) { this._site = site; diff --git a/clientlibs/js/src/DataService/DataService.spec.ts b/clientlibs/js/src/DataService/DataService.spec.ts index 4c4a8f9fb2..e2b64cbedb 100644 --- a/clientlibs/js/src/DataService/DataService.spec.ts +++ b/clientlibs/js/src/DataService/DataService.spec.ts @@ -1,4 +1,4 @@ -import { EXPERIMENT_TYPE, IExperimentAssignmentv5, IFeatureFlag, PAYLOAD_TYPE } from 'upgrade_types'; +import { EXPERIMENT_TYPE, IExperimentAssignment, IFeatureFlag, PAYLOAD_TYPE } from 'upgrade_types'; import { DataService } from './DataService'; describe('DataService', () => { @@ -120,7 +120,7 @@ describe('DataService', () => { describe('#rotateAssignmentList', () => { it('should return the rotated assignment list', () => { - const assignmentList: IExperimentAssignmentv5 = { + const assignmentList: IExperimentAssignment = { site: 'site', target: 'target', assignedCondition: [ @@ -148,7 +148,7 @@ describe('DataService', () => { experimentType: EXPERIMENT_TYPE.FACTORIAL, }; - const expectedRotatedAssignmentList: IExperimentAssignmentv5 = { + const expectedRotatedAssignmentList: IExperimentAssignment = { site: 'site', target: 'target', assignedCondition: [ @@ -182,7 +182,7 @@ describe('DataService', () => { }); it('should return the rotated assignment list with no assigned factors', () => { - const assignmentList: IExperimentAssignmentv5 = { + const assignmentList: IExperimentAssignment = { site: 'site', target: 'target', assignedCondition: [ @@ -203,7 +203,7 @@ describe('DataService', () => { experimentType: EXPERIMENT_TYPE.SIMPLE, }; - const expectedRotatedAssignmentList: IExperimentAssignmentv5 = { + const expectedRotatedAssignmentList: IExperimentAssignment = { site: 'site', target: 'target', assignedCondition: [ @@ -230,7 +230,7 @@ describe('DataService', () => { }); it('should return the rotated assignment list with no assigned conditions', () => { - const assignmentList: IExperimentAssignmentv5 = { + const assignmentList: IExperimentAssignment = { site: 'site', target: 'target', assignedCondition: [], @@ -238,7 +238,7 @@ describe('DataService', () => { experimentType: EXPERIMENT_TYPE.SIMPLE, }; - const expectedRotatedAssignmentList: IExperimentAssignmentv5 = { + const expectedRotatedAssignmentList: IExperimentAssignment = { site: 'site', target: 'target', assignedCondition: [], @@ -254,7 +254,7 @@ describe('DataService', () => { describe('#findExperimentAssignmentBySiteAndTarget', () => { it('should return the experiment assignment', () => { - const experimentAssignmentData: IExperimentAssignmentv5[] = [ + const experimentAssignmentData: IExperimentAssignment[] = [ { site: 'site', target: 'target', @@ -282,7 +282,7 @@ describe('DataService', () => { }); it('should normalize undefined target to empty string and return empty assignment', () => { - const experimentAssignmentData: IExperimentAssignmentv5[] = [ + const experimentAssignmentData: IExperimentAssignment[] = [ { site: 'site', target: 'target', @@ -298,7 +298,7 @@ describe('DataService', () => { experimentType: EXPERIMENT_TYPE.SIMPLE, }, ]; - const emptyAssignment: IExperimentAssignmentv5 = { + const emptyAssignment: IExperimentAssignment = { site: 'site', target: '', assignedCondition: [ @@ -315,7 +315,7 @@ describe('DataService', () => { }); it('should return undefined if no site + target match is found', () => { - const experimentAssignmentData: IExperimentAssignmentv5[] = [ + const experimentAssignmentData: IExperimentAssignment[] = [ { site: 'site', target: 'target', @@ -335,7 +335,7 @@ describe('DataService', () => { experimentType: EXPERIMENT_TYPE.FACTORIAL, }, ]; - const emptyAssignment: IExperimentAssignmentv5 = { + const emptyAssignment: IExperimentAssignment = { site: 'site', target: 'target1', assignedCondition: [ diff --git a/clientlibs/js/src/DataService/DataService.ts b/clientlibs/js/src/DataService/DataService.ts index 14a4537a85..dae7cac9e3 100644 --- a/clientlibs/js/src/DataService/DataService.ts +++ b/clientlibs/js/src/DataService/DataService.ts @@ -1,5 +1,5 @@ import { UpGradeClientInterfaces } from '../types'; -import { IExperimentAssignmentv5 } from 'upgrade_types'; +import { IExperimentAssignment } from 'upgrade_types'; /** * Synchronous data store @@ -7,7 +7,7 @@ import { IExperimentAssignmentv5 } from 'upgrade_types'; export class DataService { private group: UpGradeClientInterfaces.IExperimentUserGroup = null; private workingGroup: UpGradeClientInterfaces.IExperimentUserWorkingGroup = null; - private experimentAssignmentData: IExperimentAssignmentv5[] = null; + private experimentAssignmentData: IExperimentAssignment[] = null; private featureFlags: string[] = null; getGroup(): UpGradeClientInterfaces.IExperimentUserGroup { @@ -26,11 +26,11 @@ export class DataService { this.workingGroup = workingGroup; } - getExperimentAssignmentData(): IExperimentAssignmentv5[] { + getExperimentAssignmentData(): IExperimentAssignment[] { return this.experimentAssignmentData; } - setExperimentAssignmentData(experimentAssignmentData: IExperimentAssignmentv5[]) { + setExperimentAssignmentData(experimentAssignmentData: IExperimentAssignment[]) { this.experimentAssignmentData = experimentAssignmentData; } @@ -42,7 +42,7 @@ export class DataService { this.featureFlags = featureFlags; } - public rotateAssignmentList(assignment: IExperimentAssignmentv5) { + public rotateAssignmentList(assignment: IExperimentAssignment) { if (assignment.assignedCondition.length > 1) { assignment.assignedCondition.push(assignment.assignedCondition.shift()); if (assignment.assignedFactor) { @@ -52,13 +52,13 @@ export class DataService { return assignment; } - public findExperimentAssignmentBySiteAndTarget(site: string, target?: string): IExperimentAssignmentv5 { + public findExperimentAssignmentBySiteAndTarget(site: string, target?: string): IExperimentAssignment { const normalizedTarget = target ?? ''; const assignment = this.experimentAssignmentData.find( (assignment) => assignment.site === site && assignment.target === normalizedTarget ); - const emptyAssignment: IExperimentAssignmentv5 = { + const emptyAssignment: IExperimentAssignment = { site: site, target: normalizedTarget, assignedCondition: [ diff --git a/clientlibs/js/src/UpGradeClient/UpgradeClient.ts b/clientlibs/js/src/UpGradeClient/UpgradeClient.ts index 976984edc2..72e734b423 100644 --- a/clientlibs/js/src/UpGradeClient/UpgradeClient.ts +++ b/clientlibs/js/src/UpGradeClient/UpgradeClient.ts @@ -2,7 +2,7 @@ import { UpGradeClientInterfaces } from '../types'; import { ILogInput, CaliperEnvelope, - IExperimentAssignmentv5, + IExperimentAssignment, MARKED_DECISION_POINT_STATUS, IUserAliases, BinaryRewardAllowedValue, @@ -306,11 +306,11 @@ export default class UpgradeClient { * const userId = "User1" * const context = "mathia" * - * const getAllResponse: IExperimentAssignmentv5[] = await upgradeClient.getAllExperimentConditions(); + * const getAllResponse: IExperimentAssignment[] = await upgradeClient.getAllExperimentConditions(); * ``` */ - async getAllExperimentConditions(options = { ignoreCache: false }): Promise { - let response: IExperimentAssignmentv5[] = options.ignoreCache + async getAllExperimentConditions(options = { ignoreCache: false }): Promise { + let response: IExperimentAssignment[] = options.ignoreCache ? null : await this.dataService.getExperimentAssignmentData(); if (response == null) { diff --git a/clientlibs/js/yarn.lock b/clientlibs/js/yarn.lock index ccb315dda2..2288d3fdc5 100644 --- a/clientlibs/js/yarn.lock +++ b/clientlibs/js/yarn.lock @@ -1538,15 +1538,15 @@ follow-redirects@^1.16.0: integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== form-data@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053" - integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== + version "4.0.6" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.6.tgz#28e864e1b786dbebb68db1f452f9635278665827" + integrity sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" es-set-tostringtag "^2.1.0" - hasown "^2.0.2" - mime-types "^2.1.12" + hasown "^2.0.4" + mime-types "^2.1.35" fs.realpath@^1.0.0: version "1.0.0" @@ -1670,6 +1670,13 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" +hasown@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003" + integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== + dependencies: + function-bind "^1.1.2" + html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" @@ -2334,7 +2341,7 @@ mime-db@^1.54.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== -mime-types@^2.1.12: +mime-types@^2.1.35: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== diff --git a/packages/backend/src/api/Algorithms.ts b/packages/backend/src/api/Algorithms.ts index f84aa1b2aa..e11189d0f9 100644 --- a/packages/backend/src/api/Algorithms.ts +++ b/packages/backend/src/api/Algorithms.ts @@ -1,7 +1,7 @@ import seedrandom from 'seedrandom'; import { ConditionPayloadDTO } from './DTO/ConditionPayloadDTO'; import { Experiment } from './models/Experiment'; -import { CONDITION_ORDER, EXPERIMENT_TYPE, IExperimentAssignmentv5, IPayload } from 'upgrade_types'; +import { CONDITION_ORDER, EXPERIMENT_TYPE, IExperimentAssignment, IPayload } from 'upgrade_types'; import { FactorDTO } from './DTO/FactorDTO'; import { ExperimentCondition } from './models/ExperimentCondition'; import { DecisionPoint } from './models/DecisionPoint'; @@ -13,7 +13,7 @@ export function withInSubjectType( factors: FactorDTO[], userID: string, repeatedEnrollmentLength: number -): IExperimentAssignmentv5 { +): IExperimentAssignment { let assignedData = convertToAssignedCondition(experiment, conditionPayloads, decisionPoint, factors); // passing assigned conditions data converted into queue based on selected algorithm @@ -42,11 +42,11 @@ export function withInSubjectType( export function randomCondition( experiment, - assignedData: IExperimentAssignmentv5, + assignedData: IExperimentAssignment, userID: string, repeatedEnrollmentLength: number -): IExperimentAssignmentv5 { - const randomConditionArray: IExperimentAssignmentv5['assignedCondition'] = []; +): IExperimentAssignment { + const randomConditionArray: IExperimentAssignment['assignedCondition'] = []; const assignedFactorsArray: Record[] = []; // create 100 elements array of random condition @@ -61,7 +61,7 @@ export function randomCondition( } } - const randomAssignData: IExperimentAssignmentv5 = { + const randomAssignData: IExperimentAssignment = { site: assignedData.site, target: assignedData.target, assignedCondition: randomConditionArray, @@ -75,17 +75,17 @@ export function randomCondition( export function randomRoundRobinCondition( experiment, - assignedData: IExperimentAssignmentv5, + assignedData: IExperimentAssignment, userID: string, repeatedEnrollmentLength: number -): IExperimentAssignmentv5 { - const randomRoundRobinConditionArray: IExperimentAssignmentv5['assignedCondition'] = []; +): IExperimentAssignment { + const randomRoundRobinConditionArray: IExperimentAssignment['assignedCondition'] = []; const assignedFactorsArray: Record[] = []; const totalLoopsInQueue = Math.ceil(100 / assignedData.assignedCondition.length); // create array of random ordered conditions pairs for (let i = 0; i < totalLoopsInQueue; i++) { - const tempConditionArray: IExperimentAssignmentv5['assignedCondition'] = [...assignedData.assignedCondition]; + const tempConditionArray: IExperimentAssignment['assignedCondition'] = [...assignedData.assignedCondition]; const tempFactorArray: Record[] = experiment.type === EXPERIMENT_TYPE.FACTORIAL ? [...assignedData.assignedFactor] : []; @@ -104,7 +104,7 @@ export function randomRoundRobinCondition( } } - const randomRoundRobinAssignData: IExperimentAssignmentv5 = { + const randomRoundRobinAssignData: IExperimentAssignment = { site: assignedData.site, target: assignedData.target, assignedCondition: randomRoundRobinConditionArray, @@ -117,9 +117,9 @@ export function randomRoundRobinCondition( } export function rotateElements( - assignedData: IExperimentAssignmentv5, + assignedData: IExperimentAssignment, repeatedEnrollmentLength: number -): IExperimentAssignmentv5 { +): IExperimentAssignment { if (repeatedEnrollmentLength > 0 && assignedData.assignedCondition.length >= 2) { const totalloopIteration = repeatedEnrollmentLength % assignedData.assignedCondition.length; @@ -141,8 +141,8 @@ function convertToAssignedCondition( conditionPayloads: ConditionPayloadDTO[], decisionPoint: DecisionPoint, factors: FactorDTO[] -): IExperimentAssignmentv5 { - const assignedConditionArray: IExperimentAssignmentv5['assignedCondition'] = []; +): IExperimentAssignment { + const assignedConditionArray: IExperimentAssignment['assignedCondition'] = []; const assignedFactorsArray: Record[] = []; experiment.conditions.forEach((condition) => { diff --git a/packages/backend/src/api/controllers/BatchAssignController.ts b/packages/backend/src/api/controllers/BatchAssignController.ts index efe20cc1d1..bd57275533 100644 --- a/packages/backend/src/api/controllers/BatchAssignController.ts +++ b/packages/backend/src/api/controllers/BatchAssignController.ts @@ -3,7 +3,7 @@ import { AppRequest } from '../../types'; import { BatchAssignValidator } from './validators/BatchAssignValidator'; import { ExperimentAssignmentService } from '../services/ExperimentAssignmentService'; import { ExperimentUserService } from '../services/ExperimentUserService'; -import { IExperimentAssignmentv5 } from 'upgrade_types'; +import { IExperimentAssignment } from 'upgrade_types'; @Authorized() @JsonController('/batch-assign') @@ -113,7 +113,7 @@ export class BatchAssignController { public async getBatchAssignments( @Body({ validate: true }) requestBody: BatchAssignValidator, @Req() request: AppRequest - ): Promise> { + ): Promise> { request.logger.info({ message: 'Request received for batch assignments' }); const { context, site, target, userIds } = requestBody; request.logger.info({ diff --git a/packages/backend/src/api/controllers/ExperimentClientController.v5.ts b/packages/backend/src/api/controllers/ExperimentClientController.v5.ts deleted file mode 100644 index c3f4fc34e3..0000000000 --- a/packages/backend/src/api/controllers/ExperimentClientController.v5.ts +++ /dev/null @@ -1,899 +0,0 @@ -import { - JsonController, - Post, - Body, - UseBefore, - Req, - InternalServerError, - Delete, - Patch, - Authorized, -} from 'routing-controllers'; -import { ExperimentService } from '../services/ExperimentService'; -import { ExperimentAssignmentService } from '../services/ExperimentAssignmentService'; -import { ExperimentAssignmentValidator } from './validators/ExperimentAssignmentValidator'; -import { ExperimentUser } from '../models/ExperimentUser'; -import { ExperimentUserService } from '../services/ExperimentUserService'; -import { UpdateWorkingGroupValidator } from './validators/UpdateWorkingGroupValidator'; -import { IExperimentAssignmentv5, SERVER_ERROR, IGroupMembership, IUserAliases, IWorkingGroup } from 'upgrade_types'; -import { FeatureFlagService } from '../services/FeatureFlagService'; -import { ClientLibMiddleware } from '../middlewares/ClientLibMiddleware'; -import { LogValidator } from './validators/LogValidator'; -import { MetricService } from '../services/MetricService'; -import { ExperimentUserAliasesValidator } from './validators/ExperimentUserAliasesValidator'; -import * as express from 'express'; -import { AppRequest } from '../../types'; -import { MonitoredDecisionPointLog } from '../models/MonitoredDecisionPointLog'; -import { MarkExperimentValidatorv5 } from './validators/MarkExperimentValidator.v5'; -import { Log } from '../models/Log'; -import { ExperimentUserValidator, RequestedExperimentUser } from './validators/ExperimentUserValidator'; -import { HttpError } from '../errors'; -import { UpgradeLogger } from 'src/lib/logger/UpgradeLogger'; - -interface IMonitoredDecisionPoint { - id: string; - user: ExperimentUser; - site: string; - target: string; - experimentId: string; - condition: string; - monitoredPointLogs: MonitoredDecisionPointLog[]; -} - -/** - * @swagger - * definitions: - * v5initResponse: - * type: object - * properties: - * id: - * type: string - * minLength: 1 - * group: - * type: object - * properties: - * class: - * type: array - * items: - * type: string - * required: - * - class - * workingGroup: - * type: object - * properties: - * school: - * type: string - * minLength: 1 - * class: - * type: string - * minLength: 1 - * instructor: - * type: string - * minLength: 1 - * required: - * - school - * - class - * - instructor - * required: - * - id - * - group - * - workingGroup - */ - -/** - * @swagger - * tags: - * - name: Client Side SDK - * description: CRUD operations related to experiments points - */ - -@JsonController('/v5/') -@UseBefore(ClientLibMiddleware) -export class ExperimentClientController { - constructor( - public experimentService: ExperimentService, - public experimentAssignmentService: ExperimentAssignmentService, - public experimentUserService: ExperimentUserService, - public featureFlagService: FeatureFlagService, - public metricService: MetricService - ) {} - - public async checkIfUserExist( - userId: string, - logger: UpgradeLogger, - api?: string - ): Promise { - const experimentUserDoc = await this.experimentUserService.getUserDoc(userId, logger); - if (experimentUserDoc === null) { - if (api === 'init') { - return null; - } else { - throw new HttpError(404, `Experiment User not found: ${userId}`); - } - } else { - return experimentUserDoc; - } - } - /** - * @swagger - * /v5/init: - * post: - * description: Create/Update Experiment User - * consumes: - * - application/json - * parameters: - * - in: body - * name: experimentUser - * required: true - * schema: - * type: object - * properties: - * id: - * type: string - * example: user1 - * group: - * type: object - * properties: - * schoolId: - * type: array - * items: - * type: string - * example: school1 - * classId: - * type: array - * items: - * type: string - * example: class1 - * instructorId: - * type: array - * items: - * type: string - * example: instructor1 - * workingGroup: - * type: object - * properties: - * schoolId: - * type: string - * example: school1 - * classId: - * type: string - * example: class1 - * instructorId: - * type: string - * example: instructor1 - * description: ExperimentUser - * tags: - * - Client Side SDK - * produces: - * - application/json - * responses: - * '200': - * description: Set Group Membership - * schema: - * $ref: '#/definitions/v5initResponse' - * '400': - * description: BadRequestError - InvalidParameterValue - * '401': - * description: AuthorizationRequiredError - * '404': - * description: Experiment User not defined - * '500': - * description: Internal Server Error - */ - @Post('init') - public async init( - @Req() - request: AppRequest, - @Body({ validate: true }) - experimentUser: ExperimentUserValidator - ): Promise> { - request.logger.info({ message: 'Starting the init call for user' }); - // getOriginalUserDoc call for alias - const experimentUserDoc = await this.checkIfUserExist(experimentUser.id, request.logger, 'init'); - // if reinit call is made with any of the below fields not included in the call, - // then we will fetch the stored values of the field and return them in the response - // for consistent init response with 3 fields ['userId', 'group', 'workingGroup'] - const { id, group, workingGroup } = { ...experimentUserDoc, ...experimentUser }; - // append userDoc in logger - request.logger.child({ userDoc: experimentUserDoc }); - request.logger.info({ message: 'Got the original user doc' }); - - const upsertResult = await this.experimentUserService.upsertOnChange( - experimentUserDoc, - experimentUser, - request.logger - ); - - if (!upsertResult) { - request.logger.error({ - details: 'user upsert failed', - }); - throw new InternalServerError('user init failed'); - } - - return { id, group, workingGroup }; - } - - /** - * @swagger - * /v5/groupmembership: - * patch: - * description: Set group membership for a user - * consumes: - * - application/json - * parameters: - * - in: body - * name: experimentUser - * required: true - * schema: - * type: object - * properties: - * id: - * type: string - * example: user1 - * group: - * type: object - * properties: - * schoolId: - * type: array - * items: - * type: string - * example: school1 - * classId: - * type: array - * items: - * type: string - * example: class1 - * instructorId: - * type: array - * items: - * type: string - * example: instructor1 - * description: ExperimentUser - * tags: - * - Client Side SDK - * produces: - * - application/json - * responses: - * '200': - * description: Set Group Membership - * schema: - * $ref: '#/definitions/v5initResponse' - * '400': - * description: BadRequestError - InvalidParameterValue - * '401': - * description: AuthorizationRequiredError - * '404': - * description: Experiment User not defined - * '500': - * description: Internal Server Error - */ - @Patch('groupmembership') - public async setGroupMemberShip( - @Req() - request: AppRequest, - @Body({ validate: true }) - experimentUser: ExperimentUserValidator - ): Promise { - request.logger.info({ message: 'Starting the groupmembership call for user' }); - // getOriginalUserDoc call for alias - const experimentUserDoc = await this.checkIfUserExist(experimentUser.id, request.logger); - // append userDoc in logger - request.logger.child({ userDoc: experimentUserDoc }); - request.logger.info({ message: 'Got the original user doc' }); - const updateResult = await this.experimentUserService.updateGroupMembership( - experimentUserDoc.requestedUserId, - experimentUser.group, - { - logger: request.logger, - userDoc: experimentUserDoc, - } - ); - if (!updateResult) { - request.logger.error({ - details: 'update unexpectedly returned empty object', - }); - throw new InternalServerError('set group membership failed'); - } - - return { id: experimentUserDoc.id, group: experimentUser.group }; - } - - /** - * @swagger - * /v5/workinggroup: - * patch: - * description: Set working group for a user - * consumes: - * - application/json - * parameters: - * - in: body - * name: params - * required: true - * schema: - * type: object - * properties: - * id: - * type: string - * example: user1 - * workingGroup: - * type: object - * properties: - * schoolId: - * type: string - * example: school1 - * classId: - * type: string - * example: class1 - * instructorId: - * type: string - * example: instructor1 - * description: ExperimentUser - * tags: - * - Client Side SDK - * produces: - * - application/json - * responses: - * '200': - * description: Set Group Membership - * schema: - * $ref: '#/definitions/v5initResponse' - * '400': - * description: BadRequestError - InvalidParameterValue - * '401': - * description: AuthorizationRequiredError - * '404': - * description: Experiment User not defined - * '500': - * description: Internal Server Error - */ - @Patch('workinggroup') - public async setWorkingGroup( - @Req() - request: AppRequest, - @Body({ validate: true }) - workingGroupParams: UpdateWorkingGroupValidator - ): Promise { - request.logger.info({ message: 'Starting the workinggroup call for user' }); - // getOriginalUserDoc call for alias - const experimentUserDoc = await this.checkIfUserExist(workingGroupParams.id, request.logger); - // append userDoc in logger - request.logger.child({ userDoc: experimentUserDoc }); - request.logger.info({ message: 'Got the original user doc' }); - const updateResult = await this.experimentUserService.updateWorkingGroup( - experimentUserDoc.requestedUserId, - workingGroupParams.workingGroup, - { - logger: request.logger, - userDoc: experimentUserDoc, - } - ); - if (!updateResult) { - request.logger.error({ - details: 'update unexpectedly returned empty object', - }); - throw new InternalServerError('set working group failed'); - } - - return { id: experimentUserDoc.id, workingGroup: workingGroupParams.workingGroup }; - } - - /** - * @swagger - * /v5/mark: - * post: - * description: Mark a Experiment Point - * consumes: - * - application/json - * parameters: - * - in: body - * name: experimentUser - * required: true - * schema: - * type: object - * required: - * - userId - * - data - * properties: - * userId: - * type: string - * data: - * type: object - * properties: - * site: - * type: string - * target: - * type: string - * nullable: true - * example: partition1 - * assignedCondition: - * type: object - * properties: - * conditionCode: - * type: string - * example: control - * experimentId: - * type: string - * example: exp1 - * status: - * type: string - * example: condition applied - * description: ExperimentUser - * tags: - * - Client Side SDK - * produces: - * - application/json - * responses: - * '200': - * description: Experiment Point is Marked - * schema: - * type: object - * properties: - * id: - * type: string - * minLength: 1 - * userId: - * type: string - * minLength: 1 - * experimentId: - * type: string - * minLength: 1 - * site: - * type: string - * minLength: 1 - * target: - * type: string - * minLength: 1 - * condition: - * type: string - * minLength: 1 - * required: - * - id - * - experimentId - * - enrollmentCode - * - userId - * - condition - * '400': - * description: BadRequestError - InvalidParameterValue - * '401': - * description: AuthorizationRequiredError - * '404': - * description: Experiment User not defined - * '500': - * description: Internal Server Error - */ - @Post('mark') - public async markExperimentPoint( - @Req() - request: AppRequest, - @Body({ validate: true }) - experiment: MarkExperimentValidatorv5 - ): Promise { - request.logger.info({ message: 'Starting the markExperimentPoint call for user' }); - // getOriginalUserDoc call for alias - const experimentUserDoc = await this.checkIfUserExist(experiment.userId, request.logger); - // append userDoc in logger - request.logger.child({ userDoc: experimentUserDoc }); - request.logger.info({ message: 'Got the original user doc' }); - const { createdAt, updatedAt, versionNumber, ...rest } = await this.experimentAssignmentService.markExperimentPoint( - experimentUserDoc, - experiment.data.site, - experiment.status, - experiment.data.assignedCondition?.conditionCode ?? null, - request.logger, - experiment.data.assignedCondition?.experimentId ?? null, - experiment.data.target, - experiment.uniquifier ? experiment.uniquifier : null, - experiment.clientError ? experiment.clientError : null - ); - return rest; - } - - /** - * @swagger - * /v5/assign: - * post: - * description: Assign a Experiment Point - * consumes: - * - application/json - * parameters: - * - in: body - * name: user - * required: true - * schema: - * type: object - * properties: - * userId: - * type: string - * example: user1 - * context: - * type: string - * example: add - * description: User Document - * tags: - * - Client Side SDK - * produces: - * - application/json - * responses: - * '200': - * description: Experiment Point is Assigned - * schema: - * type: array - * description: '' - * minItems: 1 - * uniqueItems: true - * items: - * type: object - * required: - * - site - * - target - * - condition - * properties: - * site: - * type: string - * minLength: 1 - * target: - * type: string - * minLength: 1 - * experimentType: - * type: string - * enum: [Simple, Factorial] - * assignedCondition: - * type: array - * items: - * type: object - * properties: - * conditionCode: - * type: string - * minLength: 1 - * payload: - * type: object - * properties: - * type: - * type: string - * value: - * type: string - * id: - * type: string - * experimentId: - * type: string - * '400': - * description: BadRequestError - InvalidParameterValue - * '401': - * description: AuthorizationRequiredError - * '404': - * description: Experiment User not defined - * '500': - * description: Internal Server Error - */ - @Post('assign') - public async getAllExperimentConditions( - @Req() - request: AppRequest, - @Body({ validate: true }) - experiment: ExperimentAssignmentValidator - ): Promise { - request.logger.info({ message: 'Starting the getAllExperimentConditions call for user' }); - const experimentUserDoc = await this.checkIfUserExist(experiment.userId, request.logger); - const assignedData = await this.experimentAssignmentService.getAllExperimentConditions( - experimentUserDoc, - experiment.context, - request.logger - ); - - return this.experimentAssignmentService.formatAssignments(assignedData); - } - - /** - * @swagger - * /v5/log: - * post: - * description: Post log data - * consumes: - * - application/json - * parameters: - * - in: body - * name: data - * required: true - * schema: - * type: object - * properties: - * userId: - * type: string - * required: true - * value: - * type: array - * required: true - * items: - * type: object - * properties: - * timestamp: - * type: string - * metrics: - * type: object - * properties: - * attributes: - * type: object - * properties: - * continuousMetricName: - * type: integer - * example: 100 - * categoricalMetricName: - * type: string - * example: CATEGORY - * groupedMetrics: - * type: array - * items: - * type: object - * properties: - * groupClass: - * type: string - * required: true - * example: workspaceType - * groupKey: - * type: string - * required: true - * example: workspaceName - * groupUniquifier: - * type: string - * required: true - * example: workspaceUniquifier - * attributes: - * type: object - * properties: - * continuousMetricName: - * type: integer - * example: 100 - * categoricalMetricName: - * type: string - * example: CATEGORY - * description: User Document - * tags: - * - Client Side SDK - * produces: - * - application/json - * responses: - * '200': - * description: Log data - * '400': - * description: BadRequestError - InvalidParameterValue - * '401': - * description: AuthorizationRequiredError - * '404': - * description: Experiment User not defined - * '500': - * description: Internal Server Error - */ - @Post('log') - public async log( - @Req() - request: AppRequest, - @Body({ validate: true }) - logData: LogValidator - ): Promise[]> { - request.logger.info({ message: 'Starting the log call for user' }); - // getOriginalUserDoc call for alias - const experimentUserDoc = await this.checkIfUserExist(logData.userId, request.logger); - // append userDoc in logger - request.logger.child({ userDoc: experimentUserDoc }); - request.logger.info({ message: 'Got the original user doc' }); - const logs = await this.experimentAssignmentService.dataLog(experimentUserDoc, logData.value, request.logger); - return logs.map(({ createdAt, updatedAt, versionNumber, ...rest }) => { - return rest; - }); - } - - /** - * @swagger - * /v5/bloblog: - * post: - * description: Post blob log data - * consumes: - * - application/json - * parameters: - * - in: body - * name: data - * required: true - * schema: - * type: object - * properties: - * userId: - * type: string - * value: - * type: array - * items: - * type: object - * description: User Document - * tags: - * - Client Side SDK - * produces: - * - application/json - * responses: - * '200': - * description: Log blob data - * '400': - * description: BadRequestError - InvalidParameterValue - * '401': - * description: AuthorizationRequiredError - * '404': - * description: Experiment User not defined - * '500': - * description: Internal Server Error - */ - @Post('bloblog') - public async blobLog(@Req() request: express.Request): Promise { - return new Promise((resolve, reject) => { - request.on('readable', async () => { - const blobData = JSON.parse(request.read()); - try { - // The function will throw error if userId doesn't exist - const experimentUserDoc = await this.checkIfUserExist(blobData.userId, request.logger); - // append userDoc in logger - request.logger.child({ userDoc: experimentUserDoc }); - request.logger.info({ message: 'Got the original user doc' }); - const response = await this.experimentAssignmentService.blobDataLog( - experimentUserDoc, - blobData.value, - request.logger - ); - resolve(response); - } catch (error) { - // The error is rejected so promise can now handle this error - reject(error); - } - }); - }).catch((error) => { - request.logger.error(error); - error = new Error( - JSON.stringify({ - type: SERVER_ERROR.EXPERIMENT_USER_NOT_DEFINED, - message: error.message, - }) - ); - (error as any).type = SERVER_ERROR.EXPERIMENT_USER_NOT_DEFINED; - (error as any).httpCode = 404; - throw error; - }); - } - - /** - * @swagger - * /v5/featureflag: - * post: - * description: Get all feature flags using SDK - * consumes: - * - application/json - * parameters: - * - in: body - * name: user - * required: true - * schema: - * type: object - * properties: - * userId: - * type: string - * example: user1 - * context: - * type: string - * example: add - * description: User Document - * produces: - * - application/json - * tags: - * - Client Side SDK - * responses: - * '200': - * description: Feature flags list - * '400': - * description: BadRequestError - InvalidParameterValue - * '401': - * description: AuthorizationRequiredError - * '404': - * description: Experiment User not defined - * '500': - * description: Internal Server Error - */ - @Post('featureflag') - public async getAllFlags( - @Req() request: AppRequest, - @Body({ validate: true }) - experiment: ExperimentAssignmentValidator - ): Promise { - const experimentUserDoc = await this.checkIfUserExist(experiment.userId, request.logger); - return this.featureFlagService.getKeys(experimentUserDoc, experiment.context, request.logger); - } - - /** - * @swagger - * /v5/useraliases: - * patch: - * description: Set aliases for current user - * consumes: - * - application/json - * parameters: - * - in: body - * name: user aliases - * required: true - * schema: - * type: object - * required: - * - userId - * - aliases - * properties: - * userId: - * type: string - * example: user1 - * aliases: - * type: array - * items: - * type: string - * example: alias123 - * description: Set user aliases - * tags: - * - Client Side SDK - * produces: - * - application/json - * responses: - * '200': - * description: Experiment User aliases added - * schema: - * type: object - * properties: - * userId: - * type: string - * minLength: 1 - * aliases: - * type: array - * items: - * type: string - * required: - * - userId - * - userAliases - * '400': - * description: BadRequestError - InvalidParameterValue - * '401': - * description: AuthorizationRequiredError - * '404': - * description: Experiment User not defined - * '500': - * description: Internal Server Error - */ - @Patch('useraliases') - public async setUserAliases( - @Req() - request: AppRequest, - @Body({ validate: true }) - user: ExperimentUserAliasesValidator - ): Promise { - const experimentUserDoc = await this.checkIfUserExist(user.userId, request.logger); - // append userDoc in logger - request.logger.child({ userDoc: experimentUserDoc }); - request.logger.info({ message: 'Got the original user doc' }); - return this.experimentUserService.setAliasesForUser(experimentUserDoc, user.aliases, request.logger); - } - - /** - * @swagger - * /clearDB: - * delete: - * description: Only available in DEMO mode. Removes everything except UpGrade users, metric metadata, UpGrade settings, and migrations - * tags: - * - Client Side SDK - * produces: - * - application/json - * responses: - * '200': - * description: Database cleared - * '400': - * description: BadRequestError - InvalidParameterValue - * '401': - * description: AuthorizationRequiredError - * '500': - * description: DEMO mode is disabled - */ - @Authorized() - @Delete('clearDB') - public async clearDB(@Req() request: AppRequest): Promise { - return this.experimentUserService.clearDB(request.logger); - } -} diff --git a/packages/backend/src/api/controllers/ExperimentClientController.v6.ts b/packages/backend/src/api/controllers/ExperimentClientController.v6.ts index a7e47a989a..c76d130fac 100644 --- a/packages/backend/src/api/controllers/ExperimentClientController.v6.ts +++ b/packages/backend/src/api/controllers/ExperimentClientController.v6.ts @@ -17,7 +17,7 @@ import { FeatureFlagRequestValidator } from './validators/FeatureFlagRequestVali import { ExperimentUser } from '../models/ExperimentUser'; import { ExperimentUserService } from '../services/ExperimentUserService'; import { UpdateWorkingGroupValidatorv6 } from './validators/UpdateWorkingGroupValidator'; -import { IExperimentAssignmentv5, IGroupMembership, IUserAliases, IWorkingGroup } from 'upgrade_types'; +import { IExperimentAssignment, IGroupMembership, IUserAliases, IWorkingGroup } from 'upgrade_types'; import { FeatureFlagService } from '../services/FeatureFlagService'; import { ClientLibMiddleware } from '../middlewares/ClientLibMiddleware'; import { LogValidatorv6 } from './validators/LogValidator'; @@ -539,7 +539,7 @@ export class ExperimentClientController { request: AppRequest, @Body({ validate: true }) experiment: ExperimentAssignmentValidatorv6 - ): Promise { + ): Promise { request.logger.info({ message: 'Starting the getAllExperimentConditions call for user' }); const experimentUserDoc = request.userDoc; const assignedData = await this.experimentAssignmentService.getAllExperimentConditions( diff --git a/packages/backend/src/api/controllers/validators/ExperimentAssignmentValidator.ts b/packages/backend/src/api/controllers/validators/ExperimentAssignmentValidator.ts index 4362263355..9abd7fa624 100644 --- a/packages/backend/src/api/controllers/validators/ExperimentAssignmentValidator.ts +++ b/packages/backend/src/api/controllers/validators/ExperimentAssignmentValidator.ts @@ -5,9 +5,3 @@ export class ExperimentAssignmentValidatorv6 { @IsString() public context: string; } - -export class ExperimentAssignmentValidator extends ExperimentAssignmentValidatorv6 { - @IsNotEmpty() - @IsString() - public userId: string; -} diff --git a/packages/backend/src/api/controllers/validators/ExperimentUserAliasesValidator.ts b/packages/backend/src/api/controllers/validators/ExperimentUserAliasesValidator.ts index 243bf75465..392689280e 100644 --- a/packages/backend/src/api/controllers/validators/ExperimentUserAliasesValidator.ts +++ b/packages/backend/src/api/controllers/validators/ExperimentUserAliasesValidator.ts @@ -1,12 +1,7 @@ -import { IsNotEmpty, IsArray, IsString } from 'class-validator'; +import { IsArray, IsString } from 'class-validator'; export class ExperimentUserAliasesValidatorv6 { @IsArray() @IsString({ each: true }) public aliases: string[]; } - -export class ExperimentUserAliasesValidator extends ExperimentUserAliasesValidatorv6 { - @IsNotEmpty() - public userId: string; -} diff --git a/packages/backend/src/api/controllers/validators/LogValidator.ts b/packages/backend/src/api/controllers/validators/LogValidator.ts index 67e107b03e..182b39bf48 100644 --- a/packages/backend/src/api/controllers/validators/LogValidator.ts +++ b/packages/backend/src/api/controllers/validators/LogValidator.ts @@ -92,9 +92,3 @@ export class LogValidatorv6 implements ILogRequestBody { @Type(() => ILogInputValidator) public value: ILogInput[]; } - -export class LogValidator extends LogValidatorv6 implements ILogRequestBody { - @IsNotEmpty() - @IsString() - public userId: string; -} diff --git a/packages/backend/src/api/controllers/validators/MarkExperimentValidator.v5.ts b/packages/backend/src/api/controllers/validators/MarkExperimentValidator.v5.ts deleted file mode 100644 index ccf36078e6..0000000000 --- a/packages/backend/src/api/controllers/validators/MarkExperimentValidator.v5.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Type, Transform } from 'class-transformer'; -import { IsNotEmpty, IsDefined, IsString, IsOptional, IsEnum, ValidateNested } from 'class-validator'; -import { MARKED_DECISION_POINT_STATUS } from 'upgrade_types'; - -class AssignedCondition { - @IsOptional() - @IsString() - id?: string; - - @IsOptional() - @IsString() - conditionCode?: string; - - @IsOptional() - @IsString() - experimentId?: string; -} - -class Data { - @IsString() - @IsNotEmpty() - site: string; - - @Transform(({ value }) => value ?? '') - @IsString() - target = ''; - - @IsOptional() - @ValidateNested() - @Type(() => AssignedCondition) - assignedCondition: AssignedCondition; -} - -export class MarkExperimentValidatorv5 { - @IsNotEmpty() - public userId: string; - - @IsDefined() - @ValidateNested() - @Type(() => Data) - public data: Data; - - @IsEnum(MARKED_DECISION_POINT_STATUS) - @IsOptional() - public status?: MARKED_DECISION_POINT_STATUS; - - @IsString() - @IsOptional() - public uniquifier?: string; - - @IsString() - @IsOptional() - public clientError?: string; -} diff --git a/packages/backend/src/api/controllers/validators/UpdateWorkingGroupValidator.ts b/packages/backend/src/api/controllers/validators/UpdateWorkingGroupValidator.ts index 8f2b38772a..6ab9f42847 100644 --- a/packages/backend/src/api/controllers/validators/UpdateWorkingGroupValidator.ts +++ b/packages/backend/src/api/controllers/validators/UpdateWorkingGroupValidator.ts @@ -1,4 +1,4 @@ -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsNotEmpty } from 'class-validator'; import { IsWorkingGroupRecord } from './ExperimentUserValidator'; export class UpdateWorkingGroupValidatorv6 { @@ -6,9 +6,3 @@ export class UpdateWorkingGroupValidatorv6 { @IsNotEmpty() public workingGroup: Record; } - -export class UpdateWorkingGroupValidator extends UpdateWorkingGroupValidatorv6 { - @IsString() - @IsNotEmpty() - public id: string; -} diff --git a/packages/backend/src/api/services/ExperimentAssignmentService.ts b/packages/backend/src/api/services/ExperimentAssignmentService.ts index b15f2a5701..d02d97b49b 100644 --- a/packages/backend/src/api/services/ExperimentAssignmentService.ts +++ b/packages/backend/src/api/services/ExperimentAssignmentService.ts @@ -21,7 +21,7 @@ import { EXPERIMENT_TYPE, SUPPORTED_CALIPER_PROFILES, SUPPORTED_CALIPER_EVENTS, - IExperimentAssignmentv5, + IExperimentAssignment, CACHE_PREFIX, ASSIGNMENT_ALGORITHM, IPayload, @@ -323,7 +323,7 @@ export class ExperimentAssignmentService { experimentUserDoc: RequestedExperimentUser, context: string, logger: UpgradeLogger - ): Promise { + ): Promise { logger.info({ message: `getAllExperimentConditions: User: ${experimentUserDoc.requestedUserId}` }); const userId = experimentUserDoc.id; const previewUser = await this.previewUserService.findOneFromCache(userId, logger); @@ -473,7 +473,7 @@ export class ExperimentAssignmentService { site: string, target: string, logger: UpgradeLogger - ): Promise> { + ): Promise> { logger.info({ message: `getAllExperimentConditions: User: ${experimentUserDocs.map((doc) => doc.id).join(', ')}`, }); @@ -537,7 +537,7 @@ export class ExperimentAssignmentService { assignmentRecords.groupExclusion, enrollmentCountPerCondition ); - let fullAssignment: IExperimentAssignmentv5[] | null = null; + let fullAssignment: IExperimentAssignment[] | null = null; if (assignedCondition) { const { conditionPayloads, type, factors } = this.experimentService.formattingPayload(experiment); fullAssignment = this.mapDecisionPoints( @@ -568,7 +568,7 @@ export class ExperimentAssignmentService { } } - public formatAssignments(assignedData: IExperimentAssignmentv5[]): IExperimentAssignmentv5[] { + public formatAssignments(assignedData: IExperimentAssignment[]): IExperimentAssignment[] { return assignedData.map(({ assignedFactor, assignedCondition, ...rest }) => { const finalFactorData = assignedFactor?.map((factor) => { const updatedAssignedFactor: Record = {}; @@ -892,7 +892,7 @@ export class ExperimentAssignmentService { factors: FactorDTO[], repeatedEnrollmentCounts: RepeatedEnrollmentDataCount[], logger: UpgradeLogger - ): IExperimentAssignmentv5[] { + ): IExperimentAssignment[] { return experiment.partitions .filter((dp) => !dp.pendingActivation || experiment.state === EXPERIMENT_STATE.PREVIEW) .map((decisionPoint) => { @@ -1084,31 +1084,6 @@ export class ExperimentAssignmentService { return [userExcluded !== undefined, groupExcluded.length > 0]; } - // When browser will be sending the blob data - public async blobDataLog( - userDoc: RequestedExperimentUser, - blobLog: ILogInput[], - logger: UpgradeLogger - ): Promise { - const userId = userDoc.id; - logger.info({ message: `Add blob data userId ${userId}`, details: blobLog }); - const keyUniqueArray = []; - - // throw error if user not defined - if (!userDoc) { - logger.error({ message: `User not found in blobDataLog, userId => ${userId}`, details: blobLog }); - throw new Error(`User not defined in blobDataLog: ${userId}`); - } - - // extract the array value - const promise = blobLog.map(async (individualMetrics) => { - return this.createLog(individualMetrics, keyUniqueArray, userDoc, logger); - }); - - const logsToReturn = await Promise.all(promise); - return flatten(logsToReturn); - } - public async caliperDataLog( log: CaliperLogData, requestContext: { logger: UpgradeLogger; userDoc: any } diff --git a/packages/backend/test/integration/UserNotDefined/index.ts b/packages/backend/test/integration/UserNotDefined/index.ts index 6eda67f2cc..8fa4f39476 100644 --- a/packages/backend/test/integration/UserNotDefined/index.ts +++ b/packages/backend/test/integration/UserNotDefined/index.ts @@ -17,14 +17,6 @@ export const UserNotDefined = async () => { ) ).toEqual(Promise.resolve({})); - await expect( - experimentAssignmentService.blobDataLog( - { ...experimentUserDoc, requestedUserId: experimentUsers[0].id }, - null, - new UpgradeLogger() - ) - ).rejects.toThrow(); - await expect( experimentAssignmentService.dataLog( { ...experimentUserDoc, requestedUserId: experimentUsers[0].id }, diff --git a/packages/backend/test/integration/utils/index.ts b/packages/backend/test/integration/utils/index.ts index ef6abe894e..72fbf3cc74 100644 --- a/packages/backend/test/integration/utils/index.ts +++ b/packages/backend/test/integration/utils/index.ts @@ -6,7 +6,7 @@ import { MonitoredDecisionPoint } from '../../../src/api/models/MonitoredDecisio import { Container } from 'typedi'; import { ExperimentAssignmentService } from '../../../src/api/services/ExperimentAssignmentService'; import { CheckService } from '../../../src/api/services/CheckService'; -import { IExperimentAssignmentv5, MARKED_DECISION_POINT_STATUS } from 'upgrade_types'; +import { IExperimentAssignment, MARKED_DECISION_POINT_STATUS } from 'upgrade_types'; import { ExperimentService } from '../../../src/api/services/ExperimentService'; import { User } from '../../../src/api/models/User'; import { UpgradeLogger } from '../../../src/lib/logger/UpgradeLogger'; @@ -37,7 +37,7 @@ export function checkExperimentAssignedIsNull(experimentConditionAssignments: an } export function checkExperimentAssignedIsNotDefault( - experimentConditionAssignments: IExperimentAssignmentv5[], + experimentConditionAssignments: IExperimentAssignment[], target: string, site: string ): void { @@ -85,7 +85,7 @@ export async function getAllExperimentCondition( userId: string, logger: UpgradeLogger, context = 'home' -): Promise { +): Promise { const experimentAssignmentService = Container.get(ExperimentAssignmentService); const experimentUserService = Container.get(ExperimentUserService); // getOriginalUserDoc diff --git a/packages/backend/test/unit/controllers/ExperimentClientController.test.ts b/packages/backend/test/unit/controllers/ExperimentClientController.test.ts deleted file mode 100644 index e5838175ea..0000000000 --- a/packages/backend/test/unit/controllers/ExperimentClientController.test.ts +++ /dev/null @@ -1,190 +0,0 @@ -import app from '../../utils/expressApp'; -import request from 'supertest'; -import { useContainer as routingUseContainer } from 'routing-controllers'; -import { Container } from 'typedi'; -import { ExperimentService } from '../../../src/api/services/ExperimentService'; -import { ExperimentAssignmentService } from '../../../src/api/services/ExperimentAssignmentService'; -import { ExperimentUserService } from '../../../src/api/services/ExperimentUserService'; -import { FeatureFlagService } from '../../../src/api/services/FeatureFlagService'; -import { MetricService } from '../../../src/api/services/MetricService'; -import { ClientLibMiddleware } from '../../../src/api/middlewares/ClientLibMiddleware'; -import { UserCheckMiddleware } from '../../../src/api/middlewares/UserCheckMiddleware'; -import ExperimentServiceMock from './mocks/ExperimentServiceMock'; -import ExperimentAssignmentServiceMock from './mocks/ExperimentAssignmentServiceMock'; -import ExperimentUserServiceMock from './mocks/ExperimentUserServiceMock'; -import FeatureFlagServiceMock from './mocks/FeatureFlagServiceMock'; -import MetricServiceMock from './mocks/MetricServiceMock'; -import ClientLibMiddlewareMock from './mocks/ClientLibMiddlewareMock'; -import MockuserCheckMiddleware from './mocks/UserCheckMiddlewareMock'; - -import { useContainer as classValidatorUseContainer } from 'class-validator'; - -describe('Experiment Client Controller Testing', () => { - beforeAll(() => { - routingUseContainer(Container); - classValidatorUseContainer(Container); - - Container.set(ExperimentService, new ExperimentServiceMock()); - Container.set(ExperimentAssignmentService, new ExperimentAssignmentServiceMock()); - Container.set(ExperimentUserService, new ExperimentUserServiceMock()); - Container.set(FeatureFlagService, new FeatureFlagServiceMock()); - Container.set(MetricService, new MetricServiceMock()); - Container.set(ClientLibMiddleware, new ClientLibMiddlewareMock()); - Container.set(UserCheckMiddleware, new MockuserCheckMiddleware()); - }); - - afterAll(() => { - Container.reset(); - }); - - const logData = { - userId: 'u22', - value: [ - { - timestamp: '1970-01-01T00:00:00Z', - metrics: { - groupedMetrics: [ - { - groupClass: 'masteryWorkspace', - groupKey: 'calculating_area_of_square', - groupUniquifier: '1990-10-10T00:00:00Z', - attributes: { - hintCount: 31, - }, - }, - ], - }, - }, - ], - }; - - test('Post request for /api/v5/init', () => { - return request(app) - .post('/api/v5/init') - .send({ - id: '123', - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200); - }); - - test('Post request for /api/v5/groupmembership', () => { - return request(app) - .patch('/api/v5/groupmembership') - .send({ - id: 'u21', - group: { - class: ['C1', 'C2'], - }, - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200); - }); - - test('Post request for /api/v5/workinggroup', () => { - return request(app) - .patch('/api/v5/workinggroup') - .send({ - id: 'u21', - workingGroup: { - school: 'testschool', - class: 'testclass', - instructor: 'testteacher', - }, - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200); - }); - - test('Post request for /api/v5/mark', () => { - return request(app) - .post('/api/v5/mark') - .send({ - userId: 'u21', - status: 'condition applied', - data: { - target: 'p', - site: 'q', - assignedCondition: { - conditionCode: 'condition', - }, - }, - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200); - }); - - test('Post request for /api/v5/mark with null target', () => { - return request(app) - .post('/api/v5/mark') - .send({ - userId: 'u21', - status: 'condition applied', - data: { - target: null, - site: 'q', - assignedCondition: { - conditionCode: 'condition', - }, - }, - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200); - }); - - test('Post request for /api/v5/mark with missing target', () => { - return request(app) - .post('/api/v5/mark') - .send({ - userId: 'u21', - status: 'condition applied', - data: { - site: 'q', - assignedCondition: { - conditionCode: 'condition', - }, - }, - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200); - }); - - test('Post request for /api/v5/assign', () => { - return request(app) - .post('/api/v5/assign') - .send({ - userId: 'u21', - context: 'abc', - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200); - }); - - test('Post request for /api/v5/log', () => { - return request(app) - .post('/api/v5/log') - .send(logData) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200); - }); - - test('Post request for /api/v5/useraliases', () => { - return request(app) - .patch('/api/v5/useraliases') - .send({ - userId: 'u21', - aliases: ['abc'], - }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200); - }); -}); diff --git a/packages/backend/test/unit/controllers/mocks/ExperimentAssignmentServiceMock.ts b/packages/backend/test/unit/controllers/mocks/ExperimentAssignmentServiceMock.ts index 9977fa6a38..749b4e073b 100644 --- a/packages/backend/test/unit/controllers/mocks/ExperimentAssignmentServiceMock.ts +++ b/packages/backend/test/unit/controllers/mocks/ExperimentAssignmentServiceMock.ts @@ -1,5 +1,5 @@ import { Service } from 'typedi'; -import { IExperimentAssignmentv5 } from 'upgrade_types'; +import { IExperimentAssignment } from 'upgrade_types'; import { ILogInput } from 'upgrade_types'; @Service() @@ -12,7 +12,7 @@ export default class ExperimentAssignmentServiceMock { return Promise.resolve([]); } - public formatAssignments(assignments: IExperimentAssignmentv5[]): IExperimentAssignmentv5[] { + public formatAssignments(assignments: IExperimentAssignment[]): IExperimentAssignment[] { return []; } @@ -51,10 +51,6 @@ export default class ExperimentAssignmentServiceMock { return Promise.resolve([]); } - public blobDataLog(userId: string, blobData: any): Promise<[]> { - return Promise.resolve([]); - } - public clientFailedExperimentPoint( reason: string, site: string, diff --git a/packages/backend/test/unit/controllers/mocks/ExperimentClientControllerMock.ts b/packages/backend/test/unit/controllers/mocks/ExperimentClientControllerMock.ts deleted file mode 100644 index 07a29369dc..0000000000 --- a/packages/backend/test/unit/controllers/mocks/ExperimentClientControllerMock.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Service } from 'typedi'; -import { UpgradeLogger } from '../../../../src/lib/logger/UpgradeLogger'; - -@Service() -export default class ExperimentClientControllerMock { - public checkIfUserExist(userId: string, take: number, logger: UpgradeLogger, api?: string): Promise<[]> { - return Promise.resolve([]); - } -} diff --git a/packages/types/src/Experiment/interfaces.ts b/packages/types/src/Experiment/interfaces.ts index 2376d51933..56214f18ec 100644 --- a/packages/types/src/Experiment/interfaces.ts +++ b/packages/types/src/Experiment/interfaces.ts @@ -50,7 +50,7 @@ export interface IExperimentEnrollmentDetailStats { conditions: IConditionEnrollmentStats[]; } -export interface IExperimentAssignmentv5 { +export interface IExperimentAssignment { site: string; target: string; assignedCondition: AssignedCondition[]; @@ -58,6 +58,14 @@ export interface IExperimentAssignmentv5 { experimentType: EXPERIMENT_TYPE; } +/** + * @deprecated Use `IExperimentAssignment` instead. This interface is retained for + * backward compatibility (including declaration merging/augmentation) and will be + * removed in a future major version. + */ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface IExperimentAssignmentv5 extends IExperimentAssignment {} + export interface AssignedCondition { conditionCode: string; payload: IPayload; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 34799e0de1..2679da1d32 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -73,6 +73,7 @@ export { CaliperGradingProfile, CaliperEnvelope, IPayload, + IExperimentAssignment, IExperimentAssignmentv5, ILogRequestBody, ILogMetrics,