Skip to content

Commit 8afdb47

Browse files
Cds general stats on graph (#87)
* scaffolding old proj * created new app in angular 21 * added new favicon + updated ReadMe + added license * init routes + pages * components pages * created home route + component * deleted app.html == useless * feat(): added reset style cross-browser * feat(): es lint enabled on run server * feat(): init hero section * feat(): added prefix for eslint error * feat(): added logo svg to init animation * fix(): app-root to opis-root * lang it * feat(): logo substitution * feat(): letters logo animated * format files post run * feat(): animated grafic bars * format(): logo + completed * feat(): created env with node * feat(): recreated original interfaces * feat(): no env pushed * feat(): init homepage * feat(): mappati i dipartimenti per icone * feat(): created palette + home btns adapted * rename(): department page * feat(): created card department * new style years section * feat(): add default icon * feat(): added color palette for user selection * feat(): styled departments section * enh(): resource in service * enh(): years section as component * fix(): test all components import from vite (it, expect, describe) * test(): implemented home test file * formatted files * fix(): info department saved on localStorage + fix(): detail url + feat(): added dep id * feat(): department page logic implemented * feat() deleted old_proj directory * style(): title department page * fix(): width of border title department * changed palette * feat(): added font * feat(): init cds flow + added loader + delay * feat(): created icon component * feat(): style on cds list * fix(): width list container * feat(): scaffolding for sections, added directory and created cds selection component * feat(): added fn that call both api cds schedeOpis and teachings * feat(): ng add ng2-charts * test(): cds-selection + department * format with prettier * test(): scaffolding for service testing * feat(): added api questions * feat(): init cds selected * feat(): introduced statistics calculation for graph * feat(): cache on api /domande * formattati i files * feat(): centralized duration animation of logo * feat(): animation logo cached * fix(): delay scroll and results * feat(): update ReadMe with version node * Update src/app/pages/home/home.html Co-authored-by: Stefano Borzì <stefanoborzi32@gmail.com> * Update src/app/services/questions/questions.service.ts Co-authored-by: Stefano Borzì <stefanoborzi32@gmail.com> * no imports logo-animated Co-authored-by: Stefano Borzì <stefanoborzi32@gmail.com> * return type retrieveQuestions Co-authored-by: Stefano Borzì <stefanoborzi32@gmail.com> * Update src/app/ui/sections/cds-selected-section/cds-selected-section.html Co-authored-by: Stefano Borzì <stefanoborzi32@gmail.com> * Fixed version Co-authored-by: Stefano Borzì <stefanoborzi32@gmail.com> * refactor: fixed versions * refactor(cds-selected-section.ts + year-section.ts): added changeDetection + added return types on fns + access modifiers from public to protected * refactor(department.ts): access modifiers from public to protected + return types on fns * refactor(homepage.ts): changeDetection + access modifiers from public to protected * refactor: added return type in fns in all services * refactor(dep-card.ts): access modifiers from public to protected + return type on fns * refactor(icon.ts): access modifiers from public to readonly * refactor(logo-animated.ts): access modifiers + changeDetection + return type on fns + forEach to for-of * refactor(loader.ts): added changeDetection * fix(dep-card.ts): removed protected, added readonly on department input * enh(graph.service.ts): used key from destructuring obj * fix(department.ts): no empty obj as department interface, used null + controls on html + renaming as component * refactor(department.ts): access modifier from public to protected for selectCds function * refactor(department.ts): followed suggestion of access modifier on departmentData signal as a readonly * refactor(home.ts): added readonly as suggestion on PR * refactor(info.ts): name convention + changeDetection * refactor: closing tag for components in home.html, department.html and dep-card.html * feat: add rule on creating new component, changeDetection * refactor: substitute enums with consts * feat: add eslint rules, fix unit-tests, ignore some lint rules, we will fix them later... * refactor: updating ReadMe with info of set-env * refactor: ReadMe * refactor: ReadMe * format and lint applied * feat: ci workflows * fix: coverage for test * fix: coverage for test * scaffolding: created scripts folder * feat: check coverage test in pipeline * fix: deploy.yml workflow * fix: no branch for lint.yml and tests.yml * fix: coverage test from vitest.config file * feat: logo svg on ReadME * fix: img logo ReadMe * fix: artifacts version for pipeline * fix: type error for file environment * fix: pipeline test * fix: path absolute for environment.ts * test: working thresholds limit * test: working thresholds limit * refactor(cds.service.ts): extraction schedules in a side function * enh: renaming file type deps-name * refactor(cds.service.ts): grouped schede by a side function, feat: completed calculations for CDS Stats all years * feat: example graph working * feat: format data for CDS stats general * feat: cds general stats working on line chart * format: run command * fix(graph.ts): lint error template * feat: build + deploy pipeline * fix: missing return type on graph component * enh: created type for Record means per year + added type on map colors * enh: no cast as AcademicYear thanks to util that help to have typed data from record value * fix: test job * format: npm run format * fix: lint errors on cds-selection.spec * fix: ordered jobs in pipeline * fix: workflows * fix: uses alternative method to order workflow on pr * fix: unique workflow for all jobs in order * fix: build failure --------- Co-authored-by: Stefano Borzì <stefanoborzi32@gmail.com>
1 parent 214025b commit 8afdb47

File tree

20 files changed

+326
-180
lines changed

20 files changed

+326
-180
lines changed

.github/workflows/deploy.yml

Lines changed: 0 additions & 50 deletions
This file was deleted.

.github/workflows/lint.yml

Lines changed: 0 additions & 22 deletions
This file was deleted.

.github/workflows/main.yml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: Opis CI/CD
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- "**"
7+
push:
8+
branches:
9+
- main
10+
11+
permissions:
12+
contents: write
13+
pages: write
14+
id-token: write
15+
16+
jobs:
17+
lint:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- name: Checkout code
21+
uses: actions/checkout@v4
22+
- name: Setup Node.js
23+
uses: actions/setup-node@v3
24+
with:
25+
node-version: "24.13.0"
26+
- run: npm ci
27+
- run: npm run lint:fix
28+
29+
test:
30+
needs: lint
31+
runs-on: ubuntu-latest
32+
steps:
33+
- name: Checkout code
34+
uses: actions/checkout@v4
35+
- name: Setup Node.js
36+
uses: actions/setup-node@v3
37+
with:
38+
node-version: "24.13.0"
39+
- run: npm ci
40+
- run: npm run test:coverage
41+
- name: Upload coverage report
42+
uses: actions/upload-artifact@v4
43+
with:
44+
name: coverage-report
45+
path: coverage
46+
47+
build:
48+
needs: test
49+
runs-on: ubuntu-latest
50+
steps:
51+
- name: Checkout code
52+
uses: actions/checkout@v4
53+
- name: Setup Node.js
54+
uses: actions/setup-node@v3
55+
with:
56+
node-version: "24.13.0"
57+
- run: npm ci
58+
- run: npm run build
59+
60+
deploy:
61+
needs: build
62+
runs-on: ubuntu-latest
63+
if: github.event_name == 'push' && github.ref == 'refs/heads/main' # deploy solo su main
64+
steps:
65+
- name: Checkout code
66+
uses: actions/checkout@v4
67+
- name: Setup Node.js
68+
uses: actions/setup-node@v3
69+
with:
70+
node-version: "24.13.0"
71+
- run: npm ci
72+
- name: Deploy Opis App
73+
uses: JamesIves/github-pages-deploy-action@v4
74+
with:
75+
branch: gh-pages
76+
folder: dist/opis-manager

.github/workflows/tests.yml

Lines changed: 0 additions & 32 deletions
This file was deleted.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"ng": "ng",
88
"prestart": "node scripts/set-env.js",
99
"start": "ng serve",
10-
"build": "ng build",
10+
"build": "node scripts/set-env.js && ng build",
1111
"watch": "ng build --watch --configuration development",
1212
"lint": "ng lint",
1313
"lint:fix": "ng lint --fix",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { AcademicYear } from '@values/years';
2+
3+
export type Means = [number[], number[][]];
4+
export type MeansPerYear = Record<AcademicYear, Means>;

src/app/interfaces/cds.interface.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { Teaching } from './teaching.interface';
2+
import { GraphView } from './graph-config.interface';
3+
import { MeansPerYear } from '@c_types/means-graph.type';
24

35
export interface CDS {
46
id: number;
@@ -13,3 +15,13 @@ export interface CDS {
1315
pesi_domande: any;
1416
insegnamenti: Teaching[];
1517
}
18+
19+
export interface AllCdsInfoResp {
20+
teachings: Teaching[];
21+
coarse: MeansPerYear;
22+
graphs: {
23+
cds_stats: GraphView;
24+
// cds_stats_by_year: ChartData,
25+
// cds_techings: ChartData
26+
};
27+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { ChartData, ChartType } from 'chart.js';
2+
3+
export interface GraphView {
4+
type: ChartType;
5+
data: ChartData;
6+
}

src/app/services/cds/cds.service.ts

Lines changed: 51 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,58 @@
11
import { HttpClient, HttpHeaders } from '@angular/common/http';
2-
import { inject, Injectable, signal } from '@angular/core';
2+
import { inject, Injectable, ResourceRef, signal } from '@angular/core';
33
import { rxResource } from '@angular/core/rxjs-interop';
4+
import { MeansPerYear } from '@c_types/means-graph.type';
45
import { env } from '@env';
5-
import { CDS } from '@interfaces/cds.interface';
6+
import { AllCdsInfoResp, CDS } from '@interfaces/cds.interface';
7+
import { SchedaOpis } from '@interfaces/opis-record.interface';
68
import { Teaching } from '@interfaces/teaching.interface';
79
import { GraphService } from '@services/graph/graph.service';
10+
import { typedKeys } from '@utils/object-helpers.utils';
811
import { DELAY_API_MS } from '@values/delay-api';
912
import { AcademicYear } from '@values/years';
10-
import {
11-
catchError,
12-
delay,
13-
forkJoin,
14-
from,
15-
groupBy,
16-
map,
17-
mergeMap,
18-
Observable,
19-
throwError,
20-
toArray,
21-
} from 'rxjs';
13+
import { catchError, delay, forkJoin, map, Observable, throwError } from 'rxjs';
2214

2315
@Injectable({ providedIn: 'root' })
2416
export class CdsService {
2517
private readonly BASE_URL = env.api_url + '/cds';
2618
private readonly _http = inject(HttpClient);
2719
private readonly _graphService = inject(GraphService);
2820

29-
private vCds: Record<AcademicYear, number[] | null> = {
30-
'2013/2014': null,
31-
'2014/2015': null,
32-
'2015/2016': null,
33-
'2016/2017': null,
34-
'2017/2018': null,
35-
'2018/2019': null,
36-
'2019/2020': null,
37-
'2020/2021': null,
38-
};
39-
4021
readonly cdsSelected = signal<CDS | null>(null);
4122

42-
private formatSchedaOpis(resp: CDS[]): void {
43-
const cdsSchede = resp
44-
.flatMap((cdsCoarse) => cdsCoarse.insegnamenti)
23+
private extractValidSchedeOpis(cdsList: CDS[]): SchedaOpis[] {
24+
return cdsList
25+
.flatMap((cds) => cds.insegnamenti)
4526
.filter((insegnamento) => insegnamento.schedeopis != null)
4627
.flatMap((insegnamento) => insegnamento.schedeopis)
4728
.filter((schedaopis) => schedaopis.domande != null);
29+
}
30+
31+
private groupByYears(schede: SchedaOpis[]): Record<AcademicYear, SchedaOpis[]> {
32+
return schede.reduce(
33+
(acc, scheda) => {
34+
const year = scheda.anno_accademico as AcademicYear;
35+
if (!acc[year]) acc[year] = [];
36+
acc[year].push(scheda);
37+
return acc;
38+
},
39+
{} as Record<AcademicYear, SchedaOpis[]>,
40+
);
41+
}
4842

49-
from(cdsSchede)
50-
.pipe(
51-
groupBy((scheda) => scheda.anno_accademico),
52-
mergeMap((group) => group.pipe(toArray())),
53-
)
54-
.subscribe((schede) => {
55-
const academicYear = schede[0].anno_accademico as AcademicYear;
56-
57-
this.vCds[academicYear] = this._graphService.elaborateFormula(schede)[0];
58-
// debugger
59-
// this.vCds = Object.assign({}, this.vCds); // copy into new object to trigger ngOnChange in child components
60-
// this.nCds[academicYear] = this.graphService.round(schede.map(scheda => scheda.totale_schede)
61-
// .reduce((acc, val) => acc + val) / schede.length);
62-
});
43+
private formatAllYearsCdsStats(resp: CDS[]): MeansPerYear {
44+
const cdsSchede = this.extractValidSchedeOpis(resp);
45+
const schedeByYears = this.groupByYears(cdsSchede);
46+
47+
const vCds = {} as MeansPerYear;
48+
49+
for (const year of typedKeys(schedeByYears)) {
50+
const allSchede = schedeByYears[year];
51+
52+
vCds[year] = this._graphService.elaborateFormulaFor(allSchede);
53+
}
54+
55+
return vCds;
6356
}
6457

6558
private teachingCdsApi(cds: number): Observable<Teaching[]> {
@@ -75,35 +68,37 @@ export class CdsService {
7568
);
7669
}
7770

78-
private coarsePerCdsApi(unictCds: number): Observable<void> {
79-
const url = `${this.BASE_URL}/coarse/${unictCds}/schedeopis`;
71+
private cdsStatsApi(unictCdsId: number): Observable<MeansPerYear> {
72+
const url = `${this.BASE_URL}/coarse/${unictCdsId}/schedeopis`;
8073

8174
return this._http.get<CDS[]>(url).pipe(
8275
map((coarse) => {
83-
if (!coarse) {
84-
throw new Error('Schede OPIS non trovate');
85-
}
86-
return this.formatSchedaOpis(coarse);
76+
if (!coarse) throw new Error('Schede OPIS non trovate');
77+
return this.formatAllYearsCdsStats(coarse);
8778
}),
8879
);
8980
}
9081

91-
public getInfoCds() {
82+
public getInfoCds(): ResourceRef<AllCdsInfoResp | undefined> {
9283
return rxResource({
9384
params: () => this.cdsSelected(),
9485
stream: ({ params }) => {
9586
if (!params?.id || !params?.unict_id) {
9687
return throwError(() => new Error('Id or Unict_id missing!'));
9788
}
9889

99-
const teachings$ = this.teachingCdsApi(params.id);
100-
const coarse$ = this.coarsePerCdsApi(params.unict_id);
101-
102-
return forkJoin({
103-
teaching: teachings$,
104-
coarse: coarse$,
105-
}).pipe(
90+
return forkJoin([this.teachingCdsApi(params.id), this.cdsStatsApi(params.unict_id)]).pipe(
10691
delay(DELAY_API_MS),
92+
map(([teachings, coarse]) => {
93+
const respDTO: AllCdsInfoResp = {
94+
teachings,
95+
coarse,
96+
graphs: {
97+
cds_stats: this._graphService.formatCDSGraph(coarse),
98+
},
99+
};
100+
return respDTO;
101+
}),
107102
catchError((err) => throwError(() => err)),
108103
);
109104
},

0 commit comments

Comments
 (0)