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

Feat/vil 579 2 modification des requetes #1019

Open
wants to merge 54 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
d9a84d2
Merge branch 'master' of https://github.com/parlemonde/1village into …
SimNed Sep 2, 2024
c6ac6ab
add AnaltyicSession migration
SimNed Sep 3, 2024
7d7afee
add user is not null verification to classroomStats and sessionStats
SimNed Sep 3, 2024
d713582
add migration
SimNed Sep 3, 2024
2b0ea29
update struct in work
SimNed Sep 4, 2024
de9f9e3
Merge branch 'master' of https://github.com/parlemonde/1village into …
SimNed Sep 9, 2024
7ab29c6
VIL-579-2modification des fonction de requetes
dodoLaprovence Oct 10, 2024
072b9dc
VIL-579-2modification des fonction de requetes
dodoLaprovence Oct 10, 2024
7c2aab1
VIL-579-2modification des fonction de requetes
dodoLaprovence Oct 10, 2024
ab0a720
VIL-579-2modification des fonction de requetes
dodoLaprovence Oct 10, 2024
c3cd756
modification des requetes et de la page dashboard
dodoLaprovence Nov 11, 2024
09b6c61
linter mondification
dodoLaprovence Nov 11, 2024
f441412
conflict branch
dodoLaprovence Nov 11, 2024
f30b34b
linter order import
dodoLaprovence Nov 11, 2024
5137e09
linter order import
dodoLaprovence Nov 11, 2024
3af8de4
typescript error
dodoLaprovence Nov 11, 2024
985336a
fusion des changements
dodoLaprovence Dec 11, 2024
b8f98f0
fusion des changements
dodoLaprovence Dec 11, 2024
c47f322
fusion des changements
dodoLaprovence Dec 11, 2024
1119fcd
conflict server
dodoLaprovence Dec 12, 2024
a316f50
conflict server
dodoLaprovence Dec 12, 2024
1ad6f62
conflict server
dodoLaprovence Dec 12, 2024
ce4ad76
conflict server
dodoLaprovence Dec 12, 2024
062c66a
add AnaltyicSession migration
SimNed Sep 3, 2024
00171ea
add user is not null verification to classroomStats and sessionStats
SimNed Sep 3, 2024
18d503c
add migration
SimNed Sep 3, 2024
6c317f4
update struct in work
SimNed Sep 4, 2024
3d59ba1
VIL-579-2modification des fonction de requetes
dodoLaprovence Oct 10, 2024
db3fdfa
VIL-579-2modification des fonction de requetes
dodoLaprovence Oct 10, 2024
4699522
VIL-579-2modification des fonction de requetes
dodoLaprovence Oct 10, 2024
a7d440a
VIL-579-2modification des fonction de requetes
dodoLaprovence Oct 10, 2024
a6db0c9
modification des requetes et de la page dashboard
dodoLaprovence Nov 11, 2024
ad5063f
linter mondification
dodoLaprovence Nov 11, 2024
84fcfcb
linter order import
dodoLaprovence Nov 11, 2024
a31caa3
linter order import
dodoLaprovence Nov 11, 2024
b2d51f6
typescript error
dodoLaprovence Nov 11, 2024
43184bd
fusion des changements
dodoLaprovence Dec 11, 2024
a39d219
fusion des changements
dodoLaprovence Dec 11, 2024
5844e79
fusion des changements
dodoLaprovence Dec 11, 2024
dc95c08
conflict server
dodoLaprovence Dec 12, 2024
7aa3470
conflict server
dodoLaprovence Dec 12, 2024
9efa3ee
conflict server
dodoLaprovence Dec 12, 2024
aafb926
conflict server
dodoLaprovence Dec 12, 2024
ee6e9ae
Merge remote-tracking branch 'origin/feat/VIL-579-2-modification-des-…
dodoLaprovence Dec 12, 2024
698bac3
Merge branch 'master' into feat/VIL-579-2-modification-des-requetes
dodoLaprovence Dec 17, 2024
9f0a098
clean yarn
dodoLaprovence Dec 17, 2024
21a0559
clean yarn
dodoLaprovence Dec 17, 2024
41b4de5
clean yarn
dodoLaprovence Dec 17, 2024
1794819
linter error
dodoLaprovence Dec 17, 2024
d606a0c
typescript error
dodoLaprovence Dec 17, 2024
3767f1a
typescript error
dodoLaprovence Dec 17, 2024
13bd759
typescript error
dodoLaprovence Dec 17, 2024
7c7ee39
resolution erreur statistiques
dodoLaprovence Jan 9, 2025
71293ab
resolution erreur statistiques
dodoLaprovence Jan 9, 2025
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
Prev Previous commit
Next Next commit
conflict server
dodoLaprovence committed Dec 12, 2024
commit 1119fcde25c1595d01bcd76d3c5bd41e4d7a1b8e
25 changes: 25 additions & 0 deletions server/entities/phaseHistory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Column, CreateDateColumn, DeleteDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';

import type { VillagePhase } from './village';
import { Village } from './village';

@Entity()
@Index('IDX_PHASE_HISTORY', ['village', 'phase'], { unique: true })
export class PhaseHistory {
@PrimaryGeneratedColumn()
public id: number;

@ManyToOne(() => Village, (village) => village.phaseHistories, { onDelete: 'CASCADE' })
village: Village;

@Column({
type: 'tinyint',
})
phase: VillagePhase;

@CreateDateColumn({ type: 'datetime' })
public startingOn: Date;

@DeleteDateColumn({ type: 'datetime' })
public endingOn: Date;
}
4 changes: 4 additions & 0 deletions server/entities/village.ts
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import { Classroom } from './classroom';
import { Game } from './game';
import { GameResponse } from './gameResponse';
import { Image } from './image';
import { PhaseHistory } from './phaseHistory';
import { User } from './user';

export { VillagePhase };
@@ -39,6 +40,9 @@ export class Village implements VillageInterface {
})
public activePhase: number;

@OneToMany(() => PhaseHistory, (phaseHistory) => phaseHistory.village, { eager: true })
public phaseHistories: PhaseHistory[];

@OneToOne(() => Activity, { onDelete: 'SET NULL' })
@JoinColumn({ name: 'anthemId' })
public anthem: Activity | null;
37 changes: 35 additions & 2 deletions server/stats/classroomStats.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { UserType } from '../../types/user.type';
import type { Activity } from '../entities/activity';
import { Classroom } from '../entities/classroom';
import { Video } from '../entities/video';
import type { VillagePhase } from '../entities/village';
import { AppDataSource } from '../utils/data-source';
import { generateEmptyFilterParams, getChildrenCodesCount, getConnectedFamiliesCount, getFamiliesWithoutAccount } from './queryStatsByFilters';

const classroomRepository = AppDataSource.getRepository(Classroom);

@@ -78,11 +78,11 @@
return parseInt(result.classroomsCount);
};

export const normalizeForCountry = (inputData: any) => {

Check warning on line 81 in server/stats/classroomStats.ts

GitHub Actions / lint

Unexpected any. Specify a different type
const phaseMap = new Map();

inputData.forEach(({ activities, classroomCountryCode }: any) => {

Check warning on line 84 in server/stats/classroomStats.ts

GitHub Actions / lint

Unexpected any. Specify a different type
activities.forEach(({ count, phase, type }: any) => {

Check warning on line 85 in server/stats/classroomStats.ts

GitHub Actions / lint

Unexpected any. Specify a different type
if (!phaseMap.has(phase)) {
phaseMap.set(phase, new Map());
}
@@ -98,7 +98,7 @@
});

// Obtenez tous les codes pays uniques
const allCountryCodes = Array.from(new Set(inputData.map(({ classroomCountryCode }: any) => classroomCountryCode)));

Check warning on line 101 in server/stats/classroomStats.ts

GitHub Actions / lint

Unexpected any. Specify a different type

// Préparer les données de sortie
const result = Array.from(phaseMap.entries()).map(([phase, countryMap]) => ({
@@ -135,3 +135,36 @@

return result;
};

export const getChildrenCodesCountForClassroom = async (classroomId: number, phase: VillagePhase) => {
const classroom = await classroomRepository
.createQueryBuilder('classroom')
.innerJoin('classroom.village', 'village')
.where('classroom.id = :classroomId', { classroomId })
.getOne();

const villageId = classroom?.villageId;

if (!classroomId || !villageId) return 0;
let filterParams = generateEmptyFilterParams();
filterParams = { ...filterParams, villageId, classroomId, phase };
const whereClause = { clause: 'classroom.id = :classroomId', value: { classroomId } };
return await getChildrenCodesCount(filterParams, whereClause);
};

export const getConnectedFamiliesCountForClassroom = async (classroomId: number, phase: VillagePhase) => {
const classroom = await classroomRepository
.createQueryBuilder('classroom')
.innerJoin('classroom.village', 'village')
.where('classroom.id = :classroomId', { classroomId })
.getOne();

const villageId = classroom?.villageId;
let filterParams = generateEmptyFilterParams();
filterParams = { ...filterParams, villageId, classroomId, phase };
return await getConnectedFamiliesCount(filterParams);
};

export const getFamiliesWithoutAccountForClassroom = async (classroomId: number) => {
return getFamiliesWithoutAccount('classroom.id = :classroomId', { classroomId });
};
31 changes: 31 additions & 0 deletions server/stats/globalStats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
generateEmptyFilterParams,
getChildrenCodesCount,
getConnectedFamiliesCount,
getFamiliesWithoutAccount,
getFamilyAccountsCount,
getFloatingAccounts,
} from './queryStatsByFilters';

export const getFamiliesWithoutAccountForGlobal = async () => {
return getFamiliesWithoutAccount();
};

export const getConnectedFamiliesCountForGlobal = async () => {
const filterParams = generateEmptyFilterParams();
return getConnectedFamiliesCount(filterParams);
};

export const getChildrenCodesCountForGlobal = async () => {
const filterParams = generateEmptyFilterParams();
return await getChildrenCodesCount(filterParams);
};
export const getFloatingAccountsForGlobal = async () => {
const filterParams = generateEmptyFilterParams();
return await getFloatingAccounts(filterParams);
};

export const getFamilyAccountsCountForGlobal = async () => {
const filterParams = generateEmptyFilterParams();
return await getFamilyAccountsCount(filterParams);
};
149 changes: 149 additions & 0 deletions server/stats/queryStatsByFilters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import type { StatsFilterParams } from '../../types/statistics.type';
import { PhaseHistory } from '../entities/phaseHistory';
import { Student } from '../entities/student';
import { User } from '../entities/user';
import { VillagePhase, Village } from '../entities/village';
import { AppDataSource } from '../utils/data-source';

const studentRepository = AppDataSource.getRepository(Student);
const villageRepository = AppDataSource.getRepository(Village);
const userRepository = AppDataSource.getRepository(User);
const phaseHistoryRepository = AppDataSource.getRepository(PhaseHistory);

export const getFamiliesWithoutAccount = async (condition?: string, conditionValue?: object) => {
const query = studentRepository
.createQueryBuilder('student')
.innerJoin('student.classroom', 'classroom')
.innerJoin('classroom.user', 'user')
.innerJoin('user.village', 'village')
.where('student.numLinkedAccount < 1');

if (condition && conditionValue) {
query.andWhere(condition, conditionValue);
}

query.select([
'classroom.name AS classroom_name',
'classroom.countryCode as classroom_country',
'student.firstname AS student_firstname',
'student.lastname AS student_lastname',
'student.id AS student_id',
'student.createdAt as student_creation_date',
'village.name AS village_name',
]);

return query.getRawMany();
};

export const getConnectedFamiliesCount = async (filterParams: StatsFilterParams) => {
const { villageId, classroomId, phase } = filterParams;
const query = studentRepository
.createQueryBuilder('student')
.innerJoin('classroom', 'classroom', 'classroom.id = student.classroomId')
.andWhere('student.numLinkedAccount >= 1');

if (classroomId) {
query.andWhere('classroom.id = :classroomId', { classroomId });
}

if (villageId) {
query.andWhere('classroom.villageId = :villageId', { villageId });

if (phaseWasSelected(phase)) {
const phaseValue = phase as number;
const { debut, end } = await getPhasePeriod(villageId, phaseValue);
query.andWhere('student.createdAt >= :debut', { debut });
if (phaseValue != (await villageRepository.findOne({ where: { id: villageId } }))?.activePhase) {
query.andWhere('student.createdAt <= :end', { end });
}
}
}

return await query.getCount();
};

export const getFloatingAccounts = async (filterParams: StatsFilterParams) => {
const { villageId } = filterParams;
const query = userRepository.createQueryBuilder('user').where('user.hasStudentLinked = 0').andWhere('user.type = 4');

if (villageId) query.andWhere('user.villageId = :villageId', { villageId });

query.select(['user.id', 'user.firstname', 'user.lastname', 'user.language', 'user.email', 'user.createdAt']);
const floatingAccounts = await query.getMany();
return floatingAccounts;
};

export const getFamilyAccountsCount = async (filterParams: { villageId: number | undefined; phase: VillagePhase | undefined }) => {
const { villageId, phase } = filterParams;
const village = await villageRepository.findOne({ where: { id: villageId } });
const query = userRepository
.createQueryBuilder('user')
.innerJoin('user.village', 'village')
.innerJoin('classroom', 'classroom', 'classroom.villageId = village.id')
.innerJoin('student', 'student', 'student.classroomId = classroom.id')
.where('user.type = 3');

if (villageId) {
query.andWhere('classroom.villageId = :villageId', { villageId });
if (phaseWasSelected(phase)) {
const phaseValue = phase as number;
const { debut, end } = await getPhasePeriod(villageId, phaseValue);
query.andWhere('user.createdAt >= :debut', { debut });
if (phaseValue != village?.activePhase) query.andWhere('student.createdAt <= :end', { end });
}
}

query.groupBy('user.id');
const familyAccountsCount = await query.getCount();
return familyAccountsCount;
};

export const generateEmptyFilterParams = (): StatsFilterParams => {
const filterParams: { [K in keyof StatsFilterParams]: StatsFilterParams[K] } = {
villageId: undefined,
classroomId: undefined,
phase: undefined,
};

return filterParams;
};
export type WhereClause = {
clause: string;
value: object;
};
export const getChildrenCodesCount = async (filterParams: StatsFilterParams, whereClause?: WhereClause) => {
const { villageId, phase } = filterParams;
const village = await villageRepository.findOne({ where: { id: villageId } });
const query = studentRepository.createQueryBuilder('student').innerJoin('student.classroom', 'classroom').innerJoin('classroom.village', 'village');
if (whereClause) query.where(whereClause.clause, whereClause.value);

if (phaseWasSelected(phase) && villageId) {
const phaseValue = phase as number;
const { debut, end } = await getPhasePeriod(villageId, phaseValue);
query.andWhere('student.createdAt >= :debut', { debut });
if (phaseValue != village?.activePhase) query.andWhere('student.createdAt <= :end', { end });
}

return await query.getCount();
};

export const getPhasePeriod = async (villageId: number, phase: number): Promise<{ debut: Date | undefined; end: Date | undefined }> => {
// Getting the debut and end dates for the given phase
const query = phaseHistoryRepository
.createQueryBuilder('phaseHistory')
.withDeleted()
.where('phaseHistory.villageId = :villageId', { villageId })
.andWhere('phaseHistory.phase = :phase', { phase });
query.select(['phaseHistory.startingOn', 'phaseHistory.endingOn']);
const result = await query.getOne();
const debut = result?.startingOn;
const end = result?.endingOn;
return {
debut,
end,
};
};

export const phaseWasSelected = (phase: number | undefined): boolean => {
return phase !== undefined && Object.values(VillagePhase).includes(+phase);
};
Empty file added server/stats/villageStats.ts
Empty file.

Unchanged files with check annotations Beta

import { Classroom } from '../entities/classroom';
import {
getClassroomsInfos,

Check warning on line 5 in server/controllers/statistics.ts

GitHub Actions / lint

'getClassroomsInfos' is defined but never used
getConnectedClassroomsCount,
getContributedClassroomsCount,

Check warning on line 7 in server/controllers/statistics.ts

GitHub Actions / lint

'getContributedClassroomsCount' is defined but never used
getRegisteredClassroomsCount,
normalizeForCountry,
} from '../stats/classroomStats';
userFirstName: classroom.userFirstname,
userLastName: classroom.userLastname,
activities: classroom.activitiesCount
? classroom.activitiesCount.flatMap((phaseObj: { activities: any[]; phase: string }) =>

Check warning on line 155 in server/controllers/statistics.ts

GitHub Actions / lint

Unexpected any. Specify a different type
phaseObj.activities.map((activity) => ({
count: activity.count,
type: activity.type,
: [], // Si activitiesCount est null, retourner une liste vide
}));
const result = { data: [...transformedData], phases: normalizeForCountry(transformedData) };

Check warning on line 165 in server/controllers/statistics.ts

GitHub Actions / lint

'result' is assigned a value but never used
res.sendJSON(classroomsData);
import { AppDataSource } from '../utils/data-source';
const analyticSessionRepository = AppDataSource.getRepository(AnalyticSession);
const durationThreshold = 60;

Check warning on line 6 in server/stats/sessionStats.ts

GitHub Actions / lint

'durationThreshold' is assigned a value but never used
const teacherType = UserType.TEACHER;

Check warning on line 7 in server/stats/sessionStats.ts

GitHub Actions / lint

'teacherType' is assigned a value but never used
export const getMinDuration = async (villageId?: number | null, countryCode?: string | null, classroomId?: number | null) => {
const queryBuilder = analyticSessionRepository
export interface VillageFilter {
countryIsoCode: string;
}

Check failure on line 20 in types/village.type.ts

GitHub Actions / lint

Insert `⏎`