Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export class AnalyticsController {
* example: 45b9d8cd-f113-4f93-9826-c3d1ff4ee73c
* dateEnum:
* type: string
* enum: [last_seven_days, last_three_months, last_six_months, last_twelve_months]
* enum: [last_seven_days, last_two_weeks, last_one_month, last_three_months, last_six_months, last_twelve_months, total]
* clientOffset:
* type: number
* tags:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ export class AnalyticsRepository extends Repository<AnalyticsRepository> {
const experiment = await experimentRepository.findOneBy({ id: experimentId });
let individualEnrollmentConditionAndDecisionPoint: Promise<any>;
if (experiment && experiment.assignmentUnit === ASSIGNMENT_UNIT.WITHIN_SUBJECTS) {
individualEnrollmentConditionAndDecisionPoint = individualEnrollmentRepository
const individualEnrollmentConditionAndDecisionPointQuery = individualEnrollmentRepository
.createQueryBuilder('individualEnrollment')
.select([
'count(distinct("individualEnrollment"."userId"))::int',
Expand All @@ -530,8 +530,11 @@ export class AnalyticsRepository extends Repository<AnalyticsRepository> {
individualSelectRange,
])
.leftJoin(DecisionPoint, 'dp', 'dp.id = individualEnrollment.partitionId')
.where('"individualEnrollment"."experimentId" = :id', { id: experimentId })
.andWhere(individualWhereDate)
.where('"individualEnrollment"."experimentId" = :id', { id: experimentId });
if (dateRange !== DATE_RANGE.TOTAL) {
individualEnrollmentConditionAndDecisionPointQuery.andWhere(individualWhereDate);
}
individualEnrollmentConditionAndDecisionPoint = individualEnrollmentConditionAndDecisionPointQuery
.andWhere((qb) => {
const subQuery = qb.subQuery().select('user.id').from(PreviewUser, 'user').getQuery();
return '"individualEnrollment"."userId" NOT IN ' + subQuery;
Expand All @@ -543,16 +546,19 @@ export class AnalyticsRepository extends Repository<AnalyticsRepository> {
.addGroupBy(groupByRange)
.execute();
} else {
individualEnrollmentConditionAndDecisionPoint = individualEnrollmentRepository
const individualEnrollmentConditionAndDecisionPointQuery = individualEnrollmentRepository
.createQueryBuilder('individualEnrollment')
.select([
'count(distinct("individualEnrollment"."userId"))::int',
'"individualEnrollment"."conditionId"',
'"individualEnrollment"."partitionId"',
individualSelectRange,
])
.where('"individualEnrollment"."experimentId" = :id', { id: experimentId })
.andWhere(individualWhereDate)
.where('"individualEnrollment"."experimentId" = :id', { id: experimentId });
if (dateRange !== DATE_RANGE.TOTAL) {
individualEnrollmentConditionAndDecisionPointQuery.andWhere(individualWhereDate);
}
individualEnrollmentConditionAndDecisionPoint = individualEnrollmentConditionAndDecisionPointQuery
.andWhere((qb) => {
const subQuery = qb.subQuery().select('user.id').from(PreviewUser, 'user').getQuery();
return '"individualEnrollment"."userId" NOT IN ' + subQuery;
Expand All @@ -576,14 +582,20 @@ export class AnalyticsRepository extends Repository<AnalyticsRepository> {
'"groupEnrollment"."partitionId"',
groupSelectRange,
])
.where('"groupEnrollment"."experimentId" = :id', { id: experimentId })
.andWhere(groupWhereDate)
.where('"groupEnrollment"."experimentId" = :id', { id: experimentId });

const groupEnrollmentQueryWithDateRange =
dateRange === DATE_RANGE.TOTAL
? groupEnrollmentConditionAndDecisionPoint
: groupEnrollmentConditionAndDecisionPoint.andWhere(groupWhereDate);

const ret = groupEnrollmentQueryWithDateRange
.groupBy('"groupEnrollment"."conditionId"')
.addGroupBy('"groupEnrollment"."partitionId"')
.addGroupBy(groupByRange)
.execute();

return Promise.all([individualEnrollmentConditionAndDecisionPoint, groupEnrollmentConditionAndDecisionPoint]);
return Promise.all([individualEnrollmentConditionAndDecisionPoint, ret]);
}

private getDateVariables(
Expand All @@ -593,6 +605,7 @@ export class AnalyticsRepository extends Repository<AnalyticsRepository> {
): { whereDate: string; selectRange: string } {
let whereDate = '';
let selectRange = '';
const dateTruncString = dateRange === DATE_RANGE.TOTAL ? 'year' : 'month';
switch (dateRange) {
case DATE_RANGE.LAST_SEVEN_DAYS:
whereDate = `"${tableName}"."createdAt" > current_date - interval '7 days'`;
Expand All @@ -602,6 +615,22 @@ export class AnalyticsRepository extends Repository<AnalyticsRepository> {
clientOffset +
` minutes') AS date_range`;
break;
case DATE_RANGE.LAST_TWO_WEEKS:
whereDate = `"${tableName}"."createdAt" > current_date - interval '14 days'`;
selectRange =
`date_trunc('day', "${tableName}"."createdAt"::timestamptz at time zone 'UTC'
+ interval '` +
clientOffset +
` minutes') AS date_range`;
break;
case DATE_RANGE.LAST_ONE_MONTH:
whereDate = `"${tableName}"."createdAt" > current_date - interval '30 days'`;
selectRange =
`date_trunc('day', "${tableName}"."createdAt"::timestamptz at time zone 'UTC'
+ interval '` +
clientOffset +
` minutes') AS date_range`;
break;
case DATE_RANGE.LAST_THREE_MONTHS:
whereDate = `"${tableName}"."createdAt" > current_date - interval '3 months'`;
selectRange =
Expand All @@ -618,14 +647,21 @@ export class AnalyticsRepository extends Repository<AnalyticsRepository> {
clientOffset +
` minutes') AS date_range`;
break;
default:
case DATE_RANGE.LAST_TWELVE_MONTHS:
whereDate = `"${tableName}"."createdAt" > current_date - interval '12 months'`;
selectRange =
`date_trunc('month', "${tableName}"."createdAt"::timestamptz at time zone 'UTC'
+ interval '` +
clientOffset +
` minutes') AS date_range`;
break;
default:
selectRange =
`date_trunc('${dateTruncString}', "${tableName}"."createdAt"::timestamptz at time zone 'UTC'
+ interval '` +
clientOffset +
` minutes') AS date_range`;
break;
}
return { whereDate, selectRange };
}
Expand Down
51 changes: 40 additions & 11 deletions backend/packages/Upgrade/src/api/services/AnalyticsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
IExperimentEnrollmentStats,
} from 'upgrade_types';
import { AnalyticsRepository, ExperimentDetailsForCSVData } from '../repositories/AnalyticsRepository';
import { Experiment } from '../models/Experiment';
import ObjectsToCsv from 'objects-to-csv';
import fs from 'fs';
import { env } from '../../env';
Expand Down Expand Up @@ -70,6 +69,15 @@ export class AnalyticsService {
dateRange: DATE_RANGE,
clientOffset: number
): Promise<IEnrollmentStatByDate[]> {
const experiment = await this.experimentRepository.findOne({
where: { id: experimentId },
relations: ['conditions', 'partitions'],
});

const experimentAge = dayjs().year() - dayjs(experiment.createdAt).year();

const [individualEnrollmentConditionAndDecisionPoint, groupEnrollmentConditionAndDecisionPoint] =
await this.analyticsRepository.getEnrollmentByDateRange(experimentId, dateRange, clientOffset);
const keyToReturn = {};
switch (dateRange) {
case DATE_RANGE.LAST_SEVEN_DAYS:
Expand All @@ -81,6 +89,24 @@ export class AnalyticsService {
keyToReturn[newDate] = {};
}
break;
case DATE_RANGE.LAST_TWO_WEEKS:
for (let i = 0; i < 14; i++) {
const date = new Date();
date.setTime(date.getTime() + (date.getTimezoneOffset() + clientOffset) * 60000);
date.setDate(date.getDate() - i);
const newDate = date.toDateString();
keyToReturn[newDate] = {};
}
break;
case DATE_RANGE.LAST_ONE_MONTH:
for (let i = 0; i < 30; i++) {
const date = new Date();
date.setTime(date.getTime() + (date.getTimezoneOffset() + clientOffset) * 60000);
date.setDate(date.getDate() - i);
const newDate = date.toDateString();
keyToReturn[newDate] = {};
}
break;
case DATE_RANGE.LAST_THREE_MONTHS:
for (let i = 0; i < 3; i++) {
const date = new Date();
Expand All @@ -101,7 +127,7 @@ export class AnalyticsService {
keyToReturn[newDate] = {};
}
break;
default:
case DATE_RANGE.LAST_TWELVE_MONTHS:
for (let i = 0; i < 12; i++) {
const date = new Date();
date.setTime(date.getTime() + (date.getTimezoneOffset() + clientOffset) * 60000);
Expand All @@ -111,16 +137,19 @@ export class AnalyticsService {
keyToReturn[newDate] = {};
}
break;
}

const promiseArray = await Promise.all([
this.experimentRepository.findOne({ where: { id: experimentId }, relations: ['conditions', 'partitions'] }),
this.analyticsRepository.getEnrollmentByDateRange(experimentId, dateRange, clientOffset),
]);

const experiment: Experiment = promiseArray[0];
const [individualEnrollmentConditionAndDecisionPoint, groupEnrollmentConditionAndDecisionPoint] = promiseArray[1];
default:
for (let i = 0; i < experimentAge + 1; i++) {
const date = new Date();
date.setTime(date.getTime() + (date.getTimezoneOffset() + clientOffset) * 60000);
date.setDate(1);
date.setFullYear(date.getFullYear() - i);
date.setMonth(0);
const newDate = date.toDateString();
keyToReturn[newDate] = {};
}

break;
}
return Object.keys(keyToReturn).map((date) => {
const stats: IExperimentEnrollmentDetailDateStats = {
id: experimentId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,8 @@ export interface IContextMetaData {

export interface IExperimentGraphInfo {
[DATE_RANGE.LAST_SEVEN_DAYS]: IEnrollmentStatByDate[];
[DATE_RANGE.LAST_TWO_WEEKS]: IEnrollmentStatByDate[];
[DATE_RANGE.LAST_ONE_MONTH]: IEnrollmentStatByDate[];
[DATE_RANGE.LAST_THREE_MONTHS]: IEnrollmentStatByDate[];
[DATE_RANGE.LAST_SIX_MONTHS]: IEnrollmentStatByDate[];
[DATE_RANGE.LAST_TWELVE_MONTHS]: IEnrollmentStatByDate[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ describe('ExperimentsReducer', () => {
const previousState = { ...initialState };
previousState.graphInfo = {
[DATE_RANGE.LAST_SEVEN_DAYS]: [],
[DATE_RANGE.LAST_TWO_WEEKS]: [],
[DATE_RANGE.LAST_ONE_MONTH]: [],
[DATE_RANGE.LAST_THREE_MONTHS]: [],
[DATE_RANGE.LAST_SIX_MONTHS]: [],
[DATE_RANGE.LAST_TWELVE_MONTHS]: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,8 @@ describe('Experiments Selectors', () => {
[DATE_RANGE.LAST_SIX_MONTHS]: null,
[DATE_RANGE.LAST_THREE_MONTHS]: null,
[DATE_RANGE.LAST_TWELVE_MONTHS]: null,
[DATE_RANGE.LAST_ONE_MONTH]: null,
[DATE_RANGE.LAST_TWO_WEEKS]: null,
},
graphRange: DATE_RANGE.LAST_SEVEN_DAYS,
isGraphInfoLoading: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@
</app-common-section-card-action-buttons>

<!-- content -->
<app-experiment-enrollment-data content *ngIf="isSectionCardExpanded"></app-experiment-enrollment-data>
<home-enrollment-over-time
content
[experiment]="experiment"
*ngIf="isSectionCardExpanded"
></home-enrollment-over-time>
</app-common-section-card>
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { ExperimentEnrollmentDataComponent } from './experiment-enrollment-data/experiment-enrollment-data.component';
import { EnrollmentOverTimeComponent } from '../../../../../home/components/enrollment-over-time/enrollment-over-time.component';
import { ExperimentService } from '../../../../../../../core/experiments/experiments.service';
import { AuthService } from '../../../../../../../core/auth/auth.service';

Expand All @@ -18,6 +19,7 @@ import { AuthService } from '../../../../../../../core/auth/auth.service';
CommonSectionCardTitleHeaderComponent,
CommonSectionCardActionButtonsComponent,
ExperimentEnrollmentDataComponent,
EnrollmentOverTimeComponent,
TranslateModule,
],
templateUrl: './experiment-enrollment-data-section-card.component.html',
Expand Down
Loading