From 6f2432ab6c321c166558e4fa15334ae0b37823f3 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 23 Jan 2024 19:23:17 +0100 Subject: [PATCH 01/29] added test for adding unrelated note --- .../notes-related-to-entity.component.spec.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts index 23bdb8f412..1c2bc16bce 100644 --- a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts +++ b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts @@ -17,6 +17,7 @@ import moment from "moment"; import { ChildSchoolRelation } from "../../children/model/childSchoolRelation"; import { DatabaseEntity } from "../../../core/entity/database-entity.decorator"; import { DatabaseField } from "../../../core/entity/database-field.decorator"; +import { EntityMapperService } from "../../../core/entity/entity-mapper/entity-mapper.service"; describe("NotesRelatedToEntityComponent", () => { let component: NotesRelatedToEntityComponent; @@ -145,4 +146,27 @@ describe("NotesRelatedToEntityComponent", () => { ); expect(component.data).toEqual([n1, n2, n3]); })); + + it("should only add related notes after the initial load", async () => { + const child = new Child(); + const data = [new Note(), new Note()]; + data.forEach((n) => n.addChild(child)); + mockChildrenService.getNotesRelatedTo.and.resolveTo([...data]); + component.entity = child; + + await component.ngOnInit(); + expect(component.data).toEqual(data); + + const relatedNote = new Note(); + relatedNote.addChild(child); + await TestBed.inject(EntityMapperService).save(relatedNote); + const expectedData = jasmine.arrayWithExactContents( + data.concat(relatedNote), + ); + expect(component.data).toEqual(expectedData); + + const unrelatedNote = new Note(); + await TestBed.inject(EntityMapperService).save(unrelatedNote); + expect(component.data).toEqual(expectedData); + }); }); From 821972ac6dc385b7010b168658a51f6426592bdb Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 23 Jan 2024 19:32:41 +0100 Subject: [PATCH 02/29] `loadData` function returns result instead of initializing it and renamed it to `getData` --- .../aser/aser-component/aser.component.ts | 16 +++++++++------- .../health-checkup.component.ts | 16 +++++++++------- .../notes-related-to-entity.component.ts | 4 ++-- .../child-school-overview.component.ts | 4 ++-- .../related-entities.component.ts | 7 ++++--- .../historical-data/historical-data.component.ts | 6 ++---- .../todos-related-to-entity.component.ts | 7 ++----- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/app/child-dev-project/children/aser/aser-component/aser.component.ts b/src/app/child-dev-project/children/aser/aser-component/aser.component.ts index 2ecc12de9f..3165cb3e9c 100644 --- a/src/app/child-dev-project/children/aser/aser-component/aser.component.ts +++ b/src/app/child-dev-project/children/aser/aser-component/aser.component.ts @@ -42,12 +42,14 @@ export class AserComponent extends RelatedEntitiesComponent { super(entityMapper, entityRegistry, screenWidthObserver); } - override async initData() { - this.data = ( - await this.childrenService.getAserResultsOfChild(this.entity.getId()) - ).sort( - (a, b) => - (b.date ? b.date.valueOf() : 0) - (a.date ? a.date.valueOf() : 0), - ); + override getData() { + return this.childrenService + .getAserResultsOfChild(this.entity.getId()) + .then((data) => + data.sort( + (a, b) => + (b.date ? b.date.valueOf() : 0) - (a.date ? a.date.valueOf() : 0), + ), + ); } } diff --git a/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts b/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts index 9212b4a6b8..b45a61283b 100644 --- a/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts +++ b/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts @@ -76,12 +76,14 @@ export class HealthCheckupComponent /** * implements the health check loading from the children service and is called in the onInit() */ - override async initData() { - this.data = ( - await this.childrenService.getHealthChecksOfChild(this.entity.getId()) - ).sort( - (a, b) => - (b.date ? b.date.valueOf() : 0) - (a.date ? a.date.valueOf() : 0), - ); + override getData() { + return this.childrenService + .getHealthChecksOfChild(this.entity.getId()) + .then((data) => + data.sort( + (a, b) => + (b.date ? b.date.valueOf() : 0) - (a.date ? a.date.valueOf() : 0), + ), + ); } } diff --git a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts index d0e0951070..982d013e58 100644 --- a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts +++ b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts @@ -67,8 +67,8 @@ export class NotesRelatedToEntityComponent extends RelatedEntitiesComponent { notes.sort((a, b) => { diff --git a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts index 36d2e72fbd..a6192838c6 100644 --- a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts +++ b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts @@ -92,12 +92,12 @@ export class ChildSchoolOverviewComponent } } - override async initData() { + override getData() { if (!this.mode) { return; } - this.data = await this.childrenService.queryRelationsOf( + return this.childrenService.queryRelationsOf( this.mode, this.entity.getId(false), ); diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index 33c242cf07..c6b14b1cc8 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -77,11 +77,11 @@ export class RelatedEntitiesComponent implements OnInit { } async ngOnInit() { - await this.initData(); + this.data = await this.getData(); this.listenToEntityUpdates(); } - protected async initData() { + protected async getData(): Promise { this.isArray = isArrayProperty(this.entityCtr, this.property); this.filter = { @@ -91,7 +91,7 @@ export class RelatedEntitiesComponent implements OnInit { : this.entity.getId(), }; - this.data = (await this.entityMapper.loadType(this.entityCtr)).filter( + const data = (await this.entityMapper.loadType(this.entityCtr)).filter( (e) => this.isArray ? e[this.property]?.includes(this.entity.getId()) @@ -101,6 +101,7 @@ export class RelatedEntitiesComponent implements OnInit { if (this.showInactive === undefined) { this.showInactive = this.entity.anonymized; } + return data; } protected listenToEntityUpdates() { diff --git a/src/app/features/historical-data/historical-data/historical-data.component.ts b/src/app/features/historical-data/historical-data/historical-data.component.ts index faf99d28a1..2023226daa 100644 --- a/src/app/features/historical-data/historical-data/historical-data.component.ts +++ b/src/app/features/historical-data/historical-data/historical-data.component.ts @@ -47,10 +47,8 @@ export class HistoricalDataComponent super(entityMapper, entityRegistry, screenWidthObserver); } - override async initData() { - this.data = await this.historicalDataService.getHistoricalDataFor( - this.entity.getId(), - ); + override getData() { + return this.historicalDataService.getHistoricalDataFor(this.entity.getId()); } public getNewEntryFunction(): () => HistoricalEntityData { diff --git a/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.ts b/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.ts index cc585d69d2..32b9fbf087 100644 --- a/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.ts +++ b/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.ts @@ -65,11 +65,8 @@ export class TodosRelatedToEntityComponent extends RelatedEntitiesComponent { + override getData() { + const entityId = this.entity.getId(true); return this.dbIndexingService.queryIndexDocs( Todo, "todo_index/by_" + this.referenceProperty, From 454d31379cc49018b33bd8d2151ef9cf0334fa05 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 23 Jan 2024 20:33:35 +0100 Subject: [PATCH 03/29] using same filter logic to initialize data and pass to entities table --- .../aser/aser-component/aser.component.ts | 4 ++- .../health-checkup.component.ts | 4 ++- .../notes-related-to-entity.component.ts | 6 ++-- .../child-school-overview.component.spec.ts | 3 +- .../child-school-overview.component.ts | 8 ++--- .../related-entities.component.spec.ts | 27 ++++++++++++--- .../related-entities.component.ts | 33 +++++++++++-------- .../historical-data.component.ts | 4 ++- .../todos-related-to-entity.component.ts | 6 ++-- 9 files changed, 63 insertions(+), 32 deletions(-) diff --git a/src/app/child-dev-project/children/aser/aser-component/aser.component.ts b/src/app/child-dev-project/children/aser/aser-component/aser.component.ts index 3165cb3e9c..4662450886 100644 --- a/src/app/child-dev-project/children/aser/aser-component/aser.component.ts +++ b/src/app/child-dev-project/children/aser/aser-component/aser.component.ts @@ -10,6 +10,7 @@ import { RelatedEntitiesComponent } from "../../../../core/entity-details/relate import { EntityMapperService } from "../../../../core/entity/entity-mapper/entity-mapper.service"; import { EntityRegistry } from "../../../../core/entity/database-entity.decorator"; import { ScreenWidthObserver } from "../../../../utils/media/screen-size-observer.service"; +import { FilterService } from "../../../../core/filter/filter.service"; @DynamicComponent("Aser") @Component({ @@ -38,8 +39,9 @@ export class AserComponent extends RelatedEntitiesComponent { entityMapper: EntityMapperService, entityRegistry: EntityRegistry, screenWidthObserver: ScreenWidthObserver, + filterService: FilterService, ) { - super(entityMapper, entityRegistry, screenWidthObserver); + super(entityMapper, entityRegistry, screenWidthObserver, filterService); } override getData() { diff --git a/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts b/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts index b45a61283b..ffa7e607f1 100644 --- a/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts +++ b/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts @@ -9,6 +9,7 @@ import { RelatedEntitiesComponent } from "../../../../core/entity-details/relate import { EntityMapperService } from "../../../../core/entity/entity-mapper/entity-mapper.service"; import { EntityRegistry } from "../../../../core/entity/database-entity.decorator"; import { ScreenWidthObserver } from "../../../../utils/media/screen-size-observer.service"; +import { FilterService } from "../../../../core/filter/filter.service"; @DynamicComponent("HealthCheckup") @Component({ @@ -49,8 +50,9 @@ export class HealthCheckupComponent entityMapper: EntityMapperService, entityRegistry: EntityRegistry, screenWidthObserver: ScreenWidthObserver, + filterService: FilterService, ) { - super(entityMapper, entityRegistry, screenWidthObserver); + super(entityMapper, entityRegistry, screenWidthObserver, filterService); } private getBMI(healthCheck: HealthCheck): string { diff --git a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts index 982d013e58..145619edac 100644 --- a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts +++ b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts @@ -51,12 +51,12 @@ export class NotesRelatedToEntityComponent extends RelatedEntitiesComponent { }); it("should create a relation with the child ID", async () => { + const child = new Child(); const existingRelation = new ChildSchoolRelation(); + existingRelation.childId = child.getId(); existingRelation.start = moment().subtract(1, "year").toDate(); existingRelation.end = moment().subtract(1, "week").toDate(); mockChildrenService.queryRelationsOf.and.resolveTo([existingRelation]); - const child = new Child(); component.entity = child; await component.ngOnInit(); diff --git a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts index a6192838c6..5f2b940dcc 100644 --- a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts +++ b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts @@ -16,6 +16,7 @@ import { EntitiesTableComponent } from "../../../core/common-components/entities import { EntityMapperService } from "../../../core/entity/entity-mapper/entity-mapper.service"; import { EntityRegistry } from "../../../core/entity/database-entity.decorator"; import { ScreenWidthObserver } from "../../../utils/media/screen-size-observer.service"; +import { FilterService } from "../../../core/filter/filter.service"; // TODO: once schema-generated indices are available (#262), remove this component and use its generic super class directly @DynamicComponent("ChildSchoolOverview") @@ -52,8 +53,9 @@ export class ChildSchoolOverviewComponent entityMapper: EntityMapperService, entityRegistry: EntityRegistry, screenWidthObserver: ScreenWidthObserver, + filterService: FilterService, ) { - super(entityMapper, entityRegistry, screenWidthObserver); + super(entityMapper, entityRegistry, screenWidthObserver, filterService); this.columns = [ { id: "childId" }, // schoolId/childId replaced dynamically during init @@ -93,10 +95,6 @@ export class ChildSchoolOverviewComponent } override getData() { - if (!this.mode) { - return; - } - return this.childrenService.queryRelationsOf( this.mode, this.entity.getId(false), diff --git a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts index b2c98f7a22..9afacd2647 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts @@ -41,7 +41,7 @@ describe("RelatedEntitiesComponent", () => { expect(component).toBeTruthy(); }); - it("should load only the entities which are linked with the passed one", async () => { + it("should only show the entities which are linked with the passed one", async () => { const c1 = new Child(); const c2 = new Child(); const r1 = new ChildSchoolRelation(); @@ -53,17 +53,36 @@ describe("RelatedEntitiesComponent", () => { const entityMapper = TestBed.inject(EntityMapperService); await entityMapper.saveAll([c1, c2, r1, r2, r3]); const columns = ["start", "end", "schoolId"]; - const filter = { start: { $exists: true } } as any; component.entity = c1; component.entityType = ChildSchoolRelation.ENTITY_TYPE; component.property = "childId"; component.columns = columns; - component.filter = filter; await component.ngOnInit(); expect(component.data).toEqual([r1, r2]); - expect(component.filter).toEqual({ ...filter, childId: c1.getId() }); + }); + + it("should only show the entities which pass the filter", async () => { + const child = new Child(); + const r1 = new ChildSchoolRelation(); + r1.start = new Date(); + r1.childId = child.getId(); + const r2 = new ChildSchoolRelation(); + r2.childId = child.getId(); + const r3 = new ChildSchoolRelation(); + const entityMapper = TestBed.inject(EntityMapperService); + await entityMapper.saveAll([child, r1, r2, r3]); + const filter = { start: { $exists: true } } as any; + + component.entity = child; + component.entityType = ChildSchoolRelation.ENTITY_TYPE; + component.property = "childId"; + component.filter = filter; + await component.ngOnInit(); + + expect(component.data).toEqual([r1]); + expect(component.filter).toEqual({ ...filter, childId: child.getId() }); }); it("should ignore entities of the related type where the matching field is undefined instead of array", async () => { diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index c6b14b1cc8..a2b89b1f9b 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -17,6 +17,7 @@ import { toFormFieldConfig, } from "../../common-components/entity-form/FormConfig"; import { DataFilter } from "../../filter/filters/filters"; +import { FilterService } from "../../filter/filter.service"; /** * Load and display a list of entity subrecords (entities related to the current entity details view). @@ -69,6 +70,7 @@ export class RelatedEntitiesComponent implements OnInit { protected entityMapper: EntityMapperService, private entityRegistry: EntityRegistry, private screenWidthObserver: ScreenWidthObserver, + protected filterService: FilterService, ) { this.screenWidthObserver .shared() @@ -77,26 +79,16 @@ export class RelatedEntitiesComponent implements OnInit { } async ngOnInit() { - this.data = await this.getData(); + const data = await this.getData(); + this.filter = this.initFilter(); + this.data = data.filter(this.filterService.getFilterPredicate(this.filter)); this.listenToEntityUpdates(); } protected async getData(): Promise { this.isArray = isArrayProperty(this.entityCtr, this.property); - this.filter = { - ...this.filter, - [this.property]: this.isArray - ? { $elemMatch: { $eq: this.entity.getId() } } - : this.entity.getId(), - }; - - const data = (await this.entityMapper.loadType(this.entityCtr)).filter( - (e) => - this.isArray - ? e[this.property]?.includes(this.entity.getId()) - : e[this.property] === this.entity.getId(), - ); + const data = await this.entityMapper.loadType(this.entityCtr); if (this.showInactive === undefined) { this.showInactive = this.entity.anonymized; @@ -104,6 +96,19 @@ export class RelatedEntitiesComponent implements OnInit { return data; } + initFilter(): DataFilter { + const filter: DataFilter = { ...this.filter }; + + if (this.property) { + // only show related entities + filter[this.property] = this.isArray + ? { $elemMatch: { $eq: this.entity.getId() } } + : this.entity.getId(); + } + + return filter; + } + protected listenToEntityUpdates() { this.entityMapper .receiveUpdates(this.entityCtr) diff --git a/src/app/features/historical-data/historical-data/historical-data.component.ts b/src/app/features/historical-data/historical-data/historical-data.component.ts index 2023226daa..cc75f4ebc2 100644 --- a/src/app/features/historical-data/historical-data/historical-data.component.ts +++ b/src/app/features/historical-data/historical-data/historical-data.component.ts @@ -9,6 +9,7 @@ import { EntityMapperService } from "../../../core/entity/entity-mapper/entity-m import { EntityRegistry } from "../../../core/entity/database-entity.decorator"; import { ScreenWidthObserver } from "../../../utils/media/screen-size-observer.service"; import { FormFieldConfig } from "../../../core/common-components/entity-form/FormConfig"; +import { FilterService } from "../../../core/filter/filter.service"; /** * A general component that can be included on a entity details page through the config. @@ -43,8 +44,9 @@ export class HistoricalDataComponent entityMapper: EntityMapperService, entityRegistry: EntityRegistry, screenWidthObserver: ScreenWidthObserver, + filterService: FilterService, ) { - super(entityMapper, entityRegistry, screenWidthObserver); + super(entityMapper, entityRegistry, screenWidthObserver, filterService); } override getData() { diff --git a/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.ts b/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.ts index 32b9fbf087..2c64d477b0 100644 --- a/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.ts +++ b/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.ts @@ -13,6 +13,7 @@ import { RelatedEntitiesComponent } from "../../../core/entity-details/related-e import { EntityMapperService } from "../../../core/entity/entity-mapper/entity-mapper.service"; import { EntityRegistry } from "../../../core/entity/database-entity.decorator"; import { ScreenWidthObserver } from "../../../utils/media/screen-size-observer.service"; +import { FilterService } from "../../../core/filter/filter.service"; @DynamicComponent("TodosRelatedToEntity") @Component({ @@ -53,9 +54,10 @@ export class TodosRelatedToEntityComponent extends RelatedEntitiesComponent Date: Tue, 23 Jan 2024 20:41:29 +0100 Subject: [PATCH 04/29] some code cleanups --- .../related-entities.component.spec.ts | 13 +++++++------ .../related-entities.component.ts | 17 +++++++++-------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts index 9afacd2647..b8b296882b 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts @@ -87,19 +87,20 @@ describe("RelatedEntitiesComponent", () => { it("should ignore entities of the related type where the matching field is undefined instead of array", async () => { const c1 = new Child(); - const r1 = new Note(); - r1.children = [c1.getId()]; - const rEmpty = new Note(); - delete rEmpty.children; // some entity types will not have a default empty array + const n1 = new Note(); + n1.children = [c1.getId()]; + const nEmpty = new Note(); + delete nEmpty.children; // some entity types will not have a default empty array const entityMapper = TestBed.inject(EntityMapperService); - await entityMapper.saveAll([c1, r1, rEmpty]); + await entityMapper.saveAll([c1, n1, nEmpty]); component.entity = c1; component.entityType = Note.ENTITY_TYPE; component.property = "children"; + component.filter = {}; // reset filter await component.ngOnInit(); - expect(component.data).toEqual([r1]); + expect(component.data).toEqual([n1]); }); it("should create a new entity that references the related one", async () => { diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index a2b89b1f9b..328cc0a299 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -80,20 +80,20 @@ export class RelatedEntitiesComponent implements OnInit { async ngOnInit() { const data = await this.getData(); + this.filter = this.initFilter(); this.data = data.filter(this.filterService.getFilterPredicate(this.filter)); - this.listenToEntityUpdates(); - } - - protected async getData(): Promise { - this.isArray = isArrayProperty(this.entityCtr, this.property); - - const data = await this.entityMapper.loadType(this.entityCtr); if (this.showInactive === undefined) { + // show all related docs when visiting an archived entity this.showInactive = this.entity.anonymized; } - return data; + + this.listenToEntityUpdates(); + } + + protected getData(): Promise { + return this.entityMapper.loadType(this.entityCtr); } initFilter(): DataFilter { @@ -101,6 +101,7 @@ export class RelatedEntitiesComponent implements OnInit { if (this.property) { // only show related entities + this.isArray = isArrayProperty(this.entityCtr, this.property); filter[this.property] = this.isArray ? { $elemMatch: { $eq: this.entity.getId() } } : this.entity.getId(); From b90f7a9022ada364294b936ab7f457f68e100b75 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 23 Jan 2024 20:58:37 +0100 Subject: [PATCH 05/29] added related property for related notes --- .../children/children.service.ts | 2 +- .../notes-related-to-entity.component.spec.ts | 19 ++++++------ .../notes-related-to-entity.component.ts | 3 ++ .../related-entities.component.spec.ts | 31 +++++++++++++++++++ .../related-entities.component.ts | 1 + 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/app/child-dev-project/children/children.service.ts b/src/app/child-dev-project/children/children.service.ts index 89a281a693..ab0f9015e2 100644 --- a/src/app/child-dev-project/children/children.service.ts +++ b/src/app/child-dev-project/children/children.service.ts @@ -143,7 +143,7 @@ export class ChildrenService { ); } - private inferNoteLinkPropertyFromEntityType(entityId: string): string { + inferNoteLinkPropertyFromEntityType(entityId: string): string { const entityType = Entity.extractTypeFromId(entityId); switch (entityType) { case Child.ENTITY_TYPE: diff --git a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts index 1c2bc16bce..c531b586c3 100644 --- a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts +++ b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts @@ -23,14 +23,9 @@ describe("NotesRelatedToEntityComponent", () => { let component: NotesRelatedToEntityComponent; let fixture: ComponentFixture; - let mockChildrenService: jasmine.SpyObj; - beforeEach(waitForAsync(() => { - mockChildrenService = jasmine.createSpyObj(["getNotesRelatedTo"]); - mockChildrenService.getNotesRelatedTo.and.resolveTo([]); TestBed.configureTestingModule({ imports: [NotesRelatedToEntityComponent, MockedTestingModule.withState()], - providers: [{ provide: ChildrenService, useValue: mockChildrenService }], }).compileComponents(); })); @@ -129,29 +124,33 @@ describe("NotesRelatedToEntityComponent", () => { }); it("should sort notes by date", fakeAsync(() => { + const child = new Child(); // No date should come first const n1 = new Note(); const n2 = new Note(); n2.date = moment().subtract(1, "day").toDate(); const n3 = new Note(); n3.date = moment().subtract(2, "days").toDate(); - mockChildrenService.getNotesRelatedTo.and.resolveTo([n3, n2, n1]); + [n3, n2, n1].forEach((n) => n.addChild(child)); + const childrenService = TestBed.inject(ChildrenService); + spyOn(childrenService, "getNotesRelatedTo").and.resolveTo([n3, n2, n1]); - component.entity = new Child(); + component.entity = child; component.ngOnInit(); tick(); - expect(mockChildrenService.getNotesRelatedTo).toHaveBeenCalledWith( + expect(childrenService.getNotesRelatedTo).toHaveBeenCalledWith( component.entity.getId(true), ); expect(component.data).toEqual([n1, n2, n3]); })); - it("should only add related notes after the initial load", async () => { + xit("should only add related notes after the initial load", async () => { const child = new Child(); const data = [new Note(), new Note()]; data.forEach((n) => n.addChild(child)); - mockChildrenService.getNotesRelatedTo.and.resolveTo([...data]); + const childrenService = TestBed.inject(ChildrenService); + spyOn(childrenService, "getNotesRelatedTo").and.resolveTo([...data]); component.entity = child; await component.ngOnInit(); diff --git a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts index 145619edac..da4176c5ca 100644 --- a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts +++ b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts @@ -64,6 +64,9 @@ export class NotesRelatedToEntityComponent extends RelatedEntitiesComponent note?.getColorForId(this.entity.getId()); } + this.property = this.childrenService.inferNoteLinkPropertyFromEntityType( + this.entity.getId(true), + ); return super.ngOnInit(); } diff --git a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts index b8b296882b..05749c495d 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts @@ -14,6 +14,9 @@ import { Note } from "../../../child-dev-project/notes/model/note"; import { Subject } from "rxjs"; import { UpdatedEntity } from "../../entity/model/entity-update"; import { Entity } from "../../entity/model/entity"; +import moment from "moment"; +import { DatabaseEntity } from "../../entity/database-entity.decorator"; +import { EntityDatatype } from "../../basic-datatypes/entity/entity.datatype"; describe("RelatedEntitiesComponent", () => { let component: RelatedEntitiesComponent; @@ -131,6 +134,34 @@ describe("RelatedEntitiesComponent", () => { expect(component.data).toEqual([entity]); })); + xit("should only add entities which pass the filter object", fakeAsync(() => { + const entityUpdates = new Subject>(); + const entityMapper = TestBed.inject(EntityMapperService); + spyOn(entityMapper, "receiveUpdates").and.returnValue(entityUpdates); + component.ngOnInit(); + // only show active relations + component.filter = { isActive: true }; + tick(); + + // active -> add + const activeRelation = new ChildSchoolRelation(); + activeRelation.childId = component.entity.getId(); + activeRelation.start = moment().subtract(1, "week").toDate(); + entityUpdates.next({ entity: activeRelation, type: "new" }); + tick(); + expect(component.data).toEqual([activeRelation]); + + // inactive -> don't add + const inactiveRelation = new ChildSchoolRelation(); + inactiveRelation.childId = component.entity.getId(); + inactiveRelation.start = moment().subtract(1, "week").toDate(); + inactiveRelation.end = moment().subtract(2, "days").toDate(); + entityUpdates.next({ entity: inactiveRelation, type: "new" }); + tick(); + // TODO do we actually need to filter the data or is it sufficient to create the correct filter and let the entities table hide it? + expect(component.data).toEqual([activeRelation]); + })); + it("should remove an entity from the table when it has been deleted", fakeAsync(() => { const entityUpdates = new Subject>(); const entityMapper = TestBed.inject(EntityMapperService); diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index 328cc0a299..f06e84b93f 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -82,6 +82,7 @@ export class RelatedEntitiesComponent implements OnInit { const data = await this.getData(); this.filter = this.initFilter(); + // TODO not really required as the entities table anyway hides the not-passing ones this.data = data.filter(this.filterService.getFilterPredicate(this.filter)); if (this.showInactive === undefined) { From 3fa13deb266b6b80b396bfd593661e8e2513d838 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 23 Jan 2024 21:46:24 +0100 Subject: [PATCH 06/29] inferring related property --- .../related-entities.component.spec.ts | 40 +++++++++++++++++++ .../related-entities.component.ts | 20 +++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts index 05749c495d..6b0efbc2b5 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts @@ -17,6 +17,8 @@ import { Entity } from "../../entity/model/entity"; import moment from "moment"; import { DatabaseEntity } from "../../entity/database-entity.decorator"; import { EntityDatatype } from "../../basic-datatypes/entity/entity.datatype"; +import { EntityArrayDatatype } from "../../basic-datatypes/entity-array/entity-array.datatype"; +import { School } from "../../../child-dev-project/schools/model/school"; describe("RelatedEntitiesComponent", () => { let component: RelatedEntitiesComponent; @@ -176,4 +178,42 @@ describe("RelatedEntitiesComponent", () => { expect(component.data).toEqual([]); })); + + it("should infer the property based on related entity", async () => { + @DatabaseEntity("PropTest") + class PropTest extends Entity {} + component.entityType = PropTest.ENTITY_TYPE; + + PropTest.schema.set("singleRelation", { + dataType: EntityDatatype.dataType, + additional: Child.ENTITY_TYPE, + }); + component.entity = new Child(); + await component.ngOnInit(); + expect(component.property).toEqual("singleRelation"); + + PropTest.schema.set("arrayRelation", { + dataType: EntityArrayDatatype.dataType, + additional: School.ENTITY_TYPE, + }); + component.entity = new School(); + await component.ngOnInit(); + expect(component.property).toEqual("arrayRelation"); + + PropTest.schema.set("multiTypeRelation", { + dataType: EntityArrayDatatype.dataType, + additional: [ChildSchoolRelation.ENTITY_TYPE, School.ENTITY_TYPE], + }); + component.entity = new ChildSchoolRelation(); + await component.ngOnInit(); + expect(component.property).toEqual("multiTypeRelation"); + + // Now with 2 relations ("arrayRelation" and "multiTypeRelation") + // TODO (how) do we support this case? + component.entity = new School(); + await component.ngOnInit(); + expect(component.property).toEqual( + jasmine.arrayWithExactContents(["multiTypeRelation", "arrayRelation"]), + ); + }); }); diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index f06e84b93f..53cf2c689a 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -18,6 +18,8 @@ import { } from "../../common-components/entity-form/FormConfig"; import { DataFilter } from "../../filter/filters/filters"; import { FilterService } from "../../filter/filter.service"; +import { EntityDatatype } from "../../basic-datatypes/entity/entity.datatype"; +import { EntityArrayDatatype } from "../../basic-datatypes/entity-array/entity-array.datatype"; /** * Load and display a list of entity subrecords (entities related to the current entity details view). @@ -81,6 +83,7 @@ export class RelatedEntitiesComponent implements OnInit { async ngOnInit() { const data = await this.getData(); + this.property = this.getProperty() as any; this.filter = this.initFilter(); // TODO not really required as the entities table anyway hides the not-passing ones this.data = data.filter(this.filterService.getFilterPredicate(this.filter)); @@ -97,7 +100,22 @@ export class RelatedEntitiesComponent implements OnInit { return this.entityMapper.loadType(this.entityCtr); } - initFilter(): DataFilter { + protected getProperty(): string | string[] { + const relType = this.entity.getType(); + const found = [...this.entityCtr.schema].filter(([prop, schema]) => { + const additional = schema.additional; + switch (schema.dataType) { + case EntityDatatype.dataType: + case EntityArrayDatatype.dataType: + return Array.isArray(additional) + ? additional.includes(relType) + : additional === relType; + } + }); + return found.length === 1 ? found[0][0] : found.map(([key]) => key); + } + + protected initFilter(): DataFilter { const filter: DataFilter = { ...this.filter }; if (this.property) { From 473d6b4411f9aaea07e96cb9c4607a85275ae4a3 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 23 Jan 2024 22:09:08 +0100 Subject: [PATCH 07/29] added multi property support --- .../model/educational-material.ts | 8 +++- ...related-entities-with-summary.component.ts | 4 +- .../related-entities.component.spec.ts | 42 +++++++++++++++++++ .../related-entities.component.ts | 33 ++++++++++----- 4 files changed, 75 insertions(+), 12 deletions(-) diff --git a/src/app/child-dev-project/children/educational-material/model/educational-material.ts b/src/app/child-dev-project/children/educational-material/model/educational-material.ts index ffdc8e7510..c90bbdb3ef 100644 --- a/src/app/child-dev-project/children/educational-material/model/educational-material.ts +++ b/src/app/child-dev-project/children/educational-material/model/educational-material.ts @@ -19,6 +19,8 @@ import { Entity } from "../../../../core/entity/model/entity"; import { DatabaseEntity } from "../../../../core/entity/database-entity.decorator"; import { DatabaseField } from "../../../../core/entity/database-field.decorator"; import { ConfigurableEnumValue } from "../../../../core/basic-datatypes/configurable-enum/configurable-enum.interface"; +import { EntityDatatype } from "../../../../core/basic-datatypes/entity/entity.datatype"; +import { Child } from "../../model/child"; @DatabaseEntity("EducationalMaterial") export class EducationalMaterial extends Entity { @@ -26,7 +28,11 @@ export class EducationalMaterial extends Entity { return Object.assign(new EducationalMaterial(), params); } - @DatabaseField() child: string; // id of Child entity + @DatabaseField({ + dataType: EntityDatatype.dataType, + additional: Child.ENTITY_TYPE, + }) + child: string; // id of Child entity @DatabaseField({ label: $localize`:Date on which the material has been borrowed:Date`, }) diff --git a/src/app/core/entity-details/related-entities-with-summary/related-entities-with-summary.component.ts b/src/app/core/entity-details/related-entities-with-summary/related-entities-with-summary.component.ts index bc0cc73248..52e08a3ebc 100644 --- a/src/app/core/entity-details/related-entities-with-summary/related-entities-with-summary.component.ts +++ b/src/app/core/entity-details/related-entities-with-summary/related-entities-with-summary.component.ts @@ -47,11 +47,13 @@ export class RelatedEntitiesWithSummaryComponent untilDestroyed(this), filter( ({ entity, type }) => - type === "remove" || entity[this.property] === this.entity.getId(), + type === "remove" || + entity[this.property as string] === this.entity.getId(), ), ) .subscribe((update) => { this.data = applyUpdate(this.data, update, false); + // TODO needs to apply to filtered data this.updateSummary(); }); } diff --git a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts index 6b0efbc2b5..b54607a33d 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts @@ -19,6 +19,8 @@ import { DatabaseEntity } from "../../entity/database-entity.decorator"; import { EntityDatatype } from "../../basic-datatypes/entity/entity.datatype"; import { EntityArrayDatatype } from "../../basic-datatypes/entity-array/entity-array.datatype"; import { School } from "../../../child-dev-project/schools/model/school"; +import { DatabaseField } from "../../entity/database-field.decorator"; +import { expectEntitiesToMatch } from "../../../utils/expect-entity-data.spec"; describe("RelatedEntitiesComponent", () => { let component: RelatedEntitiesComponent; @@ -179,6 +181,46 @@ describe("RelatedEntitiesComponent", () => { expect(component.data).toEqual([]); })); + it("should support multiple properties", async () => { + @DatabaseEntity("MultiPropTest") + class MultiPropTest extends Entity { + @DatabaseField({ + dataType: EntityDatatype.dataType, + additional: Child.ENTITY_TYPE, + }) + singleChild: string; + @DatabaseField({ + dataType: EntityArrayDatatype.dataType, + additional: [Child.ENTITY_TYPE, School.ENTITY_TYPE], + }) + multiEntities: string; + } + + const child = new Child(); + component.entity = child; + component.entityType = MultiPropTest.ENTITY_TYPE; + component.filter = {}; + + await component.ngOnInit(); + + expect(component.property).toEqual( + jasmine.arrayWithExactContents(["singleChild", "multiEntities"]), + ); + // filter matching relations at any of the available props + expect(component.filter).toEqual({ + $or: [ + { singleChild: child.getId() }, + { multiEntities: { $elemMatch: { $eq: child.getId() } } }, + ], + }); + // no special properties set when creating a new entity + expectEntitiesToMatch( + [component.createNewRecordFactory()()], + [new MultiPropTest()], + true, + ); + }); + it("should infer the property based on related entity", async () => { @DatabaseEntity("PropTest") class PropTest extends Entity {} diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index 53cf2c689a..7deb23ac42 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -3,7 +3,6 @@ import { DynamicComponent } from "../../config/dynamic-components/dynamic-compon import { EntityMapperService } from "../../entity/entity-mapper/entity-mapper.service"; import { Entity, EntityConstructor } from "../../entity/model/entity"; import { EntityRegistry } from "../../entity/database-entity.decorator"; -import { isArrayProperty } from "../../basic-datatypes/datatype-utils"; import { EntitiesTableComponent } from "../../common-components/entities-table/entities-table.component"; import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; import { applyUpdate } from "../../entity/model/entity-update"; @@ -20,6 +19,7 @@ import { DataFilter } from "../../filter/filters/filters"; import { FilterService } from "../../filter/filter.service"; import { EntityDatatype } from "../../basic-datatypes/entity/entity.datatype"; import { EntityArrayDatatype } from "../../basic-datatypes/entity-array/entity-array.datatype"; +import { isArrayProperty } from "../../basic-datatypes/datatype-utils"; /** * Load and display a list of entity subrecords (entities related to the current entity details view). @@ -45,7 +45,7 @@ export class RelatedEntitiesComponent implements OnInit { * property name of the related entities (type given in this.entityType) that holds the entity id * to be matched with the id of the current main entity (given in this.entity) */ - @Input() property: string; + @Input() property: string | string[]; @Input() public set columns(value: ColumnConfig[]) { @@ -83,7 +83,7 @@ export class RelatedEntitiesComponent implements OnInit { async ngOnInit() { const data = await this.getData(); - this.property = this.getProperty() as any; + this.property = this.getProperty(); this.filter = this.initFilter(); // TODO not really required as the entities table anyway hides the not-passing ones this.data = data.filter(this.filterService.getFilterPredicate(this.filter)); @@ -120,15 +120,26 @@ export class RelatedEntitiesComponent implements OnInit { if (this.property) { // only show related entities - this.isArray = isArrayProperty(this.entityCtr, this.property); - filter[this.property] = this.isArray - ? { $elemMatch: { $eq: this.entity.getId() } } - : this.entity.getId(); + if (typeof this.property === "string") { + Object.assign(filter, this.getFilterForProperty(this.property)); + } else if (this.property.length > 0) { + filter["$or"] = this.property.map((prop) => + this.getFilterForProperty(prop), + ); + } } return filter; } + private getFilterForProperty(property: string) { + const isArray = isArrayProperty(this.entityCtr, property); + const filter = isArray + ? { $elemMatch: { $eq: this.entity.getId() } } + : this.entity.getId(); + return { [property]: filter }; + } + protected listenToEntityUpdates() { this.entityMapper .receiveUpdates(this.entityCtr) @@ -142,9 +153,11 @@ export class RelatedEntitiesComponent implements OnInit { // TODO has a similar purpose like FilterService.alignEntityWithFilter return () => { const rec = new this.entityCtr(); - rec[this.property] = this.isArray - ? [this.entity.getId()] - : this.entity.getId(); + if (!Array.isArray(this.property)) { + rec[this.property] = this.isArray + ? [this.entity.getId()] + : this.entity.getId(); + } return rec; }; } From e5e4ebe7c4f44caf5b7928f024b85e4109afcaf1 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 23 Jan 2024 22:51:14 +0100 Subject: [PATCH 08/29] removed overwrites of `property` --- .../activities-overview.component.ts | 1 - .../aser/aser-component/aser.component.ts | 1 - .../children/children.service.ts | 2 +- .../health-checkup.component.ts | 1 - .../notes-related-to-entity.component.spec.ts | 9 ++--- .../notes-related-to-entity.component.ts | 3 -- .../child-school-overview.component.spec.ts | 4 +-- .../child-school-overview.component.ts | 33 +++++-------------- ...ed-entities-with-summary.component.spec.ts | 1 - .../related-entities.component.spec.ts | 5 --- .../related-entities.component.ts | 4 +-- ...ted-time-period-entities.component.spec.ts | 3 -- .../historical-data.component.spec.ts | 9 ++--- .../historical-data.component.ts | 9 ----- 14 files changed, 22 insertions(+), 63 deletions(-) diff --git a/src/app/child-dev-project/attendance/activities-overview/activities-overview.component.ts b/src/app/child-dev-project/attendance/activities-overview/activities-overview.component.ts index e052eefc74..23d06654a3 100644 --- a/src/app/child-dev-project/attendance/activities-overview/activities-overview.component.ts +++ b/src/app/child-dev-project/attendance/activities-overview/activities-overview.component.ts @@ -21,7 +21,6 @@ export class ActivitiesOverviewComponent implements OnInit { entityCtr = RecurringActivity; - property = "linkedGroups"; titleColumn: FormFieldConfig = { id: "title", diff --git a/src/app/child-dev-project/children/aser/aser-component/aser.component.ts b/src/app/child-dev-project/children/aser/aser-component/aser.component.ts index 4662450886..8f06b5cc60 100644 --- a/src/app/child-dev-project/children/aser/aser-component/aser.component.ts +++ b/src/app/child-dev-project/children/aser/aser-component/aser.component.ts @@ -22,7 +22,6 @@ import { FilterService } from "../../../../core/filter/filter.service"; }) export class AserComponent extends RelatedEntitiesComponent { @Input() entity: Child; - property = "child"; entityCtr = Aser; override _columns: FormFieldConfig[] = [ diff --git a/src/app/child-dev-project/children/children.service.ts b/src/app/child-dev-project/children/children.service.ts index ab0f9015e2..89a281a693 100644 --- a/src/app/child-dev-project/children/children.service.ts +++ b/src/app/child-dev-project/children/children.service.ts @@ -143,7 +143,7 @@ export class ChildrenService { ); } - inferNoteLinkPropertyFromEntityType(entityId: string): string { + private inferNoteLinkPropertyFromEntityType(entityId: string): string { const entityType = Entity.extractTypeFromId(entityId); switch (entityType) { case Child.ENTITY_TYPE: diff --git a/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts b/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts index ffa7e607f1..737543ef6d 100644 --- a/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts +++ b/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts @@ -24,7 +24,6 @@ export class HealthCheckupComponent implements OnInit { @Input() entity: Child; - property = "child"; entityCtr = HealthCheck; /** diff --git a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts index c531b586c3..55254fd003 100644 --- a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts +++ b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts @@ -29,12 +29,12 @@ describe("NotesRelatedToEntityComponent", () => { }).compileComponents(); })); - beforeEach(async () => { + beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(NotesRelatedToEntityComponent); component = fixture.componentInstance; component.entity = new Child("1"); fixture.detectChanges(); - }); + })); it("should create", () => { expect(component).toBeTruthy(); @@ -145,7 +145,7 @@ describe("NotesRelatedToEntityComponent", () => { expect(component.data).toEqual([n1, n2, n3]); })); - xit("should only add related notes after the initial load", async () => { + it("should only add related notes after the initial load", async () => { const child = new Child(); const data = [new Note(), new Note()]; data.forEach((n) => n.addChild(child)); @@ -166,6 +166,7 @@ describe("NotesRelatedToEntityComponent", () => { const unrelatedNote = new Note(); await TestBed.inject(EntityMapperService).save(unrelatedNote); - expect(component.data).toEqual(expectedData); + // TODO is actually filtering data necessary here? + // expect(component.data).toEqual(expectedData); }); }); diff --git a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts index da4176c5ca..145619edac 100644 --- a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts +++ b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts @@ -64,9 +64,6 @@ export class NotesRelatedToEntityComponent extends RelatedEntitiesComponent note?.getColorForId(this.entity.getId()); } - this.property = this.childrenService.inferNoteLinkPropertyFromEntityType( - this.entity.getId(true), - ); return super.ngOnInit(); } diff --git a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.spec.ts b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.spec.ts index ff941d910c..d357dc4f0c 100644 --- a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.spec.ts +++ b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.spec.ts @@ -83,9 +83,9 @@ describe("ChildSchoolOverviewComponent", () => { ).toBeTrue(); }); - it("should create a relation with the school ID", () => { + it("should create a relation with the school ID", async () => { component.entity = new School("testID"); - component.ngOnInit(); + await component.ngOnInit(); const newRelation = component.generateNewRecordFactory()(); diff --git a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts index 5f2b940dcc..f5836defae 100644 --- a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts +++ b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts @@ -1,10 +1,7 @@ import { Component, Input, OnInit } from "@angular/core"; import { DynamicComponent } from "../../../core/config/dynamic-components/dynamic-component.decorator"; -import { Child } from "../../children/model/child"; -import { School } from "../model/school"; import { ChildSchoolRelation } from "../../children/model/childSchoolRelation"; import { ChildrenService } from "../../children/children.service"; -import { Entity } from "../../../core/entity/model/entity"; import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; import { MatSlideToggleModule } from "@angular/material/slide-toggle"; import { FormsModule } from "@angular/forms"; @@ -66,22 +63,17 @@ export class ChildSchoolOverviewComponent ]; } - async ngOnInit() { - this.mode = this.inferMode(this.entity); - this.switchRelatedEntityColumnForMode(); - - await super.ngOnInit(); + override ngOnInit(): Promise { + this.mode = this.entity.getType().toLowerCase() as any; + return super.ngOnInit(); } - private inferMode(entity: Entity): "child" | "school" { - switch (entity?.getConstructor()?.ENTITY_TYPE) { - case Child.ENTITY_TYPE: - this.property = "childId"; - return "child"; - case School.ENTITY_TYPE: - this.property = "schoolId"; - return "school"; - } + override getData() { + this.switchRelatedEntityColumnForMode(); + return this.childrenService.queryRelationsOf( + this.mode, + this.entity.getId(false), + ); } private switchRelatedEntityColumnForMode() { @@ -93,11 +85,4 @@ export class ChildSchoolOverviewComponent idColumn.id = this.mode === "child" ? "schoolId" : "childId"; } } - - override getData() { - return this.childrenService.queryRelationsOf( - this.mode, - this.entity.getId(false), - ); - } } diff --git a/src/app/core/entity-details/related-entities-with-summary/related-entities-with-summary.component.spec.ts b/src/app/core/entity-details/related-entities-with-summary/related-entities-with-summary.component.spec.ts index 5c17e54951..106d9f0fd4 100644 --- a/src/app/core/entity-details/related-entities-with-summary/related-entities-with-summary.component.spec.ts +++ b/src/app/core/entity-details/related-entities-with-summary/related-entities-with-summary.component.spec.ts @@ -45,7 +45,6 @@ describe("RelatedEntitiesWithSummaryComponent", () => { component = fixture.componentInstance; component.entity = child; component.entityType = EducationalMaterial.ENTITY_TYPE; - component.property = "child"; component.summaries = { countProperty: "materialAmount", diff --git a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts index b54607a33d..0b8c2c94bc 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts @@ -39,7 +39,6 @@ describe("RelatedEntitiesComponent", () => { component = fixture.componentInstance; component.entity = new Child(); component.entityType = ChildSchoolRelation.ENTITY_TYPE; - component.property = "childId"; component.columns = []; fixture.detectChanges(); }); @@ -63,7 +62,6 @@ describe("RelatedEntitiesComponent", () => { component.entity = c1; component.entityType = ChildSchoolRelation.ENTITY_TYPE; - component.property = "childId"; component.columns = columns; await component.ngOnInit(); @@ -84,7 +82,6 @@ describe("RelatedEntitiesComponent", () => { component.entity = child; component.entityType = ChildSchoolRelation.ENTITY_TYPE; - component.property = "childId"; component.filter = filter; await component.ngOnInit(); @@ -103,7 +100,6 @@ describe("RelatedEntitiesComponent", () => { component.entity = c1; component.entityType = Note.ENTITY_TYPE; - component.property = "children"; component.filter = {}; // reset filter await component.ngOnInit(); @@ -114,7 +110,6 @@ describe("RelatedEntitiesComponent", () => { const related = new Child(); component.entity = related; component.entityType = ChildSchoolRelation.ENTITY_TYPE; - component.property = "childId"; component.columns = []; await component.ngOnInit(); diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index 7deb23ac42..925855155d 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -45,7 +45,7 @@ export class RelatedEntitiesComponent implements OnInit { * property name of the related entities (type given in this.entityType) that holds the entity id * to be matched with the id of the current main entity (given in this.entity) */ - @Input() property: string | string[]; + property: string | string[]; @Input() public set columns(value: ColumnConfig[]) { @@ -81,9 +81,9 @@ export class RelatedEntitiesComponent implements OnInit { } async ngOnInit() { + this.property = this.getProperty(); const data = await this.getData(); - this.property = this.getProperty(); this.filter = this.initFilter(); // TODO not really required as the entities table anyway hides the not-passing ones this.data = data.filter(this.filterService.getFilterPredicate(this.filter)); diff --git a/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.spec.ts b/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.spec.ts index 4727633e12..bb86051dab 100644 --- a/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.spec.ts +++ b/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.spec.ts @@ -24,7 +24,6 @@ describe("RelatedTimePeriodEntitiesComponent", () => { let mainEntity: Child; const entityType = "ChildSchoolRelation"; - const property = "childId"; let active1, active2, inactive: ChildSchoolRelation; @@ -56,7 +55,6 @@ describe("RelatedTimePeriodEntitiesComponent", () => { component.entity = mainEntity; component.entityType = entityType; - component.property = property; fixture.detectChanges(); }); @@ -75,7 +73,6 @@ describe("RelatedTimePeriodEntitiesComponent", () => { loadType.and.resolveTo([active1, active2, inactive]); component.entity = testSchool; - component.property = "schoolId"; await component.ngOnInit(); expect(component.data).toEqual([active1]); diff --git a/src/app/features/historical-data/historical-data/historical-data.component.spec.ts b/src/app/features/historical-data/historical-data/historical-data.component.spec.ts index ace2206941..4916d3b2e0 100644 --- a/src/app/features/historical-data/historical-data/historical-data.component.spec.ts +++ b/src/app/features/historical-data/historical-data/historical-data.component.spec.ts @@ -1,12 +1,12 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { HistoricalDataComponent } from "./historical-data.component"; -import { Entity } from "../../../core/entity/model/entity"; import { HistoricalEntityData } from "../model/historical-entity-data"; import moment from "moment"; import { HistoricalDataService } from "../historical-data.service"; import { MockedTestingModule } from "../../../utils/mocked-testing.module"; import { FormDialogService } from "../../../core/form-dialog/form-dialog.service"; +import { Child } from "../../../child-dev-project/children/model/child"; describe("HistoricalDataComponent", () => { let component: HistoricalDataComponent; @@ -30,7 +30,7 @@ describe("HistoricalDataComponent", () => { fixture = TestBed.createComponent(HistoricalDataComponent); component = fixture.componentInstance; - component.entity = new Entity(); + component.entity = new Child(); fixture.detectChanges(); }); @@ -39,7 +39,6 @@ describe("HistoricalDataComponent", () => { }); it("should load the historical data", async () => { - component.entity = new Entity(); const relatedData = new HistoricalEntityData(); relatedData.relatedEntity = component.entity.getId(); mockHistoricalDataService.getHistoricalDataFor.and.resolveTo([relatedData]); @@ -53,9 +52,7 @@ describe("HistoricalDataComponent", () => { }); it("should generate new records with a link to the passed entity", () => { - component.entity = new Entity(); - - const newEntry = component.getNewEntryFunction()(); + const newEntry = component.createNewRecordFactory()(); expect(newEntry.relatedEntity).toBe(component.entity.getId()); expect(moment(newEntry.date).isSame(new Date(), "day")).toBeTrue(); diff --git a/src/app/features/historical-data/historical-data/historical-data.component.ts b/src/app/features/historical-data/historical-data/historical-data.component.ts index cc75f4ebc2..9542aeb66a 100644 --- a/src/app/features/historical-data/historical-data/historical-data.component.ts +++ b/src/app/features/historical-data/historical-data/historical-data.component.ts @@ -29,7 +29,6 @@ export class HistoricalDataComponent implements OnInit { @Input() entity: Entity; - property = "relatedEntity"; entityCtr = HistoricalEntityData; /** @deprecated use @Input() columns instead */ @@ -52,12 +51,4 @@ export class HistoricalDataComponent override getData() { return this.historicalDataService.getHistoricalDataFor(this.entity.getId()); } - - public getNewEntryFunction(): () => HistoricalEntityData { - return () => { - const newEntry = new HistoricalEntityData(); - newEntry.relatedEntity = this.entity.getId(); - return newEntry; - }; - } } From c54ef7427030820d850f372a07727de4099ad2d4 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 23 Jan 2024 23:20:09 +0100 Subject: [PATCH 09/29] using different approach to update table summary --- ...ed-entities-with-summary.component.spec.ts | 21 +++++++--- ...related-entities-with-summary.component.ts | 40 ++++++------------- .../related-entities.component.spec.ts | 2 +- 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/app/core/entity-details/related-entities-with-summary/related-entities-with-summary.component.spec.ts b/src/app/core/entity-details/related-entities-with-summary/related-entities-with-summary.component.spec.ts index 106d9f0fd4..2e9ae3f67b 100644 --- a/src/app/core/entity-details/related-entities-with-summary/related-entities-with-summary.component.spec.ts +++ b/src/app/core/entity-details/related-entities-with-summary/related-entities-with-summary.component.spec.ts @@ -62,7 +62,7 @@ describe("RelatedEntitiesWithSummaryComponent", () => { it("produces an empty summary when there are no records", () => { component.data = []; - component.updateSummary(); + component.updateSummary(component.data); expect(component.summarySum).toHaveSize(0); expect(component.summaryAvg).toHaveSize(0); }); @@ -71,7 +71,7 @@ describe("RelatedEntitiesWithSummaryComponent", () => { ...records: Partial[] ) { component.data = records.map(EducationalMaterial.create); - component.updateSummary(); + component.updateSummary(component.data); } it("produces a singleton summary when there is a single record", () => { @@ -98,7 +98,7 @@ describe("RelatedEntitiesWithSummaryComponent", () => { component.data = [{ amount: 1 }, { amount: 5 }] as any[]; delete component.summaries.groupBy; component.summaries.countProperty = "amount"; - component.updateSummary(); + component.updateSummary(component.data); expect(component.summarySum).toEqual(`6`); expect(component.summaryAvg).toEqual(`3`); @@ -177,7 +177,7 @@ describe("RelatedEntitiesWithSummaryComponent", () => { ); }); - it("loads all education data associated with a child and updates the summary", async () => { + it("loads all education data associated with a child and updates the summary", fakeAsync(() => { const educationalData = [ { materialType: PENCIL, materialAmount: 1, child: child.getId() }, { materialType: RULER, materialAmount: 2, child: child.getId() }, @@ -185,16 +185,22 @@ describe("RelatedEntitiesWithSummaryComponent", () => { spyOn(TestBed.inject(EntityMapperService), "loadType").and.resolveTo( educationalData, ); + component.entity = new Child("22"); - await component.ngOnInit(); + component.ngOnInit(); + tick(); + fixture.detectChanges(); + tick(); + expect(component.summarySum).toEqual( `${PENCIL.label}: 1, ${RULER.label}: 2`, ); expect(component.data).toEqual(educationalData); - }); + })); it("should update the summary when entity updates are received", fakeAsync(() => { component.ngOnInit(); + fixture.detectChanges(); tick(); const update1 = EducationalMaterial.create({ @@ -203,6 +209,7 @@ describe("RelatedEntitiesWithSummaryComponent", () => { materialAmount: 1, }); updates.next({ entity: update1, type: "new" }); + fixture.detectChanges(); tick(); expect(component.data).toEqual([update1]); @@ -211,6 +218,7 @@ describe("RelatedEntitiesWithSummaryComponent", () => { const update2 = update1.copy() as EducationalMaterial; update2.materialAmount = 2; updates.next({ entity: update2, type: "update" }); + fixture.detectChanges(); tick(); expect(component.data).toEqual([update2]); @@ -219,6 +227,7 @@ describe("RelatedEntitiesWithSummaryComponent", () => { const unrelatedUpdate = update1.copy() as EducationalMaterial; unrelatedUpdate.child = "differentChild"; updates.next({ entity: unrelatedUpdate, type: "new" }); + fixture.detectChanges(); tick(); // No change expect(component.data).toEqual([update2]); diff --git a/src/app/core/entity-details/related-entities-with-summary/related-entities-with-summary.component.ts b/src/app/core/entity-details/related-entities-with-summary/related-entities-with-summary.component.ts index 52e08a3ebc..ca2a493ae0 100644 --- a/src/app/core/entity-details/related-entities-with-summary/related-entities-with-summary.component.ts +++ b/src/app/core/entity-details/related-entities-with-summary/related-entities-with-summary.component.ts @@ -1,29 +1,27 @@ -import { Component, Input, OnInit } from "@angular/core"; -import { NgFor, NgIf } from "@angular/common"; +import { Component, Input, OnInit, ViewChild } from "@angular/core"; +import { NgIf } from "@angular/common"; import { DynamicComponent } from "../../config/dynamic-components/dynamic-component.decorator"; -import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; import { RelatedEntitiesComponent } from "../related-entities/related-entities.component"; import { Entity } from "../../entity/model/entity"; -import { filter } from "rxjs/operators"; -import { applyUpdate } from "../../entity/model/entity-update"; import { EntitiesTableComponent } from "../../common-components/entities-table/entities-table.component"; /** - * Load and display a list of entity subrecords (entities related to the current entity details view) + * Load and display a list of related entities * including a summary below the table. */ @DynamicComponent("RelatedEntitiesWithSummary") -@UntilDestroy() @Component({ selector: "app-related-entities-with-summary", templateUrl: "./related-entities-with-summary.component.html", - imports: [EntitiesTableComponent, NgIf, NgFor], + imports: [EntitiesTableComponent, NgIf], standalone: true, }) export class RelatedEntitiesWithSummaryComponent extends RelatedEntitiesComponent implements OnInit { + @ViewChild(EntitiesTableComponent, { static: true }) + entitiesTable: EntitiesTableComponent; /** * Configuration of what numbers should be summarized below the table. */ @@ -37,25 +35,11 @@ export class RelatedEntitiesWithSummaryComponent summarySum = ""; summaryAvg = ""; - async ngOnInit() { + override async ngOnInit() { await super.ngOnInit(); - this.updateSummary(); - - this.entityMapper - .receiveUpdates(this.entityCtr) - .pipe( - untilDestroyed(this), - filter( - ({ entity, type }) => - type === "remove" || - entity[this.property as string] === this.entity.getId(), - ), - ) - .subscribe((update) => { - this.data = applyUpdate(this.data, update, false); - // TODO needs to apply to filtered data - this.updateSummary(); - }); + this.entitiesTable.filteredRecordsChange.subscribe((data) => + this.updateSummary(data), + ); } /** @@ -63,7 +47,7 @@ export class RelatedEntitiesWithSummaryComponent * The summary contains no duplicates and is in a * human-readable format */ - updateSummary() { + updateSummary(filteredData: E[]) { if (!this.summaries) { this.summarySum = ""; this.summaryAvg = ""; @@ -73,7 +57,7 @@ export class RelatedEntitiesWithSummaryComponent const summary = new Map(); const average = new Map(); - this.data.forEach((m) => { + filteredData.forEach((m) => { const amount = m[this.summaries.countProperty]; let groupLabel; if (this.summaries.groupBy) { diff --git a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts index 0b8c2c94bc..c046de2f91 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts @@ -34,7 +34,7 @@ describe("RelatedEntitiesComponent", () => { }).compileComponents(); fixture = TestBed.createComponent( - RelatedEntitiesComponent, + RelatedEntitiesComponent, ); component = fixture.componentInstance; component.entity = new Child(); From 5a61f1de5ddf8d86fd73c1d3516cd3a151827ffc Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 23 Jan 2024 23:24:47 +0100 Subject: [PATCH 10/29] updated documentation --- .../related-entities.component.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index 925855155d..1fed4e1462 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -42,11 +42,16 @@ export class RelatedEntitiesComponent implements OnInit { } /** - * property name of the related entities (type given in this.entityType) that holds the entity id - * to be matched with the id of the current main entity (given in this.entity) + * Property name of the related entities (type given in this.entityType) that holds the entity id + * to be matched with the id of the current main entity (given in this.entity). + * This is automatically inferred and does not need to be set. */ property: string | string[]; + /** + * Columns to be displayed in the table + * @param value + */ @Input() public set columns(value: ColumnConfig[]) { if (!Array.isArray(value)) { @@ -60,8 +65,14 @@ export class RelatedEntitiesComponent implements OnInit { columnsToDisplay: string[]; + /** + * This filter is applied before displaying the data. + */ @Input() filter?: DataFilter; + /** + * Whether inactive/archived records should be shown. + */ @Input() showInactive: boolean; data: E[]; From 9e894dd112eee89e0ca0269c314b4355cfed4f0d Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 23 Jan 2024 23:31:54 +0100 Subject: [PATCH 11/29] correctly setting showInactive toggle in child school overview --- .../child-school-overview.component.spec.ts | 16 ++++++++++++++++ .../child-school-overview.component.ts | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.spec.ts b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.spec.ts index d357dc4f0c..3d9ddf9b8b 100644 --- a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.spec.ts +++ b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.spec.ts @@ -92,4 +92,20 @@ describe("ChildSchoolOverviewComponent", () => { expect(newRelation).toBeInstanceOf(ChildSchoolRelation); expect(newRelation.schoolId).toBe("testID"); }); + + it("should show archived school in 'child' mode", async () => { + component.entity = new Child(); + + await component.ngOnInit(); + + expect(component.showInactive).toBeTrue(); + }); + + it("should not show archived children in 'school' mode", async () => { + component.entity = new School(); + + await component.ngOnInit(); + + expect(component.showInactive).toBeFalse(); + }); }); diff --git a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts index f5836defae..6da60d7079 100644 --- a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts +++ b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { DynamicComponent } from "../../../core/config/dynamic-components/dynamic-component.decorator"; import { ChildSchoolRelation } from "../../children/model/childSchoolRelation"; import { ChildrenService } from "../../children/children.service"; @@ -42,7 +42,6 @@ export class ChildSchoolOverviewComponent implements OnInit { mode: "child" | "school" = "child"; - @Input() showInactive = this.mode === "child"; entityCtr = ChildSchoolRelation; constructor( @@ -65,6 +64,7 @@ export class ChildSchoolOverviewComponent override ngOnInit(): Promise { this.mode = this.entity.getType().toLowerCase() as any; + this.showInactive = this.mode === "child"; return super.ngOnInit(); } From 0dc0c094a95a39e99e0f922e057e543f6049d1d5 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 23 Jan 2024 23:32:13 +0100 Subject: [PATCH 12/29] using placeholder instead of new record factory --- .../health-checkup.component.ts | 20 ++----------------- .../health-checkup/model/health-check.ts | 2 ++ 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts b/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts index 737543ef6d..9b2bd7d5c6 100644 --- a/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts +++ b/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts @@ -1,7 +1,6 @@ -import { Component, Input, OnInit } from "@angular/core"; +import { Component } from "@angular/core"; import { HealthCheck } from "../model/health-check"; import { ChildrenService } from "../../children.service"; -import { Child } from "../../model/child"; import { FormFieldConfig } from "../../../../core/common-components/entity-form/FormConfig"; import { DynamicComponent } from "../../../../core/config/dynamic-components/dynamic-component.decorator"; import { EntitiesTableComponent } from "../../../../core/common-components/entities-table/entities-table.component"; @@ -19,11 +18,7 @@ import { FilterService } from "../../../../core/filter/filter.service"; imports: [EntitiesTableComponent], standalone: true, }) -export class HealthCheckupComponent - extends RelatedEntitiesComponent - implements OnInit -{ - @Input() entity: Child; +export class HealthCheckupComponent extends RelatedEntitiesComponent { entityCtr = HealthCheck; /** @@ -63,17 +58,6 @@ export class HealthCheckupComponent } } - override createNewRecordFactory() { - return () => { - const newHC = new HealthCheck(); - - newHC.date = new Date(); - newHC.child = this.entity.getId(); - - return newHC; - }; - } - /** * implements the health check loading from the children service and is called in the onInit() */ diff --git a/src/app/child-dev-project/children/health-checkup/model/health-check.ts b/src/app/child-dev-project/children/health-checkup/model/health-check.ts index b8ad26bcca..3ee44d9d20 100644 --- a/src/app/child-dev-project/children/health-checkup/model/health-check.ts +++ b/src/app/child-dev-project/children/health-checkup/model/health-check.ts @@ -20,6 +20,7 @@ import { DatabaseEntity } from "../../../../core/entity/database-entity.decorato import { DatabaseField } from "../../../../core/entity/database-field.decorator"; import { WarningLevel } from "../../../warning-level"; import { Child } from "../../model/child"; +import { PLACEHOLDERS } from "../../../../core/entity/schema/entity-schema-field"; /** * Model Class for the Health Checks that are taken for a Child. @@ -44,6 +45,7 @@ export class HealthCheck extends Entity { @DatabaseField({ label: $localize`:Label for date of a health check:Date`, anonymize: "retain-anonymized", + defaultValue: PLACEHOLDERS.NOW, }) date: Date; From f030a6afad91b6f4999fd16dd36790836951ca17 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 23 Jan 2024 23:54:40 +0100 Subject: [PATCH 13/29] added todos --- .../child-school-overview.component.ts | 3 ++- .../related-entities.component.ts | 1 + .../todos-related-to-entity.component.ts | 17 +++++++++-------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts index 6da60d7079..f2d4c72d6f 100644 --- a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts +++ b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts @@ -65,11 +65,12 @@ export class ChildSchoolOverviewComponent override ngOnInit(): Promise { this.mode = this.entity.getType().toLowerCase() as any; this.showInactive = this.mode === "child"; + // TODO toggling inactive does not hide/show color on school details + this.switchRelatedEntityColumnForMode(); return super.ngOnInit(); } override getData() { - this.switchRelatedEntityColumnForMode(); return this.childrenService.queryRelationsOf( this.mode, this.entity.getId(false), diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index 1fed4e1462..4a0015d3c9 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -144,6 +144,7 @@ export class RelatedEntitiesComponent implements OnInit { } private getFilterForProperty(property: string) { + // TODO doesnt work with full ids const isArray = isArrayProperty(this.entityCtr, property); const filter = isArray ? { $elemMatch: { $eq: this.entity.getId() } } diff --git a/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.ts b/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.ts index 2c64d477b0..efb0a674ab 100644 --- a/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.ts +++ b/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.ts @@ -36,9 +36,6 @@ export class TodosRelatedToEntityComponent extends RelatedEntitiesComponent custom filter component or some kind of variable interpolation? override filter: DataFilter = { isActive: true }; backgroundColorFn = (r: Todo) => { @@ -58,20 +55,24 @@ export class TodosRelatedToEntityComponent extends RelatedEntitiesComponent Date: Wed, 24 Jan 2024 13:30:48 +0100 Subject: [PATCH 14/29] removed unnecessary sorts --- .../aser/aser-component/aser.component.ts | 9 +----- .../health-checkup.component.ts | 9 +----- .../notes-related-to-entity.component.spec.ts | 31 +------------------ .../notes-related-to-entity.component.ts | 14 +-------- .../related-entities.component.spec.ts | 1 - 5 files changed, 4 insertions(+), 60 deletions(-) diff --git a/src/app/child-dev-project/children/aser/aser-component/aser.component.ts b/src/app/child-dev-project/children/aser/aser-component/aser.component.ts index 8f06b5cc60..425459523e 100644 --- a/src/app/child-dev-project/children/aser/aser-component/aser.component.ts +++ b/src/app/child-dev-project/children/aser/aser-component/aser.component.ts @@ -44,13 +44,6 @@ export class AserComponent extends RelatedEntitiesComponent { } override getData() { - return this.childrenService - .getAserResultsOfChild(this.entity.getId()) - .then((data) => - data.sort( - (a, b) => - (b.date ? b.date.valueOf() : 0) - (a.date ? a.date.valueOf() : 0), - ), - ); + return this.childrenService.getAserResultsOfChild(this.entity.getId()); } } diff --git a/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts b/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts index 9b2bd7d5c6..9cb84dd5cb 100644 --- a/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts +++ b/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts @@ -62,13 +62,6 @@ export class HealthCheckupComponent extends RelatedEntitiesComponent - data.sort( - (a, b) => - (b.date ? b.date.valueOf() : 0) - (a.date ? a.date.valueOf() : 0), - ), - ); + return this.childrenService.getHealthChecksOfChild(this.entity.getId()); } } diff --git a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts index 55254fd003..be6d675dd3 100644 --- a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts +++ b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts @@ -1,11 +1,5 @@ import { NotesRelatedToEntityComponent } from "./notes-related-to-entity.component"; -import { - ComponentFixture, - fakeAsync, - TestBed, - tick, - waitForAsync, -} from "@angular/core/testing"; +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { ChildrenService } from "../../children/children.service"; import { Note } from "../model/note"; import { Child } from "../../children/model/child"; @@ -13,7 +7,6 @@ import { MockedTestingModule } from "../../../utils/mocked-testing.module"; import { Entity } from "../../../core/entity/model/entity"; import { School } from "../../schools/model/school"; import { User } from "../../../core/user/user"; -import moment from "moment"; import { ChildSchoolRelation } from "../../children/model/childSchoolRelation"; import { DatabaseEntity } from "../../../core/entity/database-entity.decorator"; import { DatabaseField } from "../../../core/entity/database-field.decorator"; @@ -123,28 +116,6 @@ describe("NotesRelatedToEntityComponent", () => { ); }); - it("should sort notes by date", fakeAsync(() => { - const child = new Child(); - // No date should come first - const n1 = new Note(); - const n2 = new Note(); - n2.date = moment().subtract(1, "day").toDate(); - const n3 = new Note(); - n3.date = moment().subtract(2, "days").toDate(); - [n3, n2, n1].forEach((n) => n.addChild(child)); - const childrenService = TestBed.inject(ChildrenService); - spyOn(childrenService, "getNotesRelatedTo").and.resolveTo([n3, n2, n1]); - - component.entity = child; - component.ngOnInit(); - tick(); - - expect(childrenService.getNotesRelatedTo).toHaveBeenCalledWith( - component.entity.getId(true), - ); - expect(component.data).toEqual([n1, n2, n3]); - })); - it("should only add related notes after the initial load", async () => { const child = new Child(); const data = [new Note(), new Note()]; diff --git a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts index 145619edac..2b2840f7f0 100644 --- a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts +++ b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts @@ -2,7 +2,6 @@ import { Component } from "@angular/core"; import { Note } from "../model/note"; import { NoteDetailsComponent } from "../note-details/note-details.component"; import { ChildrenService } from "../../children/children.service"; -import moment from "moment"; import { FormDialogService } from "../../../core/form-dialog/form-dialog.service"; import { DynamicComponent } from "../../../core/config/dynamic-components/dynamic-component.decorator"; import { Entity } from "../../../core/entity/model/entity"; @@ -68,18 +67,7 @@ export class NotesRelatedToEntityComponent extends RelatedEntitiesComponent { - notes.sort((a, b) => { - if (!a.date && b.date) { - // note without date should be first - return -1; - } - return moment(b.date).valueOf() - moment(a.date).valueOf(); - }); - return notes; - }); + return this.childrenService.getNotesRelatedTo(this.entity.getId(true)); } generateNewRecordFactory() { diff --git a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts index c046de2f91..4b913f3fd6 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts @@ -246,7 +246,6 @@ describe("RelatedEntitiesComponent", () => { expect(component.property).toEqual("multiTypeRelation"); // Now with 2 relations ("arrayRelation" and "multiTypeRelation") - // TODO (how) do we support this case? component.entity = new School(); await component.ngOnInit(); expect(component.property).toEqual( From 4ada0ed950f7b60650870b34bda2d92e412580e4 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 24 Jan 2024 14:22:35 +0100 Subject: [PATCH 15/29] removed unnecessary data filter --- .../related-entities.component.spec.ts | 86 ++----------------- .../related-entities.component.ts | 5 +- ...ted-time-period-entities.component.spec.ts | 16 ---- 3 files changed, 10 insertions(+), 97 deletions(-) diff --git a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts index 4b913f3fd6..1848fa2d6c 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts @@ -10,11 +10,9 @@ import { MockedTestingModule } from "../../../utils/mocked-testing.module"; import { EntityMapperService } from "../../entity/entity-mapper/entity-mapper.service"; import { Child } from "../../../child-dev-project/children/model/child"; import { ChildSchoolRelation } from "../../../child-dev-project/children/model/childSchoolRelation"; -import { Note } from "../../../child-dev-project/notes/model/note"; import { Subject } from "rxjs"; import { UpdatedEntity } from "../../entity/model/entity-update"; import { Entity } from "../../entity/model/entity"; -import moment from "moment"; import { DatabaseEntity } from "../../entity/database-entity.decorator"; import { EntityDatatype } from "../../basic-datatypes/entity/entity.datatype"; import { EntityArrayDatatype } from "../../basic-datatypes/entity-array/entity-array.datatype"; @@ -23,10 +21,8 @@ import { DatabaseField } from "../../entity/database-field.decorator"; import { expectEntitiesToMatch } from "../../../utils/expect-entity-data.spec"; describe("RelatedEntitiesComponent", () => { - let component: RelatedEntitiesComponent; - let fixture: ComponentFixture< - RelatedEntitiesComponent - >; + let component: RelatedEntitiesComponent; + let fixture: ComponentFixture>; beforeEach(async () => { await TestBed.configureTestingModule({ @@ -34,7 +30,7 @@ describe("RelatedEntitiesComponent", () => { }).compileComponents(); fixture = TestBed.createComponent( - RelatedEntitiesComponent, + RelatedEntitiesComponent, ); component = fixture.componentInstance; component.entity = new Child(); @@ -47,65 +43,29 @@ describe("RelatedEntitiesComponent", () => { expect(component).toBeTruthy(); }); - it("should only show the entities which are linked with the passed one", async () => { - const c1 = new Child(); - const c2 = new Child(); - const r1 = new ChildSchoolRelation(); - r1.childId = c1.getId(); - const r2 = new ChildSchoolRelation(); - r2.childId = c1.getId(); - const r3 = new ChildSchoolRelation(); - r3.childId = c2.getId(); - const entityMapper = TestBed.inject(EntityMapperService); - await entityMapper.saveAll([c1, c2, r1, r2, r3]); + it("should create a filter for the passed entity", async () => { + const child = new Child(); const columns = ["start", "end", "schoolId"]; - - component.entity = c1; + component.entity = child; component.entityType = ChildSchoolRelation.ENTITY_TYPE; component.columns = columns; await component.ngOnInit(); - expect(component.data).toEqual([r1, r2]); + expect(component.filter).toEqual({ childId: child.getId() }); }); - it("should only show the entities which pass the filter", async () => { + it("should also included the provided filter", async () => { const child = new Child(); - const r1 = new ChildSchoolRelation(); - r1.start = new Date(); - r1.childId = child.getId(); - const r2 = new ChildSchoolRelation(); - r2.childId = child.getId(); - const r3 = new ChildSchoolRelation(); - const entityMapper = TestBed.inject(EntityMapperService); - await entityMapper.saveAll([child, r1, r2, r3]); - const filter = { start: { $exists: true } } as any; + const filter = { start: { $exists: true } }; component.entity = child; component.entityType = ChildSchoolRelation.ENTITY_TYPE; component.filter = filter; await component.ngOnInit(); - expect(component.data).toEqual([r1]); expect(component.filter).toEqual({ ...filter, childId: child.getId() }); }); - it("should ignore entities of the related type where the matching field is undefined instead of array", async () => { - const c1 = new Child(); - const n1 = new Note(); - n1.children = [c1.getId()]; - const nEmpty = new Note(); - delete nEmpty.children; // some entity types will not have a default empty array - const entityMapper = TestBed.inject(EntityMapperService); - await entityMapper.saveAll([c1, n1, nEmpty]); - - component.entity = c1; - component.entityType = Note.ENTITY_TYPE; - component.filter = {}; // reset filter - await component.ngOnInit(); - - expect(component.data).toEqual([n1]); - }); - it("should create a new entity that references the related one", async () => { const related = new Child(); component.entity = related; @@ -133,34 +93,6 @@ describe("RelatedEntitiesComponent", () => { expect(component.data).toEqual([entity]); })); - xit("should only add entities which pass the filter object", fakeAsync(() => { - const entityUpdates = new Subject>(); - const entityMapper = TestBed.inject(EntityMapperService); - spyOn(entityMapper, "receiveUpdates").and.returnValue(entityUpdates); - component.ngOnInit(); - // only show active relations - component.filter = { isActive: true }; - tick(); - - // active -> add - const activeRelation = new ChildSchoolRelation(); - activeRelation.childId = component.entity.getId(); - activeRelation.start = moment().subtract(1, "week").toDate(); - entityUpdates.next({ entity: activeRelation, type: "new" }); - tick(); - expect(component.data).toEqual([activeRelation]); - - // inactive -> don't add - const inactiveRelation = new ChildSchoolRelation(); - inactiveRelation.childId = component.entity.getId(); - inactiveRelation.start = moment().subtract(1, "week").toDate(); - inactiveRelation.end = moment().subtract(2, "days").toDate(); - entityUpdates.next({ entity: inactiveRelation, type: "new" }); - tick(); - // TODO do we actually need to filter the data or is it sufficient to create the correct filter and let the entities table hide it? - expect(component.data).toEqual([activeRelation]); - })); - it("should remove an entity from the table when it has been deleted", fakeAsync(() => { const entityUpdates = new Subject>(); const entityMapper = TestBed.inject(EntityMapperService); diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index 4a0015d3c9..a48074e17c 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -93,11 +93,8 @@ export class RelatedEntitiesComponent implements OnInit { async ngOnInit() { this.property = this.getProperty(); - const data = await this.getData(); - + this.data = await this.getData(); this.filter = this.initFilter(); - // TODO not really required as the entities table anyway hides the not-passing ones - this.data = data.filter(this.filterService.getFilterPredicate(this.filter)); if (this.showInactive === undefined) { // show all related docs when visiting an archived entity diff --git a/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.spec.ts b/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.spec.ts index bb86051dab..53a24bfd93 100644 --- a/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.spec.ts +++ b/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.spec.ts @@ -10,7 +10,6 @@ import { RelatedTimePeriodEntitiesComponent } from "./related-time-period-entiti import moment from "moment"; import { MockedTestingModule } from "../../../utils/mocked-testing.module"; import { Child } from "../../../child-dev-project/children/model/child"; -import { School } from "../../../child-dev-project/schools/model/school"; import { ChildSchoolRelation } from "../../../child-dev-project/children/model/childSchoolRelation"; import { EntityMapperService } from "../../entity/entity-mapper/entity-mapper.service"; @@ -63,21 +62,6 @@ describe("RelatedTimePeriodEntitiesComponent", () => { expect(component).toBeTruthy(); }); - it("should load correctly filtered data", async () => { - const testSchool = new School(); - active1.schoolId = testSchool.getId(); - active2.schoolId = "some-other-id"; - inactive.schoolId = "some-other-id"; - - const loadType = spyOn(entityMapper, "loadType"); - loadType.and.resolveTo([active1, active2, inactive]); - - component.entity = testSchool; - await component.ngOnInit(); - - expect(component.data).toEqual([active1]); - }); - it("should change columns to be displayed via config", async () => { component.entity = new Child(); component.single = true; From 4340bc05941c2e133b87262976cf2165acc76722 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 24 Jan 2024 14:33:40 +0100 Subject: [PATCH 16/29] not using properties in tests --- .../related-entities.component.spec.ts | 41 ++++++++++++------- .../related-entities.component.ts | 2 +- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts index 1848fa2d6c..ffe71ac133 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts @@ -130,9 +130,6 @@ describe("RelatedEntitiesComponent", () => { await component.ngOnInit(); - expect(component.property).toEqual( - jasmine.arrayWithExactContents(["singleChild", "multiEntities"]), - ); // filter matching relations at any of the available props expect(component.filter).toEqual({ $or: [ @@ -148,40 +145,56 @@ describe("RelatedEntitiesComponent", () => { ); }); - it("should infer the property based on related entity", async () => { + it("should align the filter with the provided property", async () => { @DatabaseEntity("PropTest") class PropTest extends Entity {} component.entityType = PropTest.ENTITY_TYPE; + const entity = new Child(); PropTest.schema.set("singleRelation", { dataType: EntityDatatype.dataType, additional: Child.ENTITY_TYPE, }); - component.entity = new Child(); + component.entity = entity; + component.filter = undefined; await component.ngOnInit(); - expect(component.property).toEqual("singleRelation"); + expect(component.filter).toEqual({ + singleRelation: component.entity.getId(), + }); PropTest.schema.set("arrayRelation", { dataType: EntityArrayDatatype.dataType, additional: School.ENTITY_TYPE, }); component.entity = new School(); + component.filter = undefined; await component.ngOnInit(); - expect(component.property).toEqual("arrayRelation"); + expect(component.filter).toEqual({ + arrayRelation: { $elemMatch: { $eq: component.entity.getId() } }, + }); PropTest.schema.set("multiTypeRelation", { dataType: EntityArrayDatatype.dataType, - additional: [ChildSchoolRelation.ENTITY_TYPE, School.ENTITY_TYPE], + additional: [ChildSchoolRelation.ENTITY_TYPE, Child.ENTITY_TYPE], }); component.entity = new ChildSchoolRelation(); + component.filter = undefined; await component.ngOnInit(); - expect(component.property).toEqual("multiTypeRelation"); + expect(component.filter).toEqual({ + multiTypeRelation: { $elemMatch: { $eq: component.entity.getId() } }, + }); - // Now with 2 relations ("arrayRelation" and "multiTypeRelation") - component.entity = new School(); + // Now with 2 relations ("singleRelation" and "multiTypeRelation") + component.entity = new Child(); + component.filter = undefined; await component.ngOnInit(); - expect(component.property).toEqual( - jasmine.arrayWithExactContents(["multiTypeRelation", "arrayRelation"]), - ); + expect(component.filter).toEqual({ + $or: [ + { singleRelation: component.entity.getId() }, + { + multiTypeRelation: { $elemMatch: { $eq: component.entity.getId() } }, + }, + ], + }); }); }); diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index a48074e17c..ad61aae253 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -46,7 +46,7 @@ export class RelatedEntitiesComponent implements OnInit { * to be matched with the id of the current main entity (given in this.entity). * This is automatically inferred and does not need to be set. */ - property: string | string[]; + protected property: string | string[]; /** * Columns to be displayed in the table From cdfbfa65bbc438bd6360be989742f88c960b7a29 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 24 Jan 2024 15:20:55 +0100 Subject: [PATCH 17/29] generalized newRecordFactory --- .../notes-related-to-entity.component.spec.ts | 20 +++++++++++-------- .../notes-related-to-entity.component.ts | 12 ++--------- .../related-entities.component.ts | 7 +------ src/app/core/filter/filter.service.spec.ts | 13 ++++++++++++ src/app/core/filter/filter.service.ts | 9 ++++++++- .../historical-data.component.spec.ts | 4 ++-- 6 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts index be6d675dd3..88df29bda1 100644 --- a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts +++ b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts @@ -6,11 +6,11 @@ import { Child } from "../../children/model/child"; import { MockedTestingModule } from "../../../utils/mocked-testing.module"; import { Entity } from "../../../core/entity/model/entity"; import { School } from "../../schools/model/school"; -import { User } from "../../../core/user/user"; -import { ChildSchoolRelation } from "../../children/model/childSchoolRelation"; import { DatabaseEntity } from "../../../core/entity/database-entity.decorator"; import { DatabaseField } from "../../../core/entity/database-field.decorator"; import { EntityMapperService } from "../../../core/entity/entity-mapper/entity-mapper.service"; +import { User } from "../../../core/user/user"; +import { ChildSchoolRelation } from "../../children/model/childSchoolRelation"; describe("NotesRelatedToEntityComponent", () => { let component: NotesRelatedToEntityComponent; @@ -45,22 +45,25 @@ describe("NotesRelatedToEntityComponent", () => { expect(note.getColorForId).toHaveBeenCalledWith(entity.getId()); }); - it("should create a new note and fill it with the appropriate initial value", () => { + it("should create a new note and fill it with the appropriate initial value", async () => { let entity: Entity = new Child(); component.entity = entity; - component.ngOnInit(); + component.filter = undefined; + await component.ngOnInit(); let note = component.generateNewRecordFactory()(); expect(note.children).toEqual([entity.getId()]); entity = new School(); component.entity = entity; - component.ngOnInit(); + component.filter = undefined; + await component.ngOnInit(); note = component.generateNewRecordFactory()(); expect(note.schools).toEqual([entity.getId()]); entity = new User(); component.entity = entity; - component.ngOnInit(); + component.filter = undefined; + await component.ngOnInit(); note = component.generateNewRecordFactory()(); expect(note.relatedEntities).toEqual([entity.getId(true)]); @@ -68,9 +71,10 @@ describe("NotesRelatedToEntityComponent", () => { entity["childId"] = "someChild"; entity["schoolId"] = "someSchool"; component.entity = entity; - component.ngOnInit(); + component.filter = undefined; + await component.ngOnInit(); note = component.generateNewRecordFactory()(); - expect(note.relatedEntities).toContain(entity.getId(true)); + expect(note.relatedEntities).toEqual([entity.getId(true)]); expect(note.children).toEqual(["someChild"]); expect(note.schools).toEqual(["someSchool"]); }); diff --git a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts index 2b2840f7f0..8df4e4a6c2 100644 --- a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts +++ b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts @@ -7,7 +7,6 @@ import { DynamicComponent } from "../../../core/config/dynamic-components/dynami import { Entity } from "../../../core/entity/model/entity"; import { FilterService } from "../../../core/filter/filter.service"; import { Child } from "../../children/model/child"; -import { School } from "../../schools/model/school"; import { ChildSchoolRelation } from "../../children/model/childSchoolRelation"; import { EntityDatatype } from "../../../core/basic-datatypes/entity/entity.datatype"; import { EntityArrayDatatype } from "../../../core/basic-datatypes/entity-array/entity-array.datatype"; @@ -72,14 +71,9 @@ export class NotesRelatedToEntityComponent extends RelatedEntitiesComponent { - const newNote = new Note(Date.now().toString()); - + const newNote = super.createNewRecordFactory()(); //TODO: generalize this code - possibly by only using relatedEntities to link other records here? see #1501 - if (this.entity.getType() === Child.ENTITY_TYPE) { - newNote.addChild(this.entity as Child); - } else if (this.entity.getType() === School.ENTITY_TYPE) { - newNote.addSchool(this.entity as School); - } else if (this.entity.getType() === ChildSchoolRelation.ENTITY_TYPE) { + if (this.entity.getType() === ChildSchoolRelation.ENTITY_TYPE) { newNote.addChild((this.entity as ChildSchoolRelation).childId); newNote.addSchool((this.entity as ChildSchoolRelation).schoolId); } @@ -89,8 +83,6 @@ export class NotesRelatedToEntityComponent extends RelatedEntitiesComponent implements OnInit { @Input() showInactive: boolean; data: E[]; - private isArray = false; protected entityCtr: EntityConstructor; constructor( @@ -162,11 +161,7 @@ export class RelatedEntitiesComponent implements OnInit { // TODO has a similar purpose like FilterService.alignEntityWithFilter return () => { const rec = new this.entityCtr(); - if (!Array.isArray(this.property)) { - rec[this.property] = this.isArray - ? [this.entity.getId()] - : this.entity.getId(); - } + this.filterService.alignEntityWithFilter(rec, this.filter); return rec; }; } diff --git a/src/app/core/filter/filter.service.spec.ts b/src/app/core/filter/filter.service.spec.ts index 40a675270a..fa69e507b7 100644 --- a/src/app/core/filter/filter.service.spec.ts +++ b/src/app/core/filter/filter.service.spec.ts @@ -7,6 +7,7 @@ import { ConfigurableEnumService } from "../basic-datatypes/configurable-enum/co import { createTestingConfigurableEnumService } from "../basic-datatypes/configurable-enum/configurable-enum-testing"; import moment from "moment"; import { DataFilter } from "./filters/filters"; +import { Child } from "../../child-dev-project/children/model/child"; describe("FilterService", () => { let service: FilterService; @@ -67,6 +68,18 @@ describe("FilterService", () => { expect(predicate(note)).toBeTrue(); }); + it("should support patching with array values", () => { + const child = new Child(); + const filter = { + children: { $elemMatch: { $eq: child.getId() } }, + } as DataFilter; + const note = new Note(); + + service.alignEntityWithFilter(note, filter); + + expect(note.children).toEqual([child.getId()]); + }); + it("should support filtering dates with day granularity", () => { const n1 = Note.create(moment("2022-01-01").toDate()); const n2 = Note.create(moment("2022-01-02").toDate()); diff --git a/src/app/core/filter/filter.service.ts b/src/app/core/filter/filter.service.ts index c4b9d49cd0..eb1830093c 100644 --- a/src/app/core/filter/filter.service.ts +++ b/src/app/core/filter/filter.service.ts @@ -66,10 +66,17 @@ export class FilterService { alignEntityWithFilter(entity: T, filter: DataFilter) { const schema = entity.getSchema(); Object.entries(filter ?? {}).forEach(([key, value]) => { - // TODO support arrays through recursion if (typeof value !== "object") { // only simple equality filters are automatically applied to new entities, complex conditions (e.g. $lt / $gt) are ignored) this.assignValueToEntity(key, value, schema, entity); + } else if (value["$elemMatch"]?.["$eq"]) { + // e.g. { children: { $elemMatch: { $eq: "Child:some-id" } } } + this.assignValueToEntity( + key, + [value["$elemMatch"]["$eq"]], + schema, + entity, + ); } }); } diff --git a/src/app/features/historical-data/historical-data/historical-data.component.spec.ts b/src/app/features/historical-data/historical-data/historical-data.component.spec.ts index 4916d3b2e0..ed46412877 100644 --- a/src/app/features/historical-data/historical-data/historical-data.component.spec.ts +++ b/src/app/features/historical-data/historical-data/historical-data.component.spec.ts @@ -26,13 +26,13 @@ describe("HistoricalDataComponent", () => { }).compileComponents(); })); - beforeEach(() => { + beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(HistoricalDataComponent); component = fixture.componentInstance; component.entity = new Child(); fixture.detectChanges(); - }); + })); it("should create", () => { expect(component).toBeTruthy(); From 366f4c656f8c6e01138b7ab434bee7d3e734d3cb Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 24 Jan 2024 15:24:14 +0100 Subject: [PATCH 18/29] aligned new record factory names --- .../notes-related-to-entity.component.spec.ts | 10 +++++----- .../notes-related-to-entity.component.ts | 4 ++-- .../child-school-overview.component.spec.ts | 4 ++-- .../related-time-period-entities.component.html | 2 +- .../related-time-period-entities.component.spec.ts | 4 ++-- .../related-time-period-entities.component.ts | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts index 88df29bda1..e3c9138a52 100644 --- a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts +++ b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts @@ -50,21 +50,21 @@ describe("NotesRelatedToEntityComponent", () => { component.entity = entity; component.filter = undefined; await component.ngOnInit(); - let note = component.generateNewRecordFactory()(); + let note = component.createNewRecordFactory()(); expect(note.children).toEqual([entity.getId()]); entity = new School(); component.entity = entity; component.filter = undefined; await component.ngOnInit(); - note = component.generateNewRecordFactory()(); + note = component.createNewRecordFactory()(); expect(note.schools).toEqual([entity.getId()]); entity = new User(); component.entity = entity; component.filter = undefined; await component.ngOnInit(); - note = component.generateNewRecordFactory()(); + note = component.createNewRecordFactory()(); expect(note.relatedEntities).toEqual([entity.getId(true)]); entity = new ChildSchoolRelation(); @@ -73,7 +73,7 @@ describe("NotesRelatedToEntityComponent", () => { component.entity = entity; component.filter = undefined; await component.ngOnInit(); - note = component.generateNewRecordFactory()(); + note = component.createNewRecordFactory()(); expect(note.relatedEntities).toEqual([entity.getId(true)]); expect(note.children).toEqual(["someChild"]); expect(note.schools).toEqual(["someSchool"]); @@ -110,7 +110,7 @@ describe("NotesRelatedToEntityComponent", () => { component.entity = customEntity; component.ngOnInit(); - const newNote = component.generateNewRecordFactory()(); + const newNote = component.createNewRecordFactory()(); expect(newNote.relatedEntities).toContain(customEntity.getId(true)); expect(newNote.relatedEntities).toContain(customEntity.links[0]); diff --git a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts index 8df4e4a6c2..97c62e620a 100644 --- a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts +++ b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.ts @@ -44,7 +44,7 @@ export class NotesRelatedToEntityComponent extends RelatedEntitiesComponent note?.getColor(); - newRecordFactory = this.generateNewRecordFactory(); + newRecordFactory = this.createNewRecordFactory(); constructor( private childrenService: ChildrenService, @@ -69,7 +69,7 @@ export class NotesRelatedToEntityComponent extends RelatedEntitiesComponent { const newNote = super.createNewRecordFactory()(); //TODO: generalize this code - possibly by only using relatedEntities to link other records here? see #1501 diff --git a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.spec.ts b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.spec.ts index 3d9ddf9b8b..78cd4b64d2 100644 --- a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.spec.ts +++ b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.spec.ts @@ -73,7 +73,7 @@ describe("ChildSchoolOverviewComponent", () => { component.entity = child; await component.ngOnInit(); - const newRelation = component.generateNewRecordFactory()(); + const newRelation = component.createNewRecordFactory()(); expect(newRelation.childId).toEqual(child.getId()); expect( @@ -87,7 +87,7 @@ describe("ChildSchoolOverviewComponent", () => { component.entity = new School("testID"); await component.ngOnInit(); - const newRelation = component.generateNewRecordFactory()(); + const newRelation = component.createNewRecordFactory()(); expect(newRelation).toBeInstanceOf(ChildSchoolRelation); expect(newRelation.schoolId).toBe("testID"); diff --git a/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.html b/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.html index 14662fa85c..66a65f73df 100644 --- a/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.html +++ b/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.html @@ -9,7 +9,7 @@ [records]="data" [filter]="filter" [customColumns]="_columns" - [newRecordFactory]="generateNewRecordFactory()" + [newRecordFactory]="createNewRecordFactory()" [getBackgroundColor]=" hasCurrentlyActiveEntry && showInactive ? backgroundColorFn : undefined " diff --git a/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.spec.ts b/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.spec.ts index 53a24bfd93..9cc97bf04f 100644 --- a/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.spec.ts +++ b/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.spec.ts @@ -97,7 +97,7 @@ describe("RelatedTimePeriodEntitiesComponent", () => { component.entity = child; await component.ngOnInit(); - const newRelation = component.generateNewRecordFactory()(); + const newRelation = component.createNewRecordFactory()(); expect(newRelation.childId).toEqual(child.getId()); }); @@ -114,7 +114,7 @@ describe("RelatedTimePeriodEntitiesComponent", () => { component.entity = child; await component.ngOnInit(); - const newRelation = component.generateNewRecordFactory()(); + const newRelation = component.createNewRecordFactory()(); expect( moment(existingRelation.end) diff --git a/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.ts b/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.ts index aebec695e6..cbb99ec93d 100644 --- a/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.ts +++ b/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.ts @@ -78,7 +78,7 @@ export class RelatedTimePeriodEntitiesComponent } } - generateNewRecordFactory() { + override createNewRecordFactory() { return () => { const newRelation = super.createNewRecordFactory()(); From 48975c10e2c11d03e31e987940d9b16b5ead9d57 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 24 Jan 2024 15:37:39 +0100 Subject: [PATCH 19/29] fixed color highlighting for child school overview --- .../model/educational-material.ts | 2 ++ .../child-school-overview.component.ts | 5 ++--- .../related-time-period-entities.component.html | 4 +--- .../related-time-period-entities.component.ts | 11 ----------- 4 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/app/child-dev-project/children/educational-material/model/educational-material.ts b/src/app/child-dev-project/children/educational-material/model/educational-material.ts index c90bbdb3ef..d67ae19ca5 100644 --- a/src/app/child-dev-project/children/educational-material/model/educational-material.ts +++ b/src/app/child-dev-project/children/educational-material/model/educational-material.ts @@ -21,6 +21,7 @@ import { DatabaseField } from "../../../../core/entity/database-field.decorator" import { ConfigurableEnumValue } from "../../../../core/basic-datatypes/configurable-enum/configurable-enum.interface"; import { EntityDatatype } from "../../../../core/basic-datatypes/entity/entity.datatype"; import { Child } from "../../model/child"; +import { PLACEHOLDERS } from "../../../../core/entity/schema/entity-schema-field"; @DatabaseEntity("EducationalMaterial") export class EducationalMaterial extends Entity { @@ -35,6 +36,7 @@ export class EducationalMaterial extends Entity { child: string; // id of Child entity @DatabaseField({ label: $localize`:Date on which the material has been borrowed:Date`, + defaultValue: PLACEHOLDERS.NOW, }) date: Date; @DatabaseField({ diff --git a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts index f2d4c72d6f..d29431c031 100644 --- a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts +++ b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.ts @@ -62,12 +62,11 @@ export class ChildSchoolOverviewComponent ]; } - override ngOnInit(): Promise { + override async ngOnInit(): Promise { this.mode = this.entity.getType().toLowerCase() as any; this.showInactive = this.mode === "child"; - // TODO toggling inactive does not hide/show color on school details this.switchRelatedEntityColumnForMode(); - return super.ngOnInit(); + await super.ngOnInit(); } override getData() { diff --git a/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.html b/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.html index 66a65f73df..3582e6a7e0 100644 --- a/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.html +++ b/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.html @@ -10,9 +10,7 @@ [filter]="filter" [customColumns]="_columns" [newRecordFactory]="createNewRecordFactory()" - [getBackgroundColor]=" - hasCurrentlyActiveEntry && showInactive ? backgroundColorFn : undefined - " + [getBackgroundColor]="showInactive ? backgroundColorFn : undefined" [clickMode]="clickMode" [(showInactive)]="showInactive" > diff --git a/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.ts b/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.ts index cbb99ec93d..4cb9a05edc 100644 --- a/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.ts +++ b/src/app/core/entity-details/related-time-period-entities/related-time-period-entities.component.ts @@ -44,7 +44,6 @@ export class RelatedTimePeriodEntitiesComponent // also see super class for Inputs @Input() single = true; - @Input() showInactive = false; @Input() clickMode: "popup" | "navigate" = "popup"; @Input() set columns(value: FormFieldConfig[]) { @@ -65,17 +64,7 @@ export class RelatedTimePeriodEntitiesComponent async ngOnInit() { await super.ngOnInit(); - this.onIsActiveFilterChange(); - } - - onIsActiveFilterChange() { this.hasCurrentlyActiveEntry = this.data.some((record) => record.isActive); - - if (this.showInactive) { - this.backgroundColorFn = (r: E) => r.getColor(); - } else { - this.backgroundColorFn = undefined; // Do not highlight active ones when only active are shown - } } override createNewRecordFactory() { From 5a165647a0e3f25b5562f357d9ab0347cea0f8c6 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 24 Jan 2024 16:17:46 +0100 Subject: [PATCH 20/29] small cleanups --- .../attendance-details/attendance-details.component.ts | 2 +- .../notes-related-to-entity.component.spec.ts | 3 +++ .../related-entities/related-entities.component.ts | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/child-dev-project/attendance/attendance-details/attendance-details.component.ts b/src/app/child-dev-project/attendance/attendance-details/attendance-details.component.ts index 60a04da918..ac5950ac1a 100644 --- a/src/app/child-dev-project/attendance/attendance-details/attendance-details.component.ts +++ b/src/app/child-dev-project/attendance/attendance-details/attendance-details.component.ts @@ -34,7 +34,7 @@ import { EntitiesTableComponent } from "../../../core/common-components/entities standalone: true, }) export class AttendanceDetailsComponent { - @Input() entity: ActivityAttendance = new ActivityAttendance(); + @Input() entity: ActivityAttendance; @Input() forChild: string; EventNote = EventNote; diff --git a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts index e3c9138a52..792d66f7b2 100644 --- a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts +++ b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts @@ -103,6 +103,7 @@ describe("NotesRelatedToEntityComponent", () => { ]; customEntity.childrenLink = "child-without-prefix"; + const schemaBefore = Note.schema.get("relatedEntities").additional; Note.schema.get("relatedEntities").additional = [ Child.ENTITY_TYPE, EntityWithRelations.ENTITY_TYPE, @@ -118,6 +119,8 @@ describe("NotesRelatedToEntityComponent", () => { expect(newNote.relatedEntities).toContain( Entity.createPrefixedId(Child.ENTITY_TYPE, customEntity.childrenLink), ); + + Note.schema.get("relatedEntities").additional = schemaBefore; }); it("should only add related notes after the initial load", async () => { diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index f032f629b8..0244f5b624 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -109,7 +109,7 @@ export class RelatedEntitiesComponent implements OnInit { protected getProperty(): string | string[] { const relType = this.entity.getType(); - const found = [...this.entityCtr.schema].filter(([prop, schema]) => { + const found = [...this.entityCtr.schema].filter(([, schema]) => { const additional = schema.additional; switch (schema.dataType) { case EntityDatatype.dataType: From 2957df5608bfd446ff8d3cadb339095837b0dd7b Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 24 Jan 2024 16:28:27 +0100 Subject: [PATCH 21/29] code cleanups --- .../notes-related-to-entity.component.spec.ts | 27 ------------------- .../related-entities.component.spec.ts | 6 ++--- .../related-entities.component.ts | 20 +++++++------- 3 files changed, 13 insertions(+), 40 deletions(-) diff --git a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts index 792d66f7b2..d5b60ac222 100644 --- a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts +++ b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts @@ -1,6 +1,5 @@ import { NotesRelatedToEntityComponent } from "./notes-related-to-entity.component"; import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; -import { ChildrenService } from "../../children/children.service"; import { Note } from "../model/note"; import { Child } from "../../children/model/child"; import { MockedTestingModule } from "../../../utils/mocked-testing.module"; @@ -8,7 +7,6 @@ import { Entity } from "../../../core/entity/model/entity"; import { School } from "../../schools/model/school"; import { DatabaseEntity } from "../../../core/entity/database-entity.decorator"; import { DatabaseField } from "../../../core/entity/database-field.decorator"; -import { EntityMapperService } from "../../../core/entity/entity-mapper/entity-mapper.service"; import { User } from "../../../core/user/user"; import { ChildSchoolRelation } from "../../children/model/childSchoolRelation"; @@ -122,29 +120,4 @@ describe("NotesRelatedToEntityComponent", () => { Note.schema.get("relatedEntities").additional = schemaBefore; }); - - it("should only add related notes after the initial load", async () => { - const child = new Child(); - const data = [new Note(), new Note()]; - data.forEach((n) => n.addChild(child)); - const childrenService = TestBed.inject(ChildrenService); - spyOn(childrenService, "getNotesRelatedTo").and.resolveTo([...data]); - component.entity = child; - - await component.ngOnInit(); - expect(component.data).toEqual(data); - - const relatedNote = new Note(); - relatedNote.addChild(child); - await TestBed.inject(EntityMapperService).save(relatedNote); - const expectedData = jasmine.arrayWithExactContents( - data.concat(relatedNote), - ); - expect(component.data).toEqual(expectedData); - - const unrelatedNote = new Note(); - await TestBed.inject(EntityMapperService).save(unrelatedNote); - // TODO is actually filtering data necessary here? - // expect(component.data).toEqual(expectedData); - }); }); diff --git a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts index ffe71ac133..2558fe00fb 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts @@ -54,13 +54,13 @@ describe("RelatedEntitiesComponent", () => { expect(component.filter).toEqual({ childId: child.getId() }); }); - it("should also included the provided filter", async () => { + it("should also include the provided filter", async () => { const child = new Child(); const filter = { start: { $exists: true } }; component.entity = child; component.entityType = ChildSchoolRelation.ENTITY_TYPE; - component.filter = filter; + component.filter = { ...filter }; await component.ngOnInit(); expect(component.filter).toEqual({ ...filter, childId: child.getId() }); @@ -108,7 +108,7 @@ describe("RelatedEntitiesComponent", () => { expect(component.data).toEqual([]); })); - it("should support multiple properties", async () => { + it("should support multiple related properties", async () => { @DatabaseEntity("MultiPropTest") class MultiPropTest extends Entity { @DatabaseField({ diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index 0244f5b624..a2c731ee43 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -109,16 +109,16 @@ export class RelatedEntitiesComponent implements OnInit { protected getProperty(): string | string[] { const relType = this.entity.getType(); - const found = [...this.entityCtr.schema].filter(([, schema]) => { - const additional = schema.additional; - switch (schema.dataType) { - case EntityDatatype.dataType: - case EntityArrayDatatype.dataType: - return Array.isArray(additional) - ? additional.includes(relType) - : additional === relType; - } - }); + const found = [...this.entityCtr.schema].filter( + ([, { dataType, additional }]) => { + const entityDatatype = + dataType === EntityDatatype.dataType || + dataType === EntityArrayDatatype.dataType; + return entityDatatype && Array.isArray(additional) + ? additional.includes(relType) + : additional === relType; + }, + ); return found.length === 1 ? found[0][0] : found.map(([key]) => key); } From bbc96e901b0f487de01ed15d79ff19a90615b0f5 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 31 Jan 2024 13:04:39 +0100 Subject: [PATCH 22/29] Update src/app/core/entity-details/related-entities/related-entities.component.ts Co-authored-by: Sebastian --- .../related-entities/related-entities.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index a2c731ee43..c00ccb6bbd 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -158,7 +158,6 @@ export class RelatedEntitiesComponent implements OnInit { } createNewRecordFactory() { - // TODO has a similar purpose like FilterService.alignEntityWithFilter return () => { const rec = new this.entityCtr(); this.filterService.alignEntityWithFilter(rec, this.filter); From 0e1c6c693a6c2c3418929e1130b0b6e8b458cdcf Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 1 Feb 2024 22:32:05 +0100 Subject: [PATCH 23/29] fixed setting of invalid properties --- src/app/core/filter/filter.service.spec.ts | 14 ++++++++++++++ src/app/core/filter/filter.service.ts | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/src/app/core/filter/filter.service.spec.ts b/src/app/core/filter/filter.service.spec.ts index fa69e507b7..4ec78b85f6 100644 --- a/src/app/core/filter/filter.service.spec.ts +++ b/src/app/core/filter/filter.service.spec.ts @@ -8,6 +8,7 @@ import { createTestingConfigurableEnumService } from "../basic-datatypes/configu import moment from "moment"; import { DataFilter } from "./filters/filters"; import { Child } from "../../child-dev-project/children/model/child"; +import { ChildSchoolRelation } from "../../child-dev-project/children/model/childSchoolRelation"; describe("FilterService", () => { let service: FilterService; @@ -80,6 +81,19 @@ describe("FilterService", () => { expect(note.children).toEqual([child.getId()]); }); + it("should not set properties without a schema", () => { + const filter = { + childId: `${Child.ENTITY_TYPE}:some-id`, + isActive: false, + } as DataFilter; + + const relation = new ChildSchoolRelation(); + service.alignEntityWithFilter(relation, filter); + + expect(relation.childId).toEqual(`${Child.ENTITY_TYPE}:some-id`); + expect(relation.isActive).toBeTrue(); + }); + it("should support filtering dates with day granularity", () => { const n1 = Note.create(moment("2022-01-01").toDate()); const n2 = Note.create(moment("2022-01-02").toDate()); diff --git a/src/app/core/filter/filter.service.ts b/src/app/core/filter/filter.service.ts index eb1830093c..e65b8b33b1 100644 --- a/src/app/core/filter/filter.service.ts +++ b/src/app/core/filter/filter.service.ts @@ -92,6 +92,12 @@ export class FilterService { [key, value] = this.transformNestedKey(key, value); } const property = schema.get(key); + + if (!property) { + // not a schema property + return; + } + if (property?.dataType === "configurable-enum") { value = this.parseConfigurableEnumValue(property, value); } From 8791493e0d4eec01dbbbd1ac9a87bdf3e075b038 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 2 Feb 2024 13:01:41 +0100 Subject: [PATCH 24/29] added option to set relation property --- .../related-entities.component.spec.ts | 18 +++++++++++++++--- .../related-entities.component.ts | 6 +++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts index 2558fe00fb..087003c47e 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts @@ -145,18 +145,18 @@ describe("RelatedEntitiesComponent", () => { ); }); - it("should align the filter with the provided property", async () => { + it("should align the filter with the related properties", async () => { @DatabaseEntity("PropTest") class PropTest extends Entity {} component.entityType = PropTest.ENTITY_TYPE; - const entity = new Child(); PropTest.schema.set("singleRelation", { dataType: EntityDatatype.dataType, additional: Child.ENTITY_TYPE, }); - component.entity = entity; + component.entity = new Child(); component.filter = undefined; + component.property = undefined; await component.ngOnInit(); expect(component.filter).toEqual({ singleRelation: component.entity.getId(), @@ -168,6 +168,7 @@ describe("RelatedEntitiesComponent", () => { }); component.entity = new School(); component.filter = undefined; + component.property = undefined; await component.ngOnInit(); expect(component.filter).toEqual({ arrayRelation: { $elemMatch: { $eq: component.entity.getId() } }, @@ -179,6 +180,7 @@ describe("RelatedEntitiesComponent", () => { }); component.entity = new ChildSchoolRelation(); component.filter = undefined; + component.property = undefined; await component.ngOnInit(); expect(component.filter).toEqual({ multiTypeRelation: { $elemMatch: { $eq: component.entity.getId() } }, @@ -187,6 +189,7 @@ describe("RelatedEntitiesComponent", () => { // Now with 2 relations ("singleRelation" and "multiTypeRelation") component.entity = new Child(); component.filter = undefined; + component.property = undefined; await component.ngOnInit(); expect(component.filter).toEqual({ $or: [ @@ -196,5 +199,14 @@ describe("RelatedEntitiesComponent", () => { }, ], }); + + // preselected property should not be changed + component.entity = new Child(); + component.filter = undefined; + component.property = "singleRelation"; + await component.ngOnInit(); + expect(component.filter).toEqual({ + singleRelation: component.entity.getId(), + }); }); }); diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index c00ccb6bbd..88aa875f8a 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -44,9 +44,9 @@ export class RelatedEntitiesComponent implements OnInit { /** * Property name of the related entities (type given in this.entityType) that holds the entity id * to be matched with the id of the current main entity (given in this.entity). - * This is automatically inferred and does not need to be set. + * If not explicitly set, this will be inferred based on the defined relations between the entities. */ - protected property: string | string[]; + @Input() property: string | string[]; /** * Columns to be displayed in the table @@ -91,7 +91,7 @@ export class RelatedEntitiesComponent implements OnInit { } async ngOnInit() { - this.property = this.getProperty(); + this.property = this.property ?? this.getProperty(); this.data = await this.getData(); this.filter = this.initFilter(); From 209e4aad409f9eaac203e8730bea0a4906fa1bd1 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 2 Feb 2024 13:38:11 +0100 Subject: [PATCH 25/29] added fallback handling to Todos list if multiple related properties exist --- .../related-entities.component.ts | 1 - .../todos-related-to-entity.component.spec.ts | 108 ++++++++++++++---- .../todos-related-to-entity.component.ts | 2 +- 3 files changed, 88 insertions(+), 23 deletions(-) diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index 88aa875f8a..d8dc764a37 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -140,7 +140,6 @@ export class RelatedEntitiesComponent implements OnInit { } private getFilterForProperty(property: string) { - // TODO doesnt work with full ids const isArray = isArrayProperty(this.entityCtr, property); const filter = isArray ? { $elemMatch: { $eq: this.entity.getId() } } diff --git a/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.spec.ts b/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.spec.ts index cf98c86123..fcb2f2da5f 100644 --- a/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.spec.ts +++ b/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.spec.ts @@ -1,42 +1,108 @@ -import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { TodosRelatedToEntityComponent } from "./todos-related-to-entity.component"; -import { DatabaseIndexingService } from "../../../core/entity/database-indexing/database-indexing.service"; -import { MockedTestingModule } from "../../../utils/mocked-testing.module"; import { Entity } from "../../../core/entity/model/entity"; +import { DatabaseTestingModule } from "../../../utils/database-testing.module"; +import { Child } from "../../../child-dev-project/children/model/child"; +import { Todo } from "../model/todo"; +import { EntityMapperService } from "../../../core/entity/entity-mapper/entity-mapper.service"; +import { School } from "../../../child-dev-project/schools/model/school"; +import { User } from "../../../core/user/user"; +import { Database } from "../../../core/database/database"; +import { DatabaseIndexingService } from "../../../core/entity/database-indexing/database-indexing.service"; describe("TodosRelatedToEntityComponent", () => { let component: TodosRelatedToEntityComponent; let fixture: ComponentFixture; - let mockIndexingService: jasmine.SpyObj; - - beforeEach(async () => { - mockIndexingService = jasmine.createSpyObj([ - "generateIndexOnProperty", - "queryIndexDocs", - ]); - mockIndexingService.queryIndexDocs.and.resolveTo([]); - - await TestBed.configureTestingModule({ - imports: [TodosRelatedToEntityComponent, MockedTestingModule.withState()], - providers: [ - { - provide: DatabaseIndexingService, - useValue: mockIndexingService, - }, - ], + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TodosRelatedToEntityComponent, DatabaseTestingModule], }).compileComponents(); + })); + beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(TodosRelatedToEntityComponent); component = fixture.componentInstance; component.entity = new Entity(); fixture.detectChanges(); - }); + })); + + afterEach(() => TestBed.inject(Database).destroy()); it("should create", () => { expect(component).toBeTruthy(); }); + + it("should load data from index when having a single relation", async () => { + const child = new Child(); + const relatedTodo = new Todo(); + relatedTodo.relatedEntities = [child.getId(), new School().getId()]; + const unrelatedTodo = new Todo(); + unrelatedTodo.relatedEntities = [new Child().getId()]; + await TestBed.inject(EntityMapperService).saveAll([ + relatedTodo, + unrelatedTodo, + ]); + const indexSpy = spyOn( + TestBed.inject(DatabaseIndexingService), + "queryIndexDocs", + ).and.callThrough(); + + component.entity = child; + component.property = undefined; + component.filter = undefined; + await component.ngOnInit(); + + expect(indexSpy).toHaveBeenCalled(); + expect(component.filter).toEqual({ + relatedEntities: { $elemMatch: { $eq: child.getId() } }, + }); + expect(component.data).toEqual([relatedTodo]); + }); + + it("should load data with entity mapper when having multiple relations", async () => { + const relatedSchema = Todo.schema.get("relatedEntities"); + const originalAdditional = relatedSchema.additional; + relatedSchema.additional = [User.ENTITY_TYPE]; + const user = new User(); + const relatedTodo = new Todo(); + relatedTodo.relatedEntities = [user.getId()]; + const relatedTodo2 = new Todo(); + relatedTodo2.assignedTo = [user.getId()]; + relatedTodo2.relatedEntities = [new User().getId()]; + const unrelatedTodo = new Todo(); + unrelatedTodo.relatedEntities = [new User().getId()]; + const entityMapper = TestBed.inject(EntityMapperService); + await entityMapper.saveAll([relatedTodo, relatedTodo2, unrelatedTodo]); + const loadTypeSpy = spyOn(entityMapper, "loadType").and.callThrough(); + + component.entity = user; + component.property = undefined; + component.filter = undefined; + await component.ngOnInit(); + + expect(loadTypeSpy).toHaveBeenCalledWith(Todo); + expect(component.data).toEqual( + jasmine.arrayWithExactContents([ + relatedTodo, + relatedTodo2, + unrelatedTodo, + ]), + ); + expect(component.filter).toEqual({ + $or: [ + { + assignedTo: { $elemMatch: { $eq: user.getId() } }, + }, + { + relatedEntities: { $elemMatch: { $eq: user.getId() } }, + }, + ], + }); + + relatedSchema.additional = originalAdditional; + }); }); diff --git a/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.ts b/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.ts index 95f51c2b4f..ab630b27f6 100644 --- a/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.ts +++ b/src/app/features/todos/todos-related-to-entity/todos-related-to-entity.component.ts @@ -59,7 +59,7 @@ export class TodosRelatedToEntityComponent extends RelatedEntitiesComponent Date: Fri, 2 Feb 2024 14:11:34 +0100 Subject: [PATCH 26/29] fixed tests --- .../notes-related-to-entity.component.spec.ts | 4 ++ .../child-school-overview.component.spec.ts | 66 ++++++++++--------- .../related-entities.component.spec.ts | 49 ++++++++------ 3 files changed, 70 insertions(+), 49 deletions(-) diff --git a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts index 45c5990d29..007f39907c 100644 --- a/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts +++ b/src/app/child-dev-project/notes/notes-related-to-entity/notes-related-to-entity.component.spec.ts @@ -47,6 +47,7 @@ describe("NotesRelatedToEntityComponent", () => { let entity: Entity = new Child(); component.entity = entity; component.filter = undefined; + component.property = undefined; await component.ngOnInit(); let note = component.createNewRecordFactory()(); expect(note.children).toEqual([entity.getId()]); @@ -54,6 +55,7 @@ describe("NotesRelatedToEntityComponent", () => { entity = new School(); component.entity = entity; component.filter = undefined; + component.property = undefined; await component.ngOnInit(); note = component.createNewRecordFactory()(); expect(note.schools).toEqual([entity.getId()]); @@ -61,6 +63,7 @@ describe("NotesRelatedToEntityComponent", () => { entity = new User(); component.entity = entity; component.filter = undefined; + component.property = undefined; await component.ngOnInit(); note = component.createNewRecordFactory()(); expect(note.relatedEntities).toEqual([entity.getId()]); @@ -70,6 +73,7 @@ describe("NotesRelatedToEntityComponent", () => { entity["schoolId"] = `${Child.ENTITY_TYPE}:someSchool`; component.entity = entity; component.filter = undefined; + component.property = undefined; await component.ngOnInit(); note = component.createNewRecordFactory()(); expect(note.relatedEntities).toEqual([entity.getId()]); diff --git a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.spec.ts b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.spec.ts index 1fb828476c..290095f548 100644 --- a/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.spec.ts +++ b/src/app/child-dev-project/schools/child-school-overview/child-school-overview.component.spec.ts @@ -1,4 +1,10 @@ -import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; +import { + ComponentFixture, + fakeAsync, + TestBed, + tick, + waitForAsync, +} from "@angular/core/testing"; import { ChildSchoolOverviewComponent } from "./child-school-overview.component"; import moment from "moment"; @@ -14,10 +20,6 @@ describe("ChildSchoolOverviewComponent", () => { let mockChildrenService: jasmine.SpyObj; - const testChild = new Child("22"); - const inactive = new ChildSchoolRelation("r2"); - inactive.end = moment().subtract("1", "week").toDate(); - beforeEach(waitForAsync(() => { mockChildrenService = jasmine.createSpyObj(["queryRelations"]); mockChildrenService.queryRelations.and.resolveTo([ @@ -28,39 +30,39 @@ describe("ChildSchoolOverviewComponent", () => { imports: [ChildSchoolOverviewComponent, MockedTestingModule.withState()], providers: [{ provide: ChildrenService, useValue: mockChildrenService }], }).compileComponents(); - })); - - beforeEach(() => { fixture = TestBed.createComponent(ChildSchoolOverviewComponent); component = fixture.componentInstance; - component.entity = testChild; - fixture.detectChanges(); - }); + })); it("should create", () => { expect(component).toBeTruthy(); }); - it("it calls children service with id from passed child", async () => { - await component.ngOnInit(); + it("it calls children service with id from passed child", fakeAsync(() => { + component.entity = new Child(); + + fixture.detectChanges(); + tick(); + expect(mockChildrenService.queryRelations).toHaveBeenCalledWith( - testChild.getId(), + component.entity.getId(), ); - }); + })); - it("it detects mode and uses correct index to load data ", async () => { + it("it detects mode and uses correct index to load data ", fakeAsync(() => { const testSchool = new School(); component.entity = testSchool; - await component.ngOnInit(); + fixture.detectChanges(); + tick(); expect(component.mode).toBe("school"); expect(mockChildrenService.queryRelations).toHaveBeenCalledWith( testSchool.getId(), ); - }); + })); - it("should create a relation with the child ID", async () => { + it("should create a relation with the child ID", fakeAsync(() => { const child = new Child(); const existingRelation = new ChildSchoolRelation(); existingRelation.childId = child.getId(); @@ -69,7 +71,8 @@ describe("ChildSchoolOverviewComponent", () => { mockChildrenService.queryRelations.and.resolveTo([existingRelation]); component.entity = child; - await component.ngOnInit(); + fixture.detectChanges(); + tick(); const newRelation = component.createNewRecordFactory()(); @@ -79,31 +82,34 @@ describe("ChildSchoolOverviewComponent", () => { .add(1, "day") .isSame(newRelation.start, "day"), ).toBeTrue(); - }); + })); - it("should create a relation with the school ID", async () => { + it("should create a relation with the school ID", fakeAsync(() => { component.entity = new School("testID"); - await component.ngOnInit(); + fixture.detectChanges(); + tick(); const newRelation = component.createNewRecordFactory()(); expect(newRelation).toBeInstanceOf(ChildSchoolRelation); expect(newRelation.schoolId).toBe("School:testID"); - }); + })); - it("should show archived school in 'child' mode", async () => { + it("should show archived school in 'child' mode", fakeAsync(() => { component.entity = new Child(); - await component.ngOnInit(); + fixture.detectChanges(); + tick(); expect(component.showInactive).toBeTrue(); - }); + })); - it("should not show archived children in 'school' mode", async () => { + it("should not show archived children in 'school' mode", fakeAsync(() => { component.entity = new School(); - await component.ngOnInit(); + fixture.detectChanges(); + tick(); expect(component.showInactive).toBeFalse(); - }); + })); }); diff --git a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts index 087003c47e..3b3c20aa52 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.spec.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.spec.ts @@ -33,57 +33,63 @@ describe("RelatedEntitiesComponent", () => { RelatedEntitiesComponent, ); component = fixture.componentInstance; - component.entity = new Child(); - component.entityType = ChildSchoolRelation.ENTITY_TYPE; - component.columns = []; - fixture.detectChanges(); }); it("should create", () => { expect(component).toBeTruthy(); }); - it("should create a filter for the passed entity", async () => { + it("should create a filter for the passed entity", fakeAsync(() => { const child = new Child(); const columns = ["start", "end", "schoolId"]; component.entity = child; component.entityType = ChildSchoolRelation.ENTITY_TYPE; component.columns = columns; - await component.ngOnInit(); + fixture.detectChanges(); + tick(); expect(component.filter).toEqual({ childId: child.getId() }); - }); + })); - it("should also include the provided filter", async () => { + it("should also include the provided filter", fakeAsync(() => { const child = new Child(); const filter = { start: { $exists: true } }; component.entity = child; component.entityType = ChildSchoolRelation.ENTITY_TYPE; component.filter = { ...filter }; - await component.ngOnInit(); + fixture.detectChanges(); + tick(); - expect(component.filter).toEqual({ ...filter, childId: child.getId() }); - }); + expect(component.filter).toEqual({ + ...filter, + childId: child.getId(), + // added by table + isActive: true, + }); + })); - it("should create a new entity that references the related one", async () => { + it("should create a new entity that references the related one", fakeAsync(() => { const related = new Child(); component.entity = related; component.entityType = ChildSchoolRelation.ENTITY_TYPE; component.columns = []; - await component.ngOnInit(); + fixture.detectChanges(); + tick(); const newEntity = component.createNewRecordFactory()(); expect(newEntity instanceof ChildSchoolRelation).toBeTrue(); expect(newEntity["childId"]).toBe(related.getId()); - }); + })); it("should add a new entity that was created after the initial loading to the table", fakeAsync(() => { const entityUpdates = new Subject>(); const entityMapper = TestBed.inject(EntityMapperService); spyOn(entityMapper, "receiveUpdates").and.returnValue(entityUpdates); - component.ngOnInit(); + component.entity = new Child(); + component.entityType = ChildSchoolRelation.ENTITY_TYPE; + fixture.detectChanges(); tick(); const entity = new ChildSchoolRelation(); @@ -98,8 +104,10 @@ describe("RelatedEntitiesComponent", () => { const entityMapper = TestBed.inject(EntityMapperService); spyOn(entityMapper, "receiveUpdates").and.returnValue(entityUpdates); const entity = new ChildSchoolRelation(); + component.entity = new Child(); + component.entityType = entity.getType(); component.data = [entity]; - component.ngOnInit(); + fixture.detectChanges(); tick(); entityUpdates.next({ entity: entity, type: "remove" }); @@ -108,7 +116,7 @@ describe("RelatedEntitiesComponent", () => { expect(component.data).toEqual([]); })); - it("should support multiple related properties", async () => { + it("should support multiple related properties", fakeAsync(() => { @DatabaseEntity("MultiPropTest") class MultiPropTest extends Entity { @DatabaseField({ @@ -128,7 +136,8 @@ describe("RelatedEntitiesComponent", () => { component.entityType = MultiPropTest.ENTITY_TYPE; component.filter = {}; - await component.ngOnInit(); + fixture.detectChanges(); + tick(); // filter matching relations at any of the available props expect(component.filter).toEqual({ @@ -136,6 +145,8 @@ describe("RelatedEntitiesComponent", () => { { singleChild: child.getId() }, { multiEntities: { $elemMatch: { $eq: child.getId() } } }, ], + // is added inside table + isActive: true, }); // no special properties set when creating a new entity expectEntitiesToMatch( @@ -143,7 +154,7 @@ describe("RelatedEntitiesComponent", () => { [new MultiPropTest()], true, ); - }); + })); it("should align the filter with the related properties", async () => { @DatabaseEntity("PropTest") From 1e6ca4e29de1e64c584fa683f9746bce4f603a96 Mon Sep 17 00:00:00 2001 From: Sebastian Leidig Date: Fri, 2 Feb 2024 23:05:49 +0100 Subject: [PATCH 27/29] remove AserComponent in favor of simple RelatedEntities --- .../concepts/configuration.md | 7 ++-- .../aser-component/aser.component.spec.ts | 38 ------------------- .../aser/aser-component/aser.component.ts | 30 --------------- .../children/children-components.ts | 7 ---- src/app/core/config/config-fix.ts | 32 +++++++++++++++- 5 files changed, 35 insertions(+), 79 deletions(-) delete mode 100644 src/app/child-dev-project/children/aser/aser-component/aser.component.spec.ts delete mode 100644 src/app/child-dev-project/children/aser/aser-component/aser.component.ts diff --git a/doc/compodoc_sources/concepts/configuration.md b/doc/compodoc_sources/concepts/configuration.md index eff934904b..7a591536ba 100644 --- a/doc/compodoc_sources/concepts/configuration.md +++ b/doc/compodoc_sources/concepts/configuration.md @@ -265,12 +265,13 @@ The component configuration requires another `"title"`, the `"component"` that s "title": "Education", "components": [ { - "title": "SchoolHistory", + "title": "School History", "component": "PreviousSchools" }, { - "title": "ASER Results", - "component": "Aser" + "title": "Literacy Test Results", + "component": "RelatedEntities", + "config": {} } ] } diff --git a/src/app/child-dev-project/children/aser/aser-component/aser.component.spec.ts b/src/app/child-dev-project/children/aser/aser-component/aser.component.spec.ts deleted file mode 100644 index 3186c44257..0000000000 --- a/src/app/child-dev-project/children/aser/aser-component/aser.component.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; -import { AserComponent } from "./aser.component"; -import { ChildrenService } from "../../children.service"; -import { Child } from "../../model/child"; -import { of } from "rxjs"; -import { MockedTestingModule } from "../../../../utils/mocked-testing.module"; - -describe("AserComponent", () => { - let component: AserComponent; - let fixture: ComponentFixture; - - const mockChildrenService = { - getChild: () => { - return of([new Child("22")]); - }, - getAserResultsOfChild: () => { - return of([]); - }, - }; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [AserComponent, MockedTestingModule.withState()], - providers: [{ provide: ChildrenService, useValue: mockChildrenService }], - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(AserComponent); - component = fixture.componentInstance; - component.entity = new Child("22"); - fixture.detectChanges(); - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/child-dev-project/children/aser/aser-component/aser.component.ts b/src/app/child-dev-project/children/aser/aser-component/aser.component.ts deleted file mode 100644 index 262a4777b4..0000000000 --- a/src/app/child-dev-project/children/aser/aser-component/aser.component.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Component, Input } from "@angular/core"; -import { Aser } from "../model/aser"; -import { Child } from "../../model/child"; -import { DynamicComponent } from "../../../../core/config/dynamic-components/dynamic-component.decorator"; -import { EntitiesTableComponent } from "../../../../core/common-components/entities-table/entities-table.component"; - -import { FormFieldConfig } from "../../../../core/common-components/entity-form/FormConfig"; -import { RelatedEntitiesComponent } from "../../../../core/entity-details/related-entities/related-entities.component"; - -@DynamicComponent("Aser") -@Component({ - selector: "app-aser", - templateUrl: - "../../../../core/entity-details/related-entities/related-entities.component.html", - standalone: true, - imports: [EntitiesTableComponent], -}) -export class AserComponent extends RelatedEntitiesComponent { - @Input() entity: Child; - entityCtr = Aser; - - override _columns: FormFieldConfig[] = [ - { id: "date", visibleFrom: "xs" }, - { id: "math", visibleFrom: "xs" }, - { id: "english", visibleFrom: "xs" }, - { id: "hindi", visibleFrom: "md" }, - { id: "bengali", visibleFrom: "md" }, - { id: "remarks", visibleFrom: "md" }, - ]; -} diff --git a/src/app/child-dev-project/children/children-components.ts b/src/app/child-dev-project/children/children-components.ts index c39c2287e0..2f528ddd41 100644 --- a/src/app/child-dev-project/children/children-components.ts +++ b/src/app/child-dev-project/children/children-components.ts @@ -23,13 +23,6 @@ export const childrenComponents: ComponentTuple[] = [ "./children-list/recent-attendance-blocks/recent-attendance-blocks.component" ).then((c) => c.RecentAttendanceBlocksComponent), ], - [ - "Aser", - () => - import("./aser/aser-component/aser.component").then( - (c) => c.AserComponent, - ), - ], [ "ChildBlock", () => diff --git a/src/app/core/config/config-fix.ts b/src/app/core/config/config-fix.ts index e17a7b7711..f9a570a141 100644 --- a/src/app/core/config/config-fix.ts +++ b/src/app/core/config/config-fix.ts @@ -549,7 +549,37 @@ export const defaultJsonConfig = { }, { "title": $localize`:Title inside a panel:ASER Results`, - "component": "Aser" + "component": "RelatedEntities", + "config": { + "entityType": "Aser", + "property": "child", + "columns": [ + { + "id": "date", + "visibleFrom": "xs" + }, + { + "id": "math", + "visibleFrom": "xs" + }, + { + "id": "english", + "visibleFrom": "xs" + }, + { + "id": "hindi", + "visibleFrom": "md" + }, + { + "id": "bengali", + "visibleFrom": "md" + }, + { + "id": "remarks", + "visibleFrom": "md" + } + ] + } }, { "title": $localize`:Child details section title:Find a suitable new school`, From e58e78d2188a06e0af44aa12c051776aacaf804a Mon Sep 17 00:00:00 2001 From: Sebastian Leidig Date: Fri, 2 Feb 2024 23:46:59 +0100 Subject: [PATCH 28/29] remove Health and BMI related components in favor of generic RelatedEntities --- .../children/children-components.ts | 21 ----- .../children/children.service.ts | 16 +--- .../bmi-block/bmi-block.component.scss | 4 - .../bmi-block/bmi-block.component.spec.ts | 67 ---------------- .../bmi-block/bmi-block.component.ts | 37 --------- .../children-bmi-dashboard.component.html | 63 --------------- .../children-bmi-dashboard.component.scss | 1 - .../children-bmi-dashboard.component.spec.ts | 63 --------------- .../children-bmi-dashboard.component.ts | 77 ------------------- .../health-checkup.component.spec.ts | 28 ------- .../health-checkup.component.ts | 45 ----------- .../health-checkup.stories.ts | 51 ------------ .../health-checkup/model/health-check.ts | 6 +- src/app/core/config/config-fix.ts | 27 ++++--- 14 files changed, 23 insertions(+), 483 deletions(-) delete mode 100644 src/app/child-dev-project/children/health-checkup/bmi-block/bmi-block.component.scss delete mode 100644 src/app/child-dev-project/children/health-checkup/bmi-block/bmi-block.component.spec.ts delete mode 100644 src/app/child-dev-project/children/health-checkup/bmi-block/bmi-block.component.ts delete mode 100644 src/app/child-dev-project/children/health-checkup/children-bmi-dashboard/children-bmi-dashboard.component.html delete mode 100644 src/app/child-dev-project/children/health-checkup/children-bmi-dashboard/children-bmi-dashboard.component.scss delete mode 100644 src/app/child-dev-project/children/health-checkup/children-bmi-dashboard/children-bmi-dashboard.component.spec.ts delete mode 100644 src/app/child-dev-project/children/health-checkup/children-bmi-dashboard/children-bmi-dashboard.component.ts delete mode 100644 src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.spec.ts delete mode 100644 src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts delete mode 100644 src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.stories.ts diff --git a/src/app/child-dev-project/children/children-components.ts b/src/app/child-dev-project/children/children-components.ts index 2f528ddd41..ae1382e88f 100644 --- a/src/app/child-dev-project/children/children-components.ts +++ b/src/app/child-dev-project/children/children-components.ts @@ -30,25 +30,4 @@ export const childrenComponents: ComponentTuple[] = [ (c) => c.ChildBlockComponent, ), ], - [ - "ChildrenBmiDashboard", - () => - import( - "./health-checkup/children-bmi-dashboard/children-bmi-dashboard.component" - ).then((c) => c.ChildrenBmiDashboardComponent), - ], - [ - "BmiBlock", - () => - import("./health-checkup/bmi-block/bmi-block.component").then( - (c) => c.BmiBlockComponent, - ), - ], - [ - "HealthCheckup", - () => - import( - "./health-checkup/health-checkup-component/health-checkup.component" - ).then((c) => c.HealthCheckupComponent), - ], ]; diff --git a/src/app/child-dev-project/children/children.service.ts b/src/app/child-dev-project/children/children.service.ts index 3ba042cc8b..08b9c9a445 100644 --- a/src/app/child-dev-project/children/children.service.ts +++ b/src/app/child-dev-project/children/children.service.ts @@ -3,7 +3,6 @@ import { Child } from "./model/child"; import { EntityMapperService } from "../../core/entity/entity-mapper/entity-mapper.service"; import { Note } from "../notes/model/note"; import { ChildSchoolRelation } from "./model/childSchoolRelation"; -import { HealthCheck } from "./health-checkup/model/health-check"; import moment, { Moment } from "moment"; import { DatabaseIndexingService } from "../../core/entity/database-indexing/database-indexing.service"; import { Entity } from "../../core/entity/model/entity"; @@ -212,7 +211,7 @@ export class ChildrenService { if (!Array.isArray(doc.children) || !doc.date) return; if (doc.date.length === 10) { emit(doc.date); - } else { + } else { var d = new Date(doc.date || null); emit(d.getFullYear() + "-" + String(d.getMonth()+1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0")); } @@ -240,7 +239,7 @@ export class ChildrenService { var dString; if (doc.date && doc.date.length === 10) { dString = doc.date; - } else { + } else { var d = new Date(doc.date || null); dString = d.getFullYear() + "-" + String(d.getMonth()+1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0"); } @@ -263,15 +262,4 @@ export class ChildrenService { }`, }; } - - /** - * - * @param childId should be set in the specific components and is passed by the URL as a parameter - * This function should be considered refactored and should use a index, once they're made generic - */ - getHealthChecksOfChild(childId: string): Promise { - return this.entityMapper - .loadType(HealthCheck) - .then((res) => res.filter((h) => h.child === childId)); - } } diff --git a/src/app/child-dev-project/children/health-checkup/bmi-block/bmi-block.component.scss b/src/app/child-dev-project/children/health-checkup/bmi-block/bmi-block.component.scss deleted file mode 100644 index 347006fa35..0000000000 --- a/src/app/child-dev-project/children/health-checkup/bmi-block/bmi-block.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -.bmi-block { - border-radius: 4px; - padding: 5px; -} diff --git a/src/app/child-dev-project/children/health-checkup/bmi-block/bmi-block.component.spec.ts b/src/app/child-dev-project/children/health-checkup/bmi-block/bmi-block.component.spec.ts deleted file mode 100644 index 3ceae2c8f2..0000000000 --- a/src/app/child-dev-project/children/health-checkup/bmi-block/bmi-block.component.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { - ComponentFixture, - fakeAsync, - TestBed, - tick, - waitForAsync, -} from "@angular/core/testing"; -import { HealthCheck } from "../model/health-check"; -import { ChildrenService } from "../../children.service"; -import { Child } from "../../model/child"; - -import { BmiBlockComponent } from "./bmi-block.component"; - -describe("BmiBlockComponent", () => { - let component: BmiBlockComponent; - let fixture: ComponentFixture; - const mockChildrenService: jasmine.SpyObj = - jasmine.createSpyObj("mockChildrenService", ["getHealthChecksOfChild"]); - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [BmiBlockComponent], - providers: [{ provide: ChildrenService, useValue: mockChildrenService }], - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(BmiBlockComponent); - component = fixture.componentInstance; - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); - - it("should load the BMI data for the child", fakeAsync(() => { - const testChild = new Child("testID"); - const healthCheck1 = HealthCheck.create({ - date: new Date("2020-10-30"), - height: 1.3, - weight: 60, - }); - const healthCheck2 = HealthCheck.create({ - date: new Date("2020-11-30"), - height: 1.5, - weight: 77, - }); - const healthCheck3 = HealthCheck.create({ - date: new Date("2020-09-30"), - height: 1.15, - weight: 50, - }); - mockChildrenService.getHealthChecksOfChild.and.resolveTo([ - healthCheck1, - healthCheck2, - healthCheck3, - ]); - component.entity = testChild; - fixture.detectChanges(); - - expect(mockChildrenService.getHealthChecksOfChild).toHaveBeenCalledWith( - testChild.getId(), - ); - tick(); - expect(component.currentHealthCheck).toEqual(healthCheck2); - })); -}); diff --git a/src/app/child-dev-project/children/health-checkup/bmi-block/bmi-block.component.ts b/src/app/child-dev-project/children/health-checkup/bmi-block/bmi-block.component.ts deleted file mode 100644 index 65f906ea60..0000000000 --- a/src/app/child-dev-project/children/health-checkup/bmi-block/bmi-block.component.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Component, Input, OnInit } from "@angular/core"; -import { HealthCheck } from "../model/health-check"; -import { ChildrenService } from "../../children.service"; -import { DynamicComponent } from "../../../../core/config/dynamic-components/dynamic-component.decorator"; -import { Child } from "../../model/child"; - -@DynamicComponent("BmiBlock") -@Component({ - selector: "app-bmi-block", - template: ` - {{ currentHealthCheck?.bmi.toFixed(2) }} - `, - styleUrls: ["./bmi-block.component.scss"], - standalone: true, -}) -export class BmiBlockComponent implements OnInit { - @Input() entity: Child; - currentHealthCheck: HealthCheck; - - constructor(private childrenService: ChildrenService) {} - - ngOnInit() { - this.childrenService - .getHealthChecksOfChild(this.entity.getId()) - .then((results) => { - if (results.length > 0) { - this.currentHealthCheck = results.reduce((prev, cur) => - cur.date > prev.date ? cur : prev, - ); - } - }); - } -} diff --git a/src/app/child-dev-project/children/health-checkup/children-bmi-dashboard/children-bmi-dashboard.component.html b/src/app/child-dev-project/children/health-checkup/children-bmi-dashboard/children-bmi-dashboard.component.html deleted file mode 100644 index 19affffdd9..0000000000 --- a/src/app/child-dev-project/children/health-checkup/children-bmi-dashboard/children-bmi-dashboard.component.html +++ /dev/null @@ -1,63 +0,0 @@ - - -
- - - - - - - - - - - - - - - -
- - - BMI: {{ row.bmi | number: "1.0-2" }} -
-
-
- no BMI data recorded -
- - -
-
diff --git a/src/app/child-dev-project/children/health-checkup/children-bmi-dashboard/children-bmi-dashboard.component.scss b/src/app/child-dev-project/children/health-checkup/children-bmi-dashboard/children-bmi-dashboard.component.scss deleted file mode 100644 index c9b48bcc9d..0000000000 --- a/src/app/child-dev-project/children/health-checkup/children-bmi-dashboard/children-bmi-dashboard.component.scss +++ /dev/null @@ -1 +0,0 @@ -@use "../../../../core/dashboard/dashboard-widget-base"; diff --git a/src/app/child-dev-project/children/health-checkup/children-bmi-dashboard/children-bmi-dashboard.component.spec.ts b/src/app/child-dev-project/children/health-checkup/children-bmi-dashboard/children-bmi-dashboard.component.spec.ts deleted file mode 100644 index ae0455a11b..0000000000 --- a/src/app/child-dev-project/children/health-checkup/children-bmi-dashboard/children-bmi-dashboard.component.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { - ComponentFixture, - fakeAsync, - TestBed, - tick, - waitForAsync, -} from "@angular/core/testing"; -import { ChildrenBmiDashboardComponent } from "./children-bmi-dashboard.component"; -import { HealthCheck } from "../model/health-check"; -import { MockedTestingModule } from "../../../../utils/mocked-testing.module"; -import { EntityMapperService } from "../../../../core/entity/entity-mapper/entity-mapper.service"; - -describe("ChildrenBmiDashboardComponent", () => { - let component: ChildrenBmiDashboardComponent; - let fixture: ComponentFixture; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [ChildrenBmiDashboardComponent, MockedTestingModule.withState()], - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ChildrenBmiDashboardComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); - - it("should load the BMI data for the children, but only display the unhealthy one", fakeAsync(() => { - const healthCheck1 = HealthCheck.create({ - child: "testID", - date: new Date("2020-10-30"), - height: 130, - weight: 60, - }); - const healthCheck2 = HealthCheck.create({ - child: "testID", - date: new Date("2020-11-30"), - height: 150, - weight: 15, - }); - const healthCheck3 = HealthCheck.create({ - child: "testID2", - date: new Date("2020-09-30"), - height: 115, - weight: 30, - }); - const loadTypeSpy = spyOn(TestBed.inject(EntityMapperService), "loadType"); - loadTypeSpy.and.resolveTo([healthCheck1, healthCheck2, healthCheck3]); - - component.ngOnInit(); - - expect(loadTypeSpy).toHaveBeenCalledWith(HealthCheck); - tick(); - expect(component.bmiDataSource.data).toEqual([ - { childId: "testID", bmi: healthCheck2.bmi }, - ]); - })); -}); diff --git a/src/app/child-dev-project/children/health-checkup/children-bmi-dashboard/children-bmi-dashboard.component.ts b/src/app/child-dev-project/children/health-checkup/children-bmi-dashboard/children-bmi-dashboard.component.ts deleted file mode 100644 index 1b8ba92980..0000000000 --- a/src/app/child-dev-project/children/health-checkup/children-bmi-dashboard/children-bmi-dashboard.component.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { AfterViewInit, Component, OnInit, ViewChild } from "@angular/core"; -import { MatTableDataSource, MatTableModule } from "@angular/material/table"; -import { MatPaginator, MatPaginatorModule } from "@angular/material/paginator"; -import { EntityMapperService } from "../../../../core/entity/entity-mapper/entity-mapper.service"; -import { DynamicComponent } from "../../../../core/config/dynamic-components/dynamic-component.decorator"; -import { WarningLevel } from "../../../warning-level"; -import { HealthCheck } from "../model/health-check"; -import { groupBy } from "../../../../utils/utils"; -import { Child } from "../../model/child"; -import { DecimalPipe, NgIf } from "@angular/common"; -import { DisplayEntityComponent } from "../../../../core/basic-datatypes/entity/display-entity/display-entity.component"; -import { DashboardWidgetComponent } from "../../../../core/dashboard/dashboard-widget/dashboard-widget.component"; -import { WidgetContentComponent } from "../../../../core/dashboard/dashboard-widget/widget-content/widget-content.component"; -import { DashboardWidget } from "../../../../core/dashboard/dashboard-widget/dashboard-widget"; - -interface BmiRow { - childId: string; - bmi: number; -} - -@DynamicComponent("ChildrenBmiDashboard") -@Component({ - selector: "app-children-bmi-dashboard", - templateUrl: "./children-bmi-dashboard.component.html", - styleUrls: ["./children-bmi-dashboard.component.scss"], - imports: [ - NgIf, - MatTableModule, - DecimalPipe, - MatPaginatorModule, - DisplayEntityComponent, - DashboardWidgetComponent, - WidgetContentComponent, - ], - standalone: true, -}) -export class ChildrenBmiDashboardComponent - extends DashboardWidget - implements OnInit, AfterViewInit -{ - static getRequiredEntities() { - return HealthCheck.ENTITY_TYPE; - } - - bmiDataSource = new MatTableDataSource(); - isLoading = true; - entityLabelPlural: string = Child.labelPlural; - @ViewChild("paginator") paginator: MatPaginator; - - constructor(private entityMapper: EntityMapperService) { - super(); - } - - ngOnInit() { - return this.loadBMIData(); - } - - ngAfterViewInit() { - this.bmiDataSource.paginator = this.paginator; - } - - async loadBMIData() { - // Maybe replace this by a smart index function - const healthChecks = await this.entityMapper.loadType(HealthCheck); - const BMIs: BmiRow[] = []; - groupBy(healthChecks, "child").forEach(([childId, checks]) => { - const latest = checks.reduce((prev, cur) => - cur.date > prev.date ? cur : prev, - ); - if (latest && latest.getWarningLevel() === WarningLevel.URGENT) { - BMIs.push({ childId: childId, bmi: latest?.bmi }); - } - }); - this.bmiDataSource.data = BMIs; - this.isLoading = false; - } -} diff --git a/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.spec.ts b/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.spec.ts deleted file mode 100644 index eb4ebb3916..0000000000 --- a/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; - -import { HealthCheckupComponent } from "./health-checkup.component"; -import { Child } from "../../model/child"; -import { MockedTestingModule } from "../../../../utils/mocked-testing.module"; - -describe("HealthCheckupComponent", () => { - let component: HealthCheckupComponent; - let fixture: ComponentFixture; - const child = new Child(); - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [HealthCheckupComponent, MockedTestingModule.withState()], - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(HealthCheckupComponent); - component = fixture.componentInstance; - component.entity = child; - fixture.detectChanges(); - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts b/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts deleted file mode 100644 index 9a59253a80..0000000000 --- a/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.component.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Component } from "@angular/core"; -import { HealthCheck } from "../model/health-check"; -import { FormFieldConfig } from "../../../../core/common-components/entity-form/FormConfig"; -import { DynamicComponent } from "../../../../core/config/dynamic-components/dynamic-component.decorator"; -import { EntitiesTableComponent } from "../../../../core/common-components/entities-table/entities-table.component"; -import { RelatedEntitiesComponent } from "../../../../core/entity-details/related-entities/related-entities.component"; - -@DynamicComponent("HealthCheckup") -@Component({ - selector: "app-health-checkup", - templateUrl: - "../../../../core/entity-details/related-entities/related-entities.component.html", - imports: [EntitiesTableComponent], - standalone: true, -}) -export class HealthCheckupComponent extends RelatedEntitiesComponent { - entityCtr = HealthCheck; - - /** - * Column Description - * The Date-Column needs to be transformed to apply the MathFormCheck in the SubentityRecordComponent - * BMI is rounded to 2 decimal digits - */ - override _columns: FormFieldConfig[] = [ - { id: "date" }, - { id: "height" }, - { id: "weight" }, - { - id: "bmi", - label: $localize`:Table header, Short for Body Mass Index:BMI`, - viewComponent: "ReadonlyFunction", - description: $localize`:Tooltip for BMI info:This is calculated using the height and the weight measure`, - additional: (entity: HealthCheck) => this.getBMI(entity), - }, - ]; - - private getBMI(healthCheck: HealthCheck): string { - const bmi = healthCheck.bmi; - if (Number.isNaN(bmi)) { - return "-"; - } else { - return bmi.toFixed(2); - } - } -} diff --git a/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.stories.ts b/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.stories.ts deleted file mode 100644 index 46e3122d63..0000000000 --- a/src/app/child-dev-project/children/health-checkup/health-checkup-component/health-checkup.stories.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { applicationConfig, Meta, StoryFn } from "@storybook/angular"; -import { HealthCheckupComponent } from "./health-checkup.component"; -import { ChildrenService } from "../../children.service"; -import { HealthCheck } from "../model/health-check"; -import moment from "moment"; -import { Child } from "../../model/child"; -import { importProvidersFrom } from "@angular/core"; -import { StorybookBaseModule } from "../../../../utils/storybook-base.module"; - -const hc1 = new HealthCheck(); -hc1.date = new Date(); -hc1.height = 200; -hc1.weight = 70; -const hc2 = new HealthCheck(); -hc2.date = moment().subtract(1, "year").toDate(); -hc2.height = 178; -hc2.weight = 65; -const hc3 = new HealthCheck(); -hc3.date = moment().subtract(2, "years").toDate(); -hc3.height = 175; -hc3.weight = 80; - -export default { - title: "Features/Health Checkup", - component: HealthCheckupComponent, - decorators: [ - applicationConfig({ - providers: [ - importProvidersFrom(StorybookBaseModule.withData()), - { - provide: ChildrenService, - useValue: { - getHealthChecksOfChild: () => Promise.resolve([hc1, hc2, hc3]), - }, - }, - ], - }), - ], -} as Meta; - -const Template: StoryFn = ( - args: HealthCheckupComponent, -) => ({ - component: HealthCheckupComponent, - props: args, -}); - -export const Primary = Template.bind({}); -Primary.args = { - entity: new Child(), -}; diff --git a/src/app/child-dev-project/children/health-checkup/model/health-check.ts b/src/app/child-dev-project/children/health-checkup/model/health-check.ts index 3ee44d9d20..31cfad7dc2 100644 --- a/src/app/child-dev-project/children/health-checkup/model/health-check.ts +++ b/src/app/child-dev-project/children/health-checkup/model/health-check.ts @@ -65,8 +65,12 @@ export class HealthCheck extends Entity { }) weight: number; + /** + * dynamically calculated BMI value based on the height and weight, rounded to 2 decimal digits + */ get bmi(): number { - return this.weight / ((this.height / 100) * (this.height / 100)); + const bmi = this.weight / ((this.height / 100) * (this.height / 100)); + return Math.round(bmi * 100) / 100; } getWarningLevel(): WarningLevel { diff --git a/src/app/core/config/config-fix.ts b/src/app/core/config/config-fix.ts index f9a570a141..55977aa5cf 100644 --- a/src/app/core/config/config-fix.ts +++ b/src/app/core/config/config-fix.ts @@ -155,9 +155,6 @@ export const defaultJsonConfig = { { "component": "BirthdayDashboard" }, - { - "component": "ChildrenBmiDashboard" - }, ] } }, @@ -401,12 +398,6 @@ export const defaultJsonConfig = { "filterByActivityType": "COACHING_CLASS" }, "noSorting": true - }, - { - "viewComponent": "BmiBlock", - "label": $localize`:Column label for BMI of child:BMI`, - "id": "health_BMI", - "noSorting": true } ], "columnGroups": { @@ -455,7 +446,6 @@ export const defaultJsonConfig = { "projectNumber", "name", "center", - "health_BMI", "health_bloodGroup", "health_lastDentalCheckup", "gender", @@ -635,7 +625,22 @@ export const defaultJsonConfig = { }, { "title": $localize`:Title inside a panel:Height & Weight Tracking`, - "component": "HealthCheckup" + "component": "RelatedEntities", + "config": { + "entityType": "HealthCheck", + "property": "child", + "columns": [ + { "id": "date" }, + { "id": "height" }, + { "id": "weight" }, + { + "id": "bmi", + "label": $localize`:Table header, Short for Body Mass Index:BMI`, + "viewComponent": "DisplayText", + "description": $localize`:Tooltip for BMI info:This is calculated using the height and the weight measure`, + } + ] + } } ] }, From 25d29829b33cb37611b490678343f0a774047dcd Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 5 Feb 2024 08:58:55 +0100 Subject: [PATCH 29/29] improved doc Co-authored-by: Sebastian --- .../related-entities/related-entities.component.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/app/core/entity-details/related-entities/related-entities.component.ts b/src/app/core/entity-details/related-entities/related-entities.component.ts index 50b7ecad68..cba9f8414a 100644 --- a/src/app/core/entity-details/related-entities/related-entities.component.ts +++ b/src/app/core/entity-details/related-entities/related-entities.component.ts @@ -45,6 +45,11 @@ export class RelatedEntitiesComponent implements OnInit { * Property name of the related entities (type given in this.entityType) that holds the entity id * to be matched with the id of the current main entity (given in this.entity). * If not explicitly set, this will be inferred based on the defined relations between the entities. + * + * manually setting this is only necessary if you have multiple properties referencing the same entity type + * and you want to list only records related to one of them. + * For example: if you set `entityType = "Project"` (to display a list of projects here) and the Project entities have a properties "participants" and "supervisors" both storing references to User entities, + * you can set `property = "supervisors"` to only list those projects where the current User is supervisors, not participant. */ @Input() property: string | string[];