Skip to content
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

Added appContext in Metrics #1472

Merged
merged 12 commits into from
Jun 3, 2024
2 changes: 1 addition & 1 deletion backend/packages/Upgrade/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,4 @@ CLIENT_API_SECRET = secret
CLIENT_API_KEY = key

CONTEXT_METADATA = {"add":{"EXP_IDS":["add-id1","add-id2"],"EXP_POINTS":["add-point1","add-point2"],"GROUP_TYPES":["add-group1","add-group2"],"CONDITIONS":["add-con1","add-con2","add-con3"]},"sub":{"EXP_IDS":["sub-id1","sub-id2"],"EXP_POINTS":["sub-point1","sub-point2"],"GROUP_TYPES":["sub-group1","sub-group2"],"CONDITIONS":["sub-con1","sub-con2","sub-con3"]},"mul":{"EXP_IDS":["mul-id1","mul-id2"],"EXP_POINTS":["mul-point1","mul-point2"],"GROUP_TYPES":["mul-group1","mul-group2"],"CONDITIONS":["mul-con1","mul-con2","mul-con3"]},"div":{"EXP_IDS":["div-id1","div-id2"],"EXP_POINTS":["div-point1","div-point2"],"GROUP_TYPES":["div-group1","div-group2"],"CONDITIONS":["div-con1","div-con2","div-con3"]}}
METRICS = [{"metric": "totalTimeSeconds","datatype": "continuous"}, { "groupClass": "masteryWorkspace", "allowedKeys": [ "calculating_area_various_figures", "Compare_functions_diff_reps_quadratic" ], "attributes": [{ "metric": "timeSeconds", "datatype": "continuous"}]}]
PRE_DEFINED_METRICS = [{"metrics":[{"metric":"totalTimeSeconds","datatype":"continuous"},{"metric":"workspaceCompletionStatus","datatype":"categorical","allowedValues":["GRADUATED","PROMOTED"]}],"contexts":["test-context1","test-context2"]},{"metrics":[{"groupClass":"addWorkspace","allowedKeys":["level1","level2"],"attributes":[{"metric":"workspaceCompletionStatus","datatype":"categorical","allowedValues":["GRADUATED","PROMOTED"]},{"metric":"timeSpent","datatype":"continuous"}]}],"contexts":["test-context3"]}]
RidhamShah marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion backend/packages/Upgrade/.env.test
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,4 @@ CLIENT_API_SECRET = secret
CLIENT_API_KEY = key

CONTEXT_METADATA = {"add":{"EXP_IDS":["add-id1","add-id2"],"EXP_POINTS":["add-point1","add-point2"],"GROUP_TYPES":["add-group1","add-group2"],"CONDITIONS":["add-con1","add-con2","add-con3"]},"sub":{"EXP_IDS":["sub-id1","sub-id2"],"EXP_POINTS":["sub-point1","sub-point2"],"GROUP_TYPES":["sub-group1","sub-group2"],"CONDITIONS":["sub-con1","sub-con2","sub-con3"]},"mul":{"EXP_IDS":["mul-id1","mul-id2"],"EXP_POINTS":["mul-point1","mul-point2"],"GROUP_TYPES":["mul-group1","mul-group2"],"CONDITIONS":["mul-con1","mul-con2","mul-con3"]},"div":{"EXP_IDS":["div-id1","div-id2"],"EXP_POINTS":["div-point1","div-point2"],"GROUP_TYPES":["div-group1","div-group2"],"CONDITIONS":["div-con1","div-con2","div-con3"]}}
METRICS = [{"metric": "totalTimeSeconds","datatype": "continuous"}, {"metric": "workspaceCompletionStatus","datatype": "categorical", "allowedValues": ["GRADUATED", "PROMOTED"]}, { "groupClass": "addWorkspace", "allowedKeys": [ "level1", "level2" ], "attributes": [{"metric": "workspaceCompletionStatus","datatype": "categorical", "allowedValues": ["GRADUATED", "PROMOTED"]},{"metric": "timeSpent","datatype": "continuous"}]}]
PRE_DEFINED_METRICS = [{"metrics":[{"metric":"totalTimeSeconds","datatype":"continuous"},{"metric":"workspaceCompletionStatus","datatype":"categorical","allowedValues":["GRADUATED","PROMOTED"]}],"contexts":["test-context1","test-context2"]},{"metrics":[{"groupClass":"addWorkspace","allowedKeys":["level1","level2"],"attributes":[{"metric":"workspaceCompletionStatus","datatype":"categorical","allowedValues":["GRADUATED","PROMOTED"]},{"metric":"timeSpent","datatype":"continuous"}]}],"contexts":["test-context3"]}]
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,7 @@ export class ExperimentClientController {
@Body({ validate: false })
metric: MetricValidator
): Promise<Metric[]> {
return await this.metricService.saveAllMetrics(metric.metricUnit, request.logger);
return await this.metricService.saveAllMetrics(metric.metricUnit, metric.context, request.logger);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,7 @@ export class ExperimentClientController {
@Body({ validate: false })
metric: MetricValidator
): Promise<Metric[]> {
return await this.metricService.saveAllMetrics(metric.metricUnit, request.logger);
return await this.metricService.saveAllMetrics(metric.metricUnit, metric.context, request.logger);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ export class ExperimentClientController {
@Body({ validate: false })
metric: MetricValidator
): Promise<Metric[]> {
return await this.metricService.saveAllMetrics(metric.metricUnit, request.logger);
return await this.metricService.saveAllMetrics(metric.metricUnit, metric.context, request.logger);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,7 @@ export class ExperimentClientController {
@Body({ validate: true })
metric: MetricValidator
): Promise<Metric[]> {
return await this.metricService.saveAllMetrics(metric.metricUnit, request.logger);
return await this.metricService.saveAllMetrics(metric.metricUnit, metric.context, request.logger);
}

/**
Expand Down
31 changes: 29 additions & 2 deletions backend/packages/Upgrade/src/api/controllers/MetricController.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Authorized, JsonController, Get, Delete, Param, Post, Req, Body } from 'routing-controllers';
import { MetricService } from '../services/MetricService';
import { IMetricUnit, SERVER_ERROR, } from 'upgrade_types';
import { IMetricUnit, SERVER_ERROR } from 'upgrade_types';
import { AppRequest } from '../../types';
import { MetricValidator } from './validators/MetricValidator';

Expand Down Expand Up @@ -33,6 +33,33 @@ export class MetricController {
return this.metricService.getAllMetrics(request.logger);
}

/**
* @swagger
* /metric/{context}:
* get:
* description: Get all metrics with context
* parameters:
* - in: path
* name: context
* required: true
* schema:
* type: string
* description: context
* tags:
* - Metrics
* produces:
* - application/json
* responses:
* '200':
* description: Get all metrics with context
* '404':
* description: Context not found
*/
@Get('/:context')
public getMetricsByContext(@Param('context') context: string, @Req() request: AppRequest): Promise<IMetricUnit[]> {
return this.metricService.getMetricsByContext(context, request.logger);
}

/**
* @swagger
* /metric/save:
Expand Down Expand Up @@ -75,7 +102,7 @@ export class MetricController {
@Body({ validate: true }) metric: MetricValidator,
@Req() request: AppRequest
): Promise<IMetricUnit[]> {
return this.metricService.upsertAllMetrics(metric.metricUnit, request.logger);
return this.metricService.upsertAllMetrics(metric.metricUnit, metric.context, request.logger);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { IsArray, IsEnum, IsNotEmpty, IsOptional, IsString, ValidateNested, ValidationOptions, registerDecorator } from 'class-validator';
import {
IsArray,
IsEnum,
IsNotEmpty,
IsOptional,
IsString,
ValidateNested,
ValidationOptions,
registerDecorator,
} from 'class-validator';
import { IMetricMetaData } from 'upgrade_types';

const IsMetricUnit = (validationOptions?: ValidationOptions) => {
Expand All @@ -14,7 +23,7 @@ const IsMetricUnit = (validationOptions?: ValidationOptions) => {
},
validator: {
validate(value: any) {
return validateMetricUnit(value)
return validateMetricUnit(value);
},
},
});
Expand All @@ -23,11 +32,11 @@ const IsMetricUnit = (validationOptions?: ValidationOptions) => {

function validateMetricUnit(data: unknown) {
if (Array.isArray(data) && data.every(isValidMetric)) {
return true
return true;
} else {
return false
return false;
}
};
}

function isValidMetric(value: any): value is IGroupMetric | ISingleMetric {
if ('groupClass' in value) {
Expand All @@ -49,9 +58,7 @@ function isValidMetric(value: any): value is IGroupMetric | ISingleMetric {
(value.allowedValues === undefined ||
(Array.isArray(value.allowedValues) &&
value.allowedValues.every(
(allowedValue) =>
typeof allowedValue === 'string' ||
typeof allowedValue === 'number'
(allowedValue) => typeof allowedValue === 'string' || typeof allowedValue === 'number'
)))
);
}
Expand All @@ -68,7 +75,7 @@ class ISingleMetric {
datatype: IMetricMetaData;

@IsOptional()
@IsArray({each: true})
@IsArray({ each: true })
allowedValues?: (string | number)[];
}

Expand All @@ -77,7 +84,7 @@ class IGroupMetric {
groupClass: string;

@IsArray()
@IsString({each: true})
@IsString({ each: true })
allowedKeys: string[];

@IsArray()
Expand All @@ -91,4 +98,9 @@ export class MetricValidator {
@IsNotEmpty()
@IsMetricUnit()
metricUnit: (ISingleMetric | IGroupMetric)[];

@IsArray()
@IsNotEmpty()
@IsString({ each: true })
context: string[];
}
3 changes: 3 additions & 0 deletions backend/packages/Upgrade/src/api/models/Metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export class Metric extends BaseModel {
@Column({ type: 'simple-array', nullable: true })
public allowedData: string[];

@Column('text', { array: true })
public context: string[];

@ManyToMany(() => Log, (log) => log.metrics, {
cascade: true,
})
Expand Down
10 changes: 10 additions & 0 deletions backend/packages/Upgrade/src/api/repositories/MetricRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ export class MetricRepository extends Repository<Metric> {
});
}

public async getMetricsByContext(context: string): Promise<Metric[]> {
return this.createQueryBuilder('metrics')
.where('context @> :searchContext', { searchContext: [context] })
.getMany()
.catch((errorMsg: any) => {
const errorMsgString = repositoryError(this.constructor.name, 'getMetricsByContext', { context }, errorMsg);
throw errorMsgString;
});
}

public async findMetricsWithQueries(ids: string[]): Promise<Metric[]> {
return this.createQueryBuilder('metrics')
.innerJoin('metrics.queries', 'queries')
Expand Down
25 changes: 21 additions & 4 deletions backend/packages/Upgrade/src/api/services/MetricService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,28 @@ export class MetricService {
return this.metricDocumentToJson(metricData);
}

public async saveAllMetrics(metrics: Array<IGroupMetric | ISingleMetric>, logger: UpgradeLogger): Promise<Metric[]> {
public async getMetricsByContext(context: string, logger: UpgradeLogger): Promise<IMetricUnit[]> {
logger.info({ message: `Get metrics by context ${context}` });
const metricData = await this.metricRepository.getMetricsByContext(context);
return this.metricDocumentToJson(metricData);
}

public async saveAllMetrics(
metrics: Array<IGroupMetric | ISingleMetric>,
contexts: string[],
logger: UpgradeLogger
): Promise<Metric[]> {
logger.info({ message: 'Save all metrics' });
return await this.addAllMetrics(metrics, logger);
return await this.addAllMetrics(metrics, contexts, logger);
}

public async upsertAllMetrics(
metrics: Array<IGroupMetric | ISingleMetric>,
contexts: string[],
logger: UpgradeLogger
): Promise<IMetricUnit[]> {
logger.info({ message: 'Upsert all metrics' });
const upsertedMetrics = await this.addAllMetrics(metrics, logger);
const upsertedMetrics = await this.addAllMetrics(metrics, contexts, logger);
return this.metricDocumentToJson(upsertedMetrics);
}

Expand All @@ -41,7 +52,11 @@ export class MetricService {
return this.metricDocumentToJson(updatedMetric);
}

private async addAllMetrics(metrics: Array<IGroupMetric | ISingleMetric>, logger: UpgradeLogger): Promise<Metric[]> {
private async addAllMetrics(
metrics: Array<IGroupMetric | ISingleMetric>,
contexts: string[],
logger: UpgradeLogger
): Promise<Metric[]> {
// check permission for metrics
const isAllowed = await this.checkMetricsPermission(logger);
if (!isAllowed) {
Expand All @@ -57,6 +72,7 @@ export class MetricService {
key: metric.key,
type: metric.type,
allowedData: metric.allowedData,
context: contexts,
}));
return this.metricRepository.save(metricDoc);
}
Expand Down Expand Up @@ -138,6 +154,7 @@ export class MetricService {
children: [],
metadata: { type: metric.type as any },
allowedData: metric.allowedData,
context: metric.context,
};
metricPointer.push(newMetric);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class contextInMetric1712553037665 implements MigrationInterface {
name = 'contextInMetric1712553037665';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "metric" ADD "context" text array NOT NULL`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "metric" DROP COLUMN "context"`);
}
}
2 changes: 1 addition & 1 deletion backend/packages/Upgrade/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export const env = {
initialization: {
contextMetadata: JSON.parse(getOsEnv('CONTEXT_METADATA')),
adminUsers: parseAdminUsers(getOsEnv('ADMIN_USERS')),
metrics: getOsEnvOptional('METRICS'),
metrics: getOsEnvOptional('PRE_DEFINED_METRICS'),
},
hostUrl: getOsEnv('HOST_URL'),
tokenSecretKey: getOsEnv('TOKEN_SECRET_KEY'),
Expand Down
4 changes: 3 additions & 1 deletion backend/packages/Upgrade/src/init/seed/initMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ export function InitMetrics(logger: UpgradeLogger): Promise<any> {
// Init default metrics in system
if (env.initialization.metrics) {
try {
return metricService.saveAllMetrics(JSON.parse(env.initialization.metrics), logger);
return JSON.parse(env.initialization.metrics).map((metricData) => {
RidhamShah marked this conversation as resolved.
Show resolved Hide resolved
return metricService.saveAllMetrics(metricData.metrics, metricData.contexts, logger);
});
} catch (err) {
const error = new Error('Error while initializing metrics');
logger.error(error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default async function CreateLog(): Promise<void> {

await settingService.setClientCheck(false, true, new UpgradeLogger());

await metricService.saveAllMetrics(metrics as any, new UpgradeLogger());
await metricService.saveAllMetrics(metrics as any, experimentObject.context, new UpgradeLogger());

const findMetric = await metricRepository.find();
expect(findMetric.length).toEqual(36);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default async function LogOperations(): Promise<void> {

await settingService.setClientCheck(false, true, new UpgradeLogger());

await metricService.saveAllMetrics(metrics as any, new UpgradeLogger());
await metricService.saveAllMetrics(metrics as any, experimentObject.context, new UpgradeLogger());

const findMetric = await metricRepository.find();
expect(findMetric.length).toEqual(36);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default async function RepeatedMeasure(): Promise<void> {

await settingService.setClientCheck(false, true, new UpgradeLogger());

await metricService.saveAllMetrics(metrics as any, new UpgradeLogger());
await metricService.saveAllMetrics(metrics as any, experimentObject.context, new UpgradeLogger());

// change experiment status to Enrolling
const experimentId = experiments[0].id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default async function MetricCRUD(): Promise<void> {
await settingService.setClientCheck(false, true, new UpgradeLogger());

// create metrics service
await metricService.saveAllMetrics(metrics as any, new UpgradeLogger());
await metricService.saveAllMetrics(metrics as any, ['home'], new UpgradeLogger());

let findMetric = await metricRepository.find();
expect(findMetric.length).toEqual(36);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default async function QueryCRUD(): Promise<void> {
);

// create metrics service
await metricService.saveAllMetrics(metrics as any, new UpgradeLogger());
await metricService.saveAllMetrics(metrics as any, experimentObject.context, new UpgradeLogger());

const findMetric = await metricRepository.find();
expect(findMetric.length).toEqual(36);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ export default async function MetricQueriesCheck(): Promise<void> {

await settingService.setClientCheck(false, true, new UpgradeLogger());
// create metrics service
await metricService.saveAllMetrics(metrics as any, new UpgradeLogger());
await metricService.saveAllMetrics(
metrics as any,
stratificationRandomExperimentAssignmentExperiment2.context,
new UpgradeLogger()
);

// creating new user
const user = await userService.upsertUser(systemUser as any, new UpgradeLogger());
Expand Down Expand Up @@ -412,7 +416,11 @@ export default async function MetricQueriesCheck(): Promise<void> {
];

// experiment object
const experimentObject = { ...stratificationRandomExperimentAssignmentExperiment2, queries: metricsQueries, conditionOrder: CONDITION_ORDER.ORDERED_ROUND_ROBIN };
const experimentObject = {
...stratificationRandomExperimentAssignmentExperiment2,
queries: metricsQueries,
conditionOrder: CONDITION_ORDER.ORDERED_ROUND_ROBIN,
};
await experimentService.update(experimentObject as any, user, new UpgradeLogger());
experiments = await experimentService.find(new UpgradeLogger());
const experimentTarget = experimentObject.partitions[0].target;
Expand Down Expand Up @@ -537,7 +545,7 @@ export default async function MetricQueriesCheck(): Promise<void> {
],
{ logger: new UpgradeLogger(), userDoc: experimentUserDoc }
);

const condition2 = experimentObject.conditions[1].conditionCode;
// user 1 mark experiment point on condition2
markedExperimentPoint = await markExperimentPoint(
Expand Down Expand Up @@ -699,7 +707,6 @@ export default async function MetricQueriesCheck(): Promise<void> {
const queryResult = await queryService.analyze([query.id], new UpgradeLogger());
let expectedValue;
switch (query.query.operationType) {

case OPERATION_TYPES.SUM: {
switch (query.repeatedMeasure) {
case REPEATED_MEASURE.mostRecent: {
Expand Down
Loading
Loading