From 230d4b78f68fd6360f94c7d2a77ff5b2b4357eb7 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Fri, 25 Mar 2022 23:36:17 +0000 Subject: [PATCH 01/13] refactor: upgrade zone.js from 0.11.4 to 0.11.5 (#1165) Co-authored-by: Simon <33730997+TheSlimvReal@users.noreply.github.com> --- package-lock.json | 19 ++++++++++--------- package.json | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index febbc230b5..e4bf59b4f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "ndb-core", "version": "0.0.0", "hasInstallScript": true, "license": "GPL-3.0", @@ -46,7 +47,7 @@ "tslib": "^2.3.1", "uuid": "^8.3.2", "webdav": "^4.8.0", - "zone.js": "~0.11.4" + "zone.js": "^0.11.5" }, "devDependencies": { "@angular-devkit/build-angular": "~0.1102.14", @@ -31420,11 +31421,11 @@ } }, "node_modules/zone.js": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.4.tgz", - "integrity": "sha512-DDh2Ab+A/B+9mJyajPjHFPWfYU1H+pdun4wnnk0OcQTNjem1XQSZ2CDW+rfZEUDjv5M19SBqAkjZi0x5wuB5Qw==", + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.5.tgz", + "integrity": "sha512-D1/7VxEuQ7xk6z/kAROe4SUbd9CzxY4zOwVGnGHerd/SgLIVU5f4esDzQUsOCeArn933BZfWMKydH7l7dPEp0g==", "dependencies": { - "tslib": "^2.0.0" + "tslib": "^2.3.0" } }, "node_modules/zwitch": { @@ -55828,11 +55829,11 @@ "dev": true }, "zone.js": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.4.tgz", - "integrity": "sha512-DDh2Ab+A/B+9mJyajPjHFPWfYU1H+pdun4wnnk0OcQTNjem1XQSZ2CDW+rfZEUDjv5M19SBqAkjZi0x5wuB5Qw==", + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.5.tgz", + "integrity": "sha512-D1/7VxEuQ7xk6z/kAROe4SUbd9CzxY4zOwVGnGHerd/SgLIVU5f4esDzQUsOCeArn933BZfWMKydH7l7dPEp0g==", "requires": { - "tslib": "^2.0.0" + "tslib": "^2.3.0" } }, "zwitch": { diff --git a/package.json b/package.json index 75f33b98b6..1ded69bcea 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "tslib": "^2.3.1", "uuid": "^8.3.2", "webdav": "^4.8.0", - "zone.js": "~0.11.4" + "zone.js": "~0.11.5" }, "devDependencies": { "@angular-devkit/build-angular": "~0.1102.14", From 1ac2b78a4d286d0e3f16643e6fae680c0197e283 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Wed, 30 Mar 2022 12:07:29 +0100 Subject: [PATCH 02/13] refactor: upgrade @sentry/browser from 6.18.1 to 6.18.2 (#1169) --- package-lock.json | 128 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 65 insertions(+), 65 deletions(-) diff --git a/package-lock.json b/package-lock.json index e4bf59b4f7..d21f041e76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "@fortawesome/free-regular-svg-icons": "^5.15.2", "@fortawesome/free-solid-svg-icons": "^5.15.4", "@ngneat/until-destroy": "^8.1.4", - "@sentry/browser": "^6.18.1", + "@sentry/browser": "^6.18.2", "angulartics2": "^10.1.0", "crypto-js": "^4.1.1", "deep-object-diff": "^1.1.7", @@ -47,7 +47,7 @@ "tslib": "^2.3.1", "uuid": "^8.3.2", "webdav": "^4.8.0", - "zone.js": "^0.11.5" + "zone.js": "~0.11.5" }, "devDependencies": { "@angular-devkit/build-angular": "~0.1102.14", @@ -4910,13 +4910,13 @@ "dev": true }, "node_modules/@sentry/browser": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.18.1.tgz", - "integrity": "sha512-OZmk6RNcdQWxUkC8HBEruqpWUsaX/+pb1J/R5cDfHNeePLbDj9b8KFfs9QkgyZmmEP6l0Nu80TuDsdPF0q4uyw==", + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.18.2.tgz", + "integrity": "sha512-EsqKSNboi2gOiMuEwQranLucxrARi00y2vgUnaPXcqTKTlVlHDetoWHvq8/r29idA1JHGka5tDrwrmWccWIkrg==", "dependencies": { - "@sentry/core": "6.18.1", - "@sentry/types": "6.18.1", - "@sentry/utils": "6.18.1", + "@sentry/core": "6.18.2", + "@sentry/types": "6.18.2", + "@sentry/utils": "6.18.2", "tslib": "^1.9.3" }, "engines": { @@ -4929,14 +4929,14 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/core": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.18.1.tgz", - "integrity": "sha512-9V8Q+3Asi+3RL67CSIMMZ9mjMsu2/hrpQszYStX7hPPpAZIlAKk2MT5B+na/r80iWKhy+3Ts6aDFF218QtnsVw==", - "dependencies": { - "@sentry/hub": "6.18.1", - "@sentry/minimal": "6.18.1", - "@sentry/types": "6.18.1", - "@sentry/utils": "6.18.1", + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.18.2.tgz", + "integrity": "sha512-r5ad/gq5S/JHc9sd5CUhZQT9ojQ+f+thk/AoGeGawX/8HURZYAgIqD565d6FK0VsZEDkdRMl58z1Qon20h3y1g==", + "dependencies": { + "@sentry/hub": "6.18.2", + "@sentry/minimal": "6.18.2", + "@sentry/types": "6.18.2", + "@sentry/utils": "6.18.2", "tslib": "^1.9.3" }, "engines": { @@ -4949,12 +4949,12 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/hub": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.18.1.tgz", - "integrity": "sha512-+zGzgc/xX3an/nKA3ELMn9YD9VmqbNaNwWZ5/SjNUvzsYHh2UNZ7YzT8WawQsRVOXLljyCKxkWpFB4EchiYGbw==", + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.18.2.tgz", + "integrity": "sha512-d0AugekMkbnN12b4EXMjseJxtLPc9S20DGobCPUb4oAQT6S2oDQEj1jwP6PQ5vtgyy+GMYWxBMgqAQ4pjVYISQ==", "dependencies": { - "@sentry/types": "6.18.1", - "@sentry/utils": "6.18.1", + "@sentry/types": "6.18.2", + "@sentry/utils": "6.18.2", "tslib": "^1.9.3" }, "engines": { @@ -4967,12 +4967,12 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/minimal": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.18.1.tgz", - "integrity": "sha512-dm+0MuasWNi/LASvHX+09oCo8IBZY5WpMK8qXvQMnwQ9FVfklrjcfEI3666WORDCmeUhDCSeL2MbjPDm+AmPLg==", + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.18.2.tgz", + "integrity": "sha512-n7KYuo34W2LxE+3dnZ47of7XHuORINCnXq66XH72eoj67tf0XeWbIhEJrYGmoLRyRfoCYYrBLWiDl/uTjLzrzQ==", "dependencies": { - "@sentry/hub": "6.18.1", - "@sentry/types": "6.18.1", + "@sentry/hub": "6.18.2", + "@sentry/types": "6.18.2", "tslib": "^1.9.3" }, "engines": { @@ -4985,19 +4985,19 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/types": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.18.1.tgz", - "integrity": "sha512-wp741NoBKnXE/4T9L723sWJ8EcNMxeTIT1smgNJOfbPwrsDICoYmGEt6JFa05XHpWBGI66WuNvnDjoHVeh6zhA==", + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.18.2.tgz", + "integrity": "sha512-WzpJf/Q5aORTzrSwer/As1NlO90dBAQpaHV2ikDDKqOyMWEgjKb5/4gh59p9gH8JMMnLetP1AvQel0fOj5UnUw==", "engines": { "node": ">=6" } }, "node_modules/@sentry/utils": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.18.1.tgz", - "integrity": "sha512-IFZmuvA+c5lDGlZEri13JSyUP0BHelzY0S4dcKxAzskPW+BtBdQDgYGV90iED1y+IRMLawWb34GF7HyJSouN1Q==", + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.18.2.tgz", + "integrity": "sha512-EC619jesknyu4xpwud5WC/5odYLz6JUy7OSFy5405PpdGeh/m8XUvuJAx4zDx0Iz/Mlk0S1Md+ZcQwqkv39dkw==", "dependencies": { - "@sentry/types": "6.18.1", + "@sentry/types": "6.18.2", "tslib": "^1.9.3" }, "engines": { @@ -35019,13 +35019,13 @@ } }, "@sentry/browser": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.18.1.tgz", - "integrity": "sha512-OZmk6RNcdQWxUkC8HBEruqpWUsaX/+pb1J/R5cDfHNeePLbDj9b8KFfs9QkgyZmmEP6l0Nu80TuDsdPF0q4uyw==", + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.18.2.tgz", + "integrity": "sha512-EsqKSNboi2gOiMuEwQranLucxrARi00y2vgUnaPXcqTKTlVlHDetoWHvq8/r29idA1JHGka5tDrwrmWccWIkrg==", "requires": { - "@sentry/core": "6.18.1", - "@sentry/types": "6.18.1", - "@sentry/utils": "6.18.1", + "@sentry/core": "6.18.2", + "@sentry/types": "6.18.2", + "@sentry/utils": "6.18.2", "tslib": "^1.9.3" }, "dependencies": { @@ -35037,14 +35037,14 @@ } }, "@sentry/core": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.18.1.tgz", - "integrity": "sha512-9V8Q+3Asi+3RL67CSIMMZ9mjMsu2/hrpQszYStX7hPPpAZIlAKk2MT5B+na/r80iWKhy+3Ts6aDFF218QtnsVw==", - "requires": { - "@sentry/hub": "6.18.1", - "@sentry/minimal": "6.18.1", - "@sentry/types": "6.18.1", - "@sentry/utils": "6.18.1", + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.18.2.tgz", + "integrity": "sha512-r5ad/gq5S/JHc9sd5CUhZQT9ojQ+f+thk/AoGeGawX/8HURZYAgIqD565d6FK0VsZEDkdRMl58z1Qon20h3y1g==", + "requires": { + "@sentry/hub": "6.18.2", + "@sentry/minimal": "6.18.2", + "@sentry/types": "6.18.2", + "@sentry/utils": "6.18.2", "tslib": "^1.9.3" }, "dependencies": { @@ -35056,12 +35056,12 @@ } }, "@sentry/hub": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.18.1.tgz", - "integrity": "sha512-+zGzgc/xX3an/nKA3ELMn9YD9VmqbNaNwWZ5/SjNUvzsYHh2UNZ7YzT8WawQsRVOXLljyCKxkWpFB4EchiYGbw==", + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.18.2.tgz", + "integrity": "sha512-d0AugekMkbnN12b4EXMjseJxtLPc9S20DGobCPUb4oAQT6S2oDQEj1jwP6PQ5vtgyy+GMYWxBMgqAQ4pjVYISQ==", "requires": { - "@sentry/types": "6.18.1", - "@sentry/utils": "6.18.1", + "@sentry/types": "6.18.2", + "@sentry/utils": "6.18.2", "tslib": "^1.9.3" }, "dependencies": { @@ -35073,12 +35073,12 @@ } }, "@sentry/minimal": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.18.1.tgz", - "integrity": "sha512-dm+0MuasWNi/LASvHX+09oCo8IBZY5WpMK8qXvQMnwQ9FVfklrjcfEI3666WORDCmeUhDCSeL2MbjPDm+AmPLg==", + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.18.2.tgz", + "integrity": "sha512-n7KYuo34W2LxE+3dnZ47of7XHuORINCnXq66XH72eoj67tf0XeWbIhEJrYGmoLRyRfoCYYrBLWiDl/uTjLzrzQ==", "requires": { - "@sentry/hub": "6.18.1", - "@sentry/types": "6.18.1", + "@sentry/hub": "6.18.2", + "@sentry/types": "6.18.2", "tslib": "^1.9.3" }, "dependencies": { @@ -35090,16 +35090,16 @@ } }, "@sentry/types": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.18.1.tgz", - "integrity": "sha512-wp741NoBKnXE/4T9L723sWJ8EcNMxeTIT1smgNJOfbPwrsDICoYmGEt6JFa05XHpWBGI66WuNvnDjoHVeh6zhA==" + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.18.2.tgz", + "integrity": "sha512-WzpJf/Q5aORTzrSwer/As1NlO90dBAQpaHV2ikDDKqOyMWEgjKb5/4gh59p9gH8JMMnLetP1AvQel0fOj5UnUw==" }, "@sentry/utils": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.18.1.tgz", - "integrity": "sha512-IFZmuvA+c5lDGlZEri13JSyUP0BHelzY0S4dcKxAzskPW+BtBdQDgYGV90iED1y+IRMLawWb34GF7HyJSouN1Q==", + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.18.2.tgz", + "integrity": "sha512-EC619jesknyu4xpwud5WC/5odYLz6JUy7OSFy5405PpdGeh/m8XUvuJAx4zDx0Iz/Mlk0S1Md+ZcQwqkv39dkw==", "requires": { - "@sentry/types": "6.18.1", + "@sentry/types": "6.18.2", "tslib": "^1.9.3" }, "dependencies": { diff --git a/package.json b/package.json index 1ded69bcea..6d5c9636f7 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@fortawesome/free-regular-svg-icons": "^5.15.2", "@fortawesome/free-solid-svg-icons": "^5.15.4", "@ngneat/until-destroy": "^8.1.4", - "@sentry/browser": "^6.18.1", + "@sentry/browser": "^6.18.2", "angulartics2": "^10.1.0", "crypto-js": "^4.1.1", "deep-object-diff": "^1.1.7", From 599d7795be2ae8894cd07217b7acac6801990726 Mon Sep 17 00:00:00 2001 From: Simon <33730997+TheSlimvReal@users.noreply.github.com> Date: Fri, 1 Apr 2022 13:23:38 +0200 Subject: [PATCH 03/13] test: fixed invalid test Co-authored-by: Schottkyc137 <45085299+Schottkyc137@users.noreply.github.com> --- .../entity-form/entity-form.service.spec.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/app/core/entity-components/entity-form/entity-form.service.spec.ts b/src/app/core/entity-components/entity-form/entity-form.service.spec.ts index 0a157cb243..fc010c4a07 100644 --- a/src/app/core/entity-components/entity-form/entity-form.service.spec.ts +++ b/src/app/core/entity-components/entity-form/entity-form.service.spec.ts @@ -31,12 +31,13 @@ describe("EntityFormService", () => { it("should not save invalid entities", () => { const entity = new Entity("initialId"); - spyOn(entity, "copy").and.returnValue(entity); - spyOn(entity, "assertValid").and.throwError(new Error()); + const copyEntity = entity.copy(); + spyOn(entity, "copy").and.returnValue(copyEntity); + spyOn(copyEntity, "assertValid").and.throwError(new Error()); const formGroup = TestBed.inject(FormBuilder).group({ _id: "newId" }); expect(() => service.saveChanges(formGroup, entity)).toThrowError(); - expect(entity.getId).not.toBe("newId"); + expect(entity.getId()).not.toBe("newId"); }); it("should update entity if saving is successful", async () => { From 47d30eb3910df740dedbf02e05130256bbd447e0 Mon Sep 17 00:00:00 2001 From: Simon <33730997+TheSlimvReal@users.noreply.github.com> Date: Fri, 1 Apr 2022 15:09:18 +0200 Subject: [PATCH 04/13] refactor: fixed many SonarCloud and CodeClimate issues Co-authored-by: Schottkyc137 <45085299+Schottkyc137@users.noreply.github.com> --- .../roll-call-setup.component.ts | 2 +- .../attendance-block.component.ts | 2 - .../attendance/attendance.service.ts | 2 +- .../attendance/model/event-note.spec.ts | 46 +++++--- .../model/recurring-activity.spec.ts | 109 ++---------------- .../children/aser/model/aser.spec.ts | 60 ++-------- .../child-block/child-block.component.html | 4 +- .../children-bmi-dashboard.component.html | 2 +- .../children-count-dashboard.component.html | 2 +- .../children-list/children-list.component.ts | 17 ++- .../children/children.service.spec.ts | 28 ++--- .../children/children.service.ts | 4 +- ...child-school-relation-generator.service.ts | 2 +- .../model/educational-material.spec.ts | 64 ++-------- .../health-checkup/model/health-check.spec.ts | 53 ++------- .../children/model/child.spec.ts | 84 +++----------- .../model/childSchoolRelation.spec.ts | 61 ++-------- .../notes/model/note.spec.ts | 52 +++------ .../note-attendance-count-block.component.ts | 2 - ...ild-meeting-note-attendance.component.scss | 3 - .../notes-manager.component.spec.ts | 3 +- .../schools/model/school.spec.ts | 50 +------- .../conflict-resolution-list.component.html | 2 +- .../conflict-resolution.module.ts | 1 - src/app/core/admin/admin/admin.component.html | 2 +- .../admin/user-list/user-list.component.html | 2 +- src/app/core/app-config/app-config.ts | 2 +- src/app/core/config/config-fix.ts | 2 +- src/app/core/config/config.spec.ts | 13 +++ .../demo-data/demo-data-generator.spec.ts | 8 -- .../form/form.component.spec.ts | 4 +- .../entity-form/entity-form.service.ts | 2 +- .../entity-form/entity-form.component.scss | 3 - .../entity-list/filter-generator.service.ts | 2 +- .../list-filter/list-filter.component.ts | 2 - .../edit-single-entity.component.ts | 7 +- .../database-indexing.service.ts | 6 +- .../core/entity/mock-entity-mapper-service.ts | 5 +- src/app/core/entity/model/entity.spec.ts | 89 +++++++------- .../core/form-dialog/form-dialog.service.ts | 1 - .../latest-changes/latest-changes.service.ts | 14 +-- src/app/core/user/user.spec.ts | 56 ++------- .../demo-historical-data-generator.ts | 2 +- .../historical-data.service.spec.ts | 2 +- .../historical-data.service.ts | 2 +- .../historical-data.component.spec.ts | 2 +- .../historical-data.component.ts | 2 +- .../historical-data.stories.ts | 4 +- .../model/historical-entity-data.spec.ts | 10 ++ .../{ => model}/historical-entity-data.ts | 6 +- .../{ => model}/rating-answers.ts | 0 .../progress-dashboard-config.spec.ts | 51 +------- .../progress-dashboard.component.html | 2 +- .../object-table/object-table.component.html | 2 + 54 files changed, 263 insertions(+), 697 deletions(-) create mode 100644 src/app/core/config/config.spec.ts create mode 100644 src/app/features/historical-data/model/historical-entity-data.spec.ts rename src/app/features/historical-data/{ => model}/historical-entity-data.ts (63%) rename src/app/features/historical-data/{ => model}/rating-answers.ts (100%) diff --git a/src/app/child-dev-project/attendance/add-day-attendance/roll-call-setup/roll-call-setup.component.ts b/src/app/child-dev-project/attendance/add-day-attendance/roll-call-setup/roll-call-setup.component.ts index 1091e22b53..194fea912c 100644 --- a/src/app/child-dev-project/attendance/add-day-attendance/roll-call-setup/roll-call-setup.component.ts +++ b/src/app/child-dev-project/attendance/add-day-attendance/roll-call-setup/roll-call-setup.component.ts @@ -145,7 +145,7 @@ export class RollCallSetupComponent implements OnInit { return score; }; - this.existingEvents = this.existingEvents.sort( + this.existingEvents.sort( (a, b) => calculateEventPriority(b) - calculateEventPriority(a) ); } diff --git a/src/app/child-dev-project/attendance/attendance-block/attendance-block.component.ts b/src/app/child-dev-project/attendance/attendance-block/attendance-block.component.ts index dfb218b836..4295c5fd6e 100644 --- a/src/app/child-dev-project/attendance/attendance-block/attendance-block.component.ts +++ b/src/app/child-dev-project/attendance/attendance-block/attendance-block.component.ts @@ -18,8 +18,6 @@ export class AttendanceBlockComponent implements OnChanges { LStatus = AttendanceLogicalStatus; logicalCount: { [key in AttendanceLogicalStatus]?: number }; - constructor() {} - ngOnChanges() { this.logicalCount = this.attendanceData.individualLogicalStatusCounts.get(this.forChild) ?? diff --git a/src/app/child-dev-project/attendance/attendance.service.ts b/src/app/child-dev-project/attendance/attendance.service.ts index b436866bc5..1788867a69 100644 --- a/src/app/child-dev-project/attendance/attendance.service.ts +++ b/src/app/child-dev-project/attendance/attendance.service.ts @@ -155,7 +155,7 @@ export class AttendanceService { String(sinceDate.getDate()).padStart(2, "0"); } - return await this.dbIndexing.queryIndexDocsRange( + return this.dbIndexing.queryIndexDocsRange( EventNote, "events_index/by_activity", activityId + dateLimit, diff --git a/src/app/child-dev-project/attendance/model/event-note.spec.ts b/src/app/child-dev-project/attendance/model/event-note.spec.ts index 24e884216c..b216a0064f 100644 --- a/src/app/child-dev-project/attendance/model/event-note.spec.ts +++ b/src/app/child-dev-project/attendance/model/event-note.spec.ts @@ -15,25 +15,37 @@ * along with ndb-core. If not, see . */ -import { Entity } from "../../../core/entity/model/entity"; import { EventNote } from "./event-note"; +import { testEntitySubclass } from "../../../core/entity/model/entity.spec"; +import { defaultAttendanceStatusTypes } from "../../../core/config/default-config/default-attendance-status-types"; +import { defaultInteractionTypes } from "../../../core/config/default-config/default-interaction-types"; describe("EventNote", () => { - const ENTITY_TYPE = "EventNote"; - - it("has correct _id and entityId and type", function () { - const id = "test1"; - const entity = new EventNote(id); - - expect(entity.getId()).toBe(id); - expect(Entity.extractEntityIdFromId(entity._id)).toBe(id); - }); - - it("has correct type/prefix", function () { - const id = "test1"; - const entity = new EventNote(id); - - expect(entity.getType()).toBe(ENTITY_TYPE); - expect(Entity.extractTypeFromId(entity._id)).toBe(ENTITY_TYPE); + testEntitySubclass("EventNote", EventNote, { + _id: "EventNote:some-id", + children: ["child-1", "child-2"], + childrenAttendance: [ + [ + "child-1", + { + status: defaultAttendanceStatusTypes[1].id, + remarks: "did not show up", + }, + ], + [ + "child-2", + { + status: defaultAttendanceStatusTypes[0].id, + remarks: "", + }, + ], + ], + category: defaultInteractionTypes.find((it) => it.isMeeting).id, + authors: ["some-coach"], + relatesTo: "RecurringActivity:some-id", + date: new Date(), + schools: [], + subject: "some subject", + text: "some text about the event", }); }); diff --git a/src/app/child-dev-project/attendance/model/recurring-activity.spec.ts b/src/app/child-dev-project/attendance/model/recurring-activity.spec.ts index 06bb0cbdea..d5ac591961 100644 --- a/src/app/child-dev-project/attendance/model/recurring-activity.spec.ts +++ b/src/app/child-dev-project/attendance/model/recurring-activity.spec.ts @@ -15,107 +15,18 @@ * along with ndb-core. If not, see . */ -import { EntitySchemaService } from "../../../core/entity/schema/entity-schema.service"; -import { waitForAsync } from "@angular/core/testing"; import { RecurringActivity } from "./recurring-activity"; -import { Entity } from "../../../core/entity/model/entity"; -import { InteractionType } from "../../notes/model/interaction-type.interface"; -import { ConfigurableEnumDatatype } from "../../../core/configurable-enum/configurable-enum-datatype/configurable-enum-datatype"; +import { testEntitySubclass } from "../../../core/entity/model/entity.spec"; +import { defaultInteractionTypes } from "../../../core/config/default-config/default-interaction-types"; describe("RecurringActivity", () => { - const ENTITY_TYPE = "RecurringActivity"; - let entitySchemaService: EntitySchemaService; - const testInteractionTypes: InteractionType[] = [ - { - id: "HOME_VISIT", - label: "Home Visit", - }, - { - id: "GUARDIAN_TALK", - label: "Talk with Guardians", - }, - ]; - - beforeEach( - waitForAsync(() => { - const mockConfigService = jasmine.createSpyObj("mockConfigService", [ - "getConfig", - ]); - mockConfigService.getConfig.and.returnValue(testInteractionTypes); - - entitySchemaService = new EntitySchemaService(); - entitySchemaService.registerSchemaDatatype( - new ConfigurableEnumDatatype(mockConfigService) - ); - }) - ); - - it("has correct _id and entityId and type", function () { - const id = "test1"; - const entity = new RecurringActivity(id); - - expect(entity.getId()).toBe(id); - expect(Entity.extractEntityIdFromId(entity._id)).toBe(id); - }); - - it("has correct type/prefix", function () { - const id = "test1"; - const entity = new RecurringActivity(id); - - expect(entity.getType()).toBe(ENTITY_TYPE); - expect(Entity.extractTypeFromId(entity._id)).toBe(ENTITY_TYPE); - }); - - it("has all and only defined schema fields in rawData", function () { - const id = "test1"; - const expectedData = { - _id: ENTITY_TYPE + ":" + id, - - title: "test activity", - type: "HOME_VISIT", - assignedTo: ["demo"], - participants: ["1", "2"], - linkedGroups: ["3"], - - searchIndices: [], - }; - - const entity = new RecurringActivity(id); - entity.title = expectedData.title; - entity.type = testInteractionTypes.find((e) => e.id === "HOME_VISIT"); - entity.assignedTo = expectedData.assignedTo; - entity.participants = expectedData.participants; - entity.linkedGroups = expectedData.linkedGroups; - - const rawData = entitySchemaService.transformEntityToDatabaseFormat(entity); - - expect(rawData).toEqual(expectedData); - }); - - it("loads all defined fields from rawData", function () { - const id = "test1"; - const rawData = { - _id: ENTITY_TYPE + ":" + id, - - title: "test activity", - type: "HOME_VISIT", - assignedTo: ["demo"], - participants: ["1", "2"], - - searchIndices: [], - }; - - const entity = entitySchemaService.transformDatabaseToEntityFormat( - rawData, - RecurringActivity.schema - ); - - expect(entity._id).toBe(rawData._id); - expect(entity.title).toBe(rawData.title); - expect(entity.type).toEqual( - testInteractionTypes.find((e) => e.id === "HOME_VISIT") - ); - expect(entity.assignedTo).toBe(rawData.assignedTo); - expect(entity.participants).toBe(rawData.participants); + testEntitySubclass("RecurringActivity", RecurringActivity, { + _id: "RecurringActivity:some-id", + + title: "test activity", + type: defaultInteractionTypes[1].id, + assignedTo: ["demo"], + participants: ["1", "2"], + linkedGroups: ["3"], }); }); diff --git a/src/app/child-dev-project/children/aser/model/aser.spec.ts b/src/app/child-dev-project/children/aser/model/aser.spec.ts index 330cab31c4..4ed2811dd0 100644 --- a/src/app/child-dev-project/children/aser/model/aser.spec.ts +++ b/src/app/child-dev-project/children/aser/model/aser.spec.ts @@ -16,60 +16,22 @@ */ import { Aser } from "./aser"; -import { Entity } from "../../../../core/entity/model/entity"; -import { EntitySchemaService } from "../../../../core/entity/schema/entity-schema.service"; import { mathLevels } from "./mathLevels"; import { readingLevels } from "./readingLevels"; import { WarningLevel } from "../../../../core/entity/model/warning-level"; +import { testEntitySubclass } from "../../../../core/entity/model/entity.spec"; describe("Aser", () => { - const ENTITY_TYPE = "Aser"; - const entitySchemaService = new EntitySchemaService(); - - it("has correct _id and entityId and type", function () { - const id = "test1"; - const entity = new Aser(id); - - expect(entity.getId()).toBe(id); - expect(Entity.extractEntityIdFromId(entity._id)).toBe(id); - }); - - it("has correct type/prefix", function () { - const id = "test1"; - const entity = new Aser(id); - - expect(entity.getType()).toBe(ENTITY_TYPE); - expect(Entity.extractTypeFromId(entity._id)).toBe(ENTITY_TYPE); - }); - - it("has all and only defined schema fields in rawData", function () { - const id = "test1"; - const expectedData = { - _id: ENTITY_TYPE + ":" + id, - - child: "1", - date: new Date(), - hindi: readingLevels[2], - bengali: readingLevels[1], - english: readingLevels[2], - math: mathLevels[4], - remarks: "nothing to remark", - - searchIndices: [], - }; - - const entity = new Aser(id); - entity.child = expectedData.child; - entity.date = expectedData.date; - entity.hindi = expectedData.hindi; - entity.bengali = expectedData.bengali; - entity.english = expectedData.english; - entity.math = expectedData.math; - entity.remarks = expectedData.remarks; - - const rawData = entitySchemaService.transformEntityToDatabaseFormat(entity); - - expect(rawData).toEqual(expectedData); + testEntitySubclass("Aser", Aser, { + _id: "Aser:some-id", + + child: "1", + date: new Date(), + hindi: readingLevels[2].id, + bengali: readingLevels[1].id, + english: readingLevels[2].id, + math: mathLevels[4].id, + remarks: "nothing to remark", }); it("warning level OK if no results", function () { diff --git a/src/app/child-dev-project/children/child-block/child-block.component.html b/src/app/child-dev-project/children/child-block/child-block.component.html index 3c6fbeec7a..83ae96ca05 100644 --- a/src/app/child-dev-project/children/child-block/child-block.component.html +++ b/src/app/child-dev-project/children/child-block/child-block.component.html @@ -6,7 +6,7 @@ [ngClass]="{ inactive: !entity.isActive, clickable: !linkDisabled }" class="truncate-text" > - + Picture of a child {{ entity?.name }} ({{ entity?.projectNumber }})
- + Picture of a child

{{ entity?.name }}

diff --git a/src/app/child-dev-project/children/children-bmi-dashboard/children-bmi-dashboard.component.html b/src/app/child-dev-project/children/children-bmi-dashboard/children-bmi-dashboard.component.html index a18c2bd1ec..e1b92755c9 100644 --- a/src/app/child-dev-project/children/children-bmi-dashboard/children-bmi-dashboard.component.html +++ b/src/app/child-dev-project/children/children-bmi-dashboard/children-bmi-dashboard.component.html @@ -9,7 +9,7 @@ - +
-
+
[] = [ { key: "", label: $localize`All`, filterFun: () => true }, ]; - schools - .sort((s1, s2) => s1.name.localeCompare(s2.name)) - .forEach((school) => - options.push({ - key: school.getId(), - label: school.name, - filterFun: (c) => c.schoolId === school.getId(), - }) - ); + schools.sort((s1, s2) => s1.name.localeCompare(s2.name)); + schools.forEach((school) => + options.push({ + key: school.getId(), + label: school.name, + filterFun: (c) => c.schoolId === school.getId(), + }) + ); return options; } } diff --git a/src/app/child-dev-project/children/children.service.spec.ts b/src/app/child-dev-project/children/children.service.spec.ts index 44e5e1884b..cb5b850946 100644 --- a/src/app/child-dev-project/children/children.service.spec.ts +++ b/src/app/child-dev-project/children/children.service.spec.ts @@ -297,15 +297,11 @@ async function verifyChildRelationsOrder( "child", child.getId() ); - const sorted = relations.sort((a, b) => { - const aValue = new Date(a.start); - const bValue = new Date(b.start); - return aValue > bValue ? -1 : aValue === bValue ? 0 : 1; - }); + relations.sort((a, b) => compareStartDate(a, b)); const res = await childrenService.querySortedRelations(child.getId()); - expect(res.length).toBe(sorted.length); + expect(res.length).toBe(relations.length); for (let i = 0; i < res.length; i++) { - compareRelations(res[i], sorted[i]); + compareRelations(res[i], relations[i]); } } @@ -317,11 +313,17 @@ async function verifyLatestChildRelations( "child", child.getId() ); - const latest: ChildSchoolRelation = relations.sort((a, b) => { - const aValue = new Date(a.start); - const bValue = new Date(b.start); - return aValue > bValue ? -1 : aValue === bValue ? 0 : 1; - })[0]; + + relations.sort((a, b) => compareStartDate(a, b)); const res = await childrenService.queryLatestRelation(child.getId()); - compareRelations(res, latest); + compareRelations(res, relations[0]); +} + +function compareStartDate(a: ChildSchoolRelation, b: ChildSchoolRelation) { + const aValue = new Date(a.start); + const bValue = new Date(b.start); + if (aValue === bValue) { + return 0; + } + return aValue > bValue ? -1 : 1; } diff --git a/src/app/child-dev-project/children/children.service.ts b/src/app/child-dev-project/children/children.service.ts index 49fbd14630..6773f200ad 100644 --- a/src/app/child-dev-project/children/children.service.ts +++ b/src/app/child-dev-project/children/children.service.ts @@ -292,7 +292,7 @@ export class ChildrenService { }; } - async getSchoolRelationsFor(childId: string): Promise { - return await this.querySortedRelations(childId); + getSchoolRelationsFor(childId: string): Promise { + return this.querySortedRelations(childId); } } diff --git a/src/app/child-dev-project/children/demo-data-generators/demo-child-school-relation-generator.service.ts b/src/app/child-dev-project/children/demo-data-generators/demo-child-school-relation-generator.service.ts index 3352d4e289..eb19ed91a3 100644 --- a/src/app/child-dev-project/children/demo-data-generators/demo-child-school-relation-generator.service.ts +++ b/src/app/child-dev-project/children/demo-data-generators/demo-child-school-relation-generator.service.ts @@ -38,7 +38,7 @@ export class DemoChildSchoolRelationGenerator extends DemoDataGenerator. */ -import { waitForAsync } from "@angular/core/testing"; import { EducationalMaterial } from "./educational-material"; -import { Entity } from "../../../../core/entity/model/entity"; -import { EntitySchemaService } from "../../../../core/entity/schema/entity-schema.service"; import { materials } from "./materials"; +import { testEntitySubclass } from "../../../../core/entity/model/entity.spec"; describe("EducationalMaterial Entity", () => { - const ENTITY_TYPE = "EducationalMaterial"; - let entitySchemaService: EntitySchemaService; - - beforeEach( - waitForAsync(() => { - entitySchemaService = new EntitySchemaService(); - }) - ); - - it("has correct _id and entityId and type", function () { - const id = "test1"; - const entity = new EducationalMaterial(id); - - expect(entity.getId()).toBe(id); - expect(Entity.extractEntityIdFromId(entity._id)).toBe(id); - }); - - it("has correct type/prefix", function () { - const id = "test1"; - const entity = new EducationalMaterial(id); - - expect(entity.getType()).toBe(ENTITY_TYPE); - expect(Entity.extractTypeFromId(entity._id)).toBe(ENTITY_TYPE); - }); - - it("has all and only defined schema fields in rawData", function () { - const id = "test1"; - const expectedData = { - _id: ENTITY_TYPE + ":" + id, - _rev: "XYZ", - - child: "1", - date: new Date(), - materialType: materials[1], - materialAmount: 2, - description: "bar", - - searchIndices: [], - }; - - const entity = new EducationalMaterial(id); - entity._rev = expectedData._rev; - entity.child = expectedData.child; - entity.date = expectedData.date; - entity.materialType = expectedData.materialType; - entity.materialAmount = expectedData.materialAmount; - entity.description = expectedData.description; - - const rawData = entitySchemaService.transformEntityToDatabaseFormat(entity); - - expect(rawData).toEqual(expectedData); + testEntitySubclass("EducationalMaterial", EducationalMaterial, { + _id: "EducationalMaterial:some-id", + _rev: "XYZ", + + child: "1", + date: new Date(), + materialType: materials[1].id, + materialAmount: 2, + description: "bar", }); }); diff --git a/src/app/child-dev-project/children/health-checkup/model/health-check.spec.ts b/src/app/child-dev-project/children/health-checkup/model/health-check.spec.ts index 0337c9c475..50552bd66d 100644 --- a/src/app/child-dev-project/children/health-checkup/model/health-check.spec.ts +++ b/src/app/child-dev-project/children/health-checkup/model/health-check.spec.ts @@ -15,55 +15,16 @@ * along with ndb-core. If not, see . */ -import { waitForAsync } from "@angular/core/testing"; -import { Entity } from "../../../../core/entity/model/entity"; import { HealthCheck } from "./health-check"; -import { EntitySchemaService } from "../../../../core/entity/schema/entity-schema.service"; +import { testEntitySubclass } from "../../../../core/entity/model/entity.spec"; describe("HealthCheck Entity", () => { - const ENTITY_TYPE = "HealthCheck"; - let entitySchemaService: EntitySchemaService; + testEntitySubclass("HealthCheck", HealthCheck, { + _id: "HealthCheck:some-id", - beforeEach( - waitForAsync(() => { - entitySchemaService = new EntitySchemaService(); - }) - ); - - it("has correct _id and entityId and type", function () { - const id = "test1"; - const entity = new HealthCheck(id); - - expect(entity.getId()).toBe(id); - expect(Entity.extractEntityIdFromId(entity._id)).toBe(id); - }); - - it("has correct type/prefix", function () { - const id = "test1"; - const entity = new HealthCheck(id); - - expect(entity.getType()).toBe(ENTITY_TYPE); - expect(Entity.extractTypeFromId(entity._id)).toBe(ENTITY_TYPE); - }); - - it("has all and only defined schema fields in rawData", function () { - const id = "test1"; - const expectedData = { - _id: ENTITY_TYPE + ":" + id, - - child: "1", - date: new Date(), - height: 101, - weight: 32, - - searchIndices: [], - }; - - const entity = new HealthCheck(id); - Object.assign(entity, expectedData); - - const rawData = entitySchemaService.transformEntityToDatabaseFormat(entity); - - expect(rawData).toEqual(expectedData); + child: "1", + date: new Date(), + height: 101, + weight: 32, }); }); diff --git a/src/app/child-dev-project/children/model/child.spec.ts b/src/app/child-dev-project/children/model/child.spec.ts index 569e06de2c..52fe9afec4 100644 --- a/src/app/child-dev-project/children/model/child.spec.ts +++ b/src/app/child-dev-project/children/model/child.spec.ts @@ -16,80 +16,28 @@ */ import { Child } from "./child"; -import { waitForAsync } from "@angular/core/testing"; -import { Entity } from "../../../core/entity/model/entity"; -import { EntitySchemaService } from "../../../core/entity/schema/entity-schema.service"; -import { PhotoDatatype } from "../child-photo-service/datatype-photo"; import { genders } from "./genders"; +import { testEntitySubclass } from "../../../core/entity/model/entity.spec"; +import { centersUnique } from "../demo-data-generators/fixtures/centers"; describe("Child", () => { - const ENTITY_TYPE = "Child"; - let entitySchemaService: EntitySchemaService; + testEntitySubclass("Child", Child, { + _id: "Child:some-id", - beforeEach( - waitForAsync(() => { - entitySchemaService = new EntitySchemaService(); - entitySchemaService.registerSchemaDatatype(new PhotoDatatype()); - }) - ); + name: "Max", + projectNumber: "projectNumber01", + gender: genders[1].id, + dateOfBirth: "2010-01-01", - it("has correct _id and entityId and type", function () { - const id = "test1"; - const entity = new Child(id); + photo: "..", + center: centersUnique[0].id, + admissionDate: new Date("2021-03-1"), + status: "Active", - expect(entity.getId()).toBe(id); - expect(Entity.extractEntityIdFromId(entity._id)).toBe(id); - }); - - it("has correct type/prefix", function () { - const id = "test1"; - const entity = new Child(id); - - expect(entity.getType()).toBe(ENTITY_TYPE); - expect(Entity.extractTypeFromId(entity._id)).toBe(ENTITY_TYPE); - }); - - it("has all and only defined schema fields in rawData", function () { - const id = "test1"; - const expectedData = { - _id: ENTITY_TYPE + ":" + id, - - name: "Max", - projectNumber: "1", - gender: genders[1], - dateOfBirth: "2010-01-01", - - photo: "..", - center: { id: "alpha", label: "Alpha" }, - admissionDate: new Date(), - status: "Active", - - dropoutDate: new Date(), - dropoutType: "unknown", - dropoutRemarks: "no idea what happened", - - searchIndices: [], - }; - expectedData.searchIndices.push(expectedData.name); - expectedData.searchIndices.push(expectedData.projectNumber); - - const entity = new Child(id); - entity.name = expectedData.name; - entity.projectNumber = expectedData.projectNumber; - entity.gender = expectedData.gender; - entity.dateOfBirth = new Date(expectedData.dateOfBirth); - - entity.photo = { path: expectedData.photo, photo: null }; - entity.center = expectedData.center; - entity.admissionDate = expectedData.admissionDate; - entity.status = expectedData.status; - - entity.dropoutDate = expectedData.dropoutDate; - entity.dropoutType = expectedData.dropoutType; - entity.dropoutRemarks = expectedData.dropoutRemarks; - - const rawData = entitySchemaService.transformEntityToDatabaseFormat(entity); + dropoutDate: new Date("2022-03-31"), + dropoutType: "unknown", + dropoutRemarks: "no idea what happened", - expect(rawData).toEqual(expectedData); + searchIndices: ["Max", "projectNumber01"], }); }); diff --git a/src/app/child-dev-project/children/model/childSchoolRelation.spec.ts b/src/app/child-dev-project/children/model/childSchoolRelation.spec.ts index 69ed91ff1f..b2759b45c2 100644 --- a/src/app/child-dev-project/children/model/childSchoolRelation.spec.ts +++ b/src/app/child-dev-project/children/model/childSchoolRelation.spec.ts @@ -15,62 +15,19 @@ * along with ndb-core. If not, see . */ -import { waitForAsync } from "@angular/core/testing"; import { ChildSchoolRelation } from "./childSchoolRelation"; -import { Entity } from "../../../core/entity/model/entity"; -import { EntitySchemaService } from "../../../core/entity/schema/entity-schema.service"; import moment from "moment"; +import { testEntitySubclass } from "../../../core/entity/model/entity.spec"; describe("ChildSchoolRelation Entity", () => { - const ENTITY_TYPE = "ChildSchoolRelation"; - let entitySchemaService: EntitySchemaService; - - beforeEach( - waitForAsync(() => { - entitySchemaService = new EntitySchemaService(); - }) - ); - - it("has correct _id and entityId and type", function () { - const id = "test1"; - const entity = new ChildSchoolRelation(id); - - expect(entity.getId()).toBe(id); - expect(Entity.extractEntityIdFromId(entity._id)).toBe(id); - }); - - it("has correct type/prefix", function () { - const id = "test1"; - const entity = new ChildSchoolRelation(id); - - expect(entity.getType()).toBe(ENTITY_TYPE); - expect(Entity.extractTypeFromId(entity._id)).toBe(ENTITY_TYPE); - }); - - it("has all and only defined schema fields in rawData", function () { - const id = "test1"; - const expectedData = { - _id: ENTITY_TYPE + ":" + id, - - childId: "1", - schoolId: "2", - schoolClass: "10", - start: "2019-01-01", - end: "2019-12-31", - - searchIndices: [], - }; - - const entity = new ChildSchoolRelation(id); - entity.childId = expectedData.childId; - entity.schoolId = expectedData.schoolId; - entity.schoolClass = expectedData.schoolClass; - entity.start = new Date(expectedData.start); - entity.end = new Date(expectedData.end); - - const rawData = entitySchemaService.transformEntityToDatabaseFormat(entity); - - expect(rawData).toEqual(expectedData); + testEntitySubclass("ChildSchoolRelation", ChildSchoolRelation, { + _id: "ChildSchoolRelation:some-id", + + childId: "1", + schoolId: "2", + schoolClass: "10", + start: "2019-01-01", + end: "2019-12-31", }); it("should mark relations without end date as active", () => { diff --git a/src/app/child-dev-project/notes/model/note.spec.ts b/src/app/child-dev-project/notes/model/note.spec.ts index c33c4c5f83..d553f6ea67 100644 --- a/src/app/child-dev-project/notes/model/note.spec.ts +++ b/src/app/child-dev-project/notes/model/note.spec.ts @@ -2,7 +2,6 @@ import { Note } from "./note"; import { warningLevels } from "../../warning-levels"; import { EntitySchemaService } from "../../../core/entity/schema/entity-schema.service"; import { waitForAsync } from "@angular/core/testing"; -import { Entity } from "../../../core/entity/model/entity"; import { ATTENDANCE_STATUS_CONFIG_ID, AttendanceLogicalStatus, @@ -23,6 +22,8 @@ import { getWarningLevelColor, WarningLevel, } from "../../../core/entity/model/warning-level"; +import { testEntitySubclass } from "../../../core/entity/model/entity.spec"; +import { defaultInteractionTypes } from "../../../core/config/default-config/default-interaction-types"; const testStatusTypes: ConfigurableEnumConfig = [ { @@ -92,43 +93,18 @@ describe("Note", () => { }) ); - it("has correct _id and entityId", function () { - const id = "test1"; - const entity = new Note(id); - - expect(entity.getId()).toBe(id); - expect(entity.getType()).toBe(ENTITY_TYPE); - expect(Entity.extractEntityIdFromId(entity._id)).toBe(id); - }); - - it("has all and only defined schema fields in rawData", function () { - const id = "1"; - const expectedData = { - _id: ENTITY_TYPE + ":" + id, - - children: ["1", "2", "5"], - childrenAttendance: [], - schools: [], - date: new Date(), - subject: "Note Subject", - text: "Note text", - authors: ["1"], - category: "GUARDIAN_TALK", - warningLevel: "OK", - - searchIndices: [], - }; - - const entity = new Note(id); - Object.assign(entity, expectedData); - entity.category = testInteractionTypes.find( - (c) => c.id === "GUARDIAN_TALK" - ); - entity.warningLevel = warningLevels.find((level) => level.id === "OK"); - - const rawData = entitySchemaService.transformEntityToDatabaseFormat(entity); - - expect(rawData).toEqual(expectedData); + testEntitySubclass("Note", Note, { + _id: "Note:some-id", + + children: ["1", "2", "5"], + childrenAttendance: [], + schools: [], + date: new Date(), + subject: "Note Subject", + text: "Note text", + authors: ["1"], + category: defaultInteractionTypes[1].id, + warningLevel: warningLevels[2].id, }); it("should return the correct childIds", function () { diff --git a/src/app/child-dev-project/notes/note-attendance-block/note-attendance-count-block.component.ts b/src/app/child-dev-project/notes/note-attendance-block/note-attendance-count-block.component.ts index 537101a564..87e784d3fe 100644 --- a/src/app/child-dev-project/notes/note-attendance-block/note-attendance-count-block.component.ts +++ b/src/app/child-dev-project/notes/note-attendance-block/note-attendance-count-block.component.ts @@ -28,8 +28,6 @@ export class NoteAttendanceCountBlockComponent participantsWithStatus: number; - constructor() {} - ngOnInit() { if (this.note) { this.participantsWithStatus = this.note.countWithStatus( diff --git a/src/app/child-dev-project/notes/note-details/child-meeting-attendance/child-meeting-note-attendance.component.scss b/src/app/child-dev-project/notes/note-details/child-meeting-attendance/child-meeting-note-attendance.component.scss index 8c407f5363..1516703f21 100644 --- a/src/app/child-dev-project/notes/note-details/child-meeting-attendance/child-meeting-note-attendance.component.scss +++ b/src/app/child-dev-project/notes/note-details/child-meeting-attendance/child-meeting-note-attendance.component.scss @@ -2,9 +2,6 @@ display: none; } -.actions { -} - .dense-form-field { margin-top: -1em; } diff --git a/src/app/child-dev-project/notes/notes-manager/notes-manager.component.spec.ts b/src/app/child-dev-project/notes/notes-manager/notes-manager.component.spec.ts index 7606b2e072..a41c11430e 100644 --- a/src/app/child-dev-project/notes/notes-manager/notes-manager.component.spec.ts +++ b/src/app/child-dev-project/notes/notes-manager/notes-manager.component.spec.ts @@ -14,7 +14,7 @@ import { EntityMapperService } from "../../../core/entity/entity-mapper.service" import { RouterTestingModule } from "@angular/router/testing"; import { FormDialogService } from "../../../core/form-dialog/form-dialog.service"; import { ActivatedRoute, Router } from "@angular/router"; -import { of, Subject } from "rxjs"; +import { BehaviorSubject, of, Subject } from "rxjs"; import { Note } from "../model/note"; import { Angulartics2Module } from "angulartics2"; import { NoteDetailsComponent } from "../note-details/note-details.component"; @@ -27,7 +27,6 @@ import { ConfigService } from "../../../core/config/config.service"; import { By } from "@angular/platform-browser"; import { EntityListComponent } from "../../../core/entity-components/entity-list/entity-list.component"; import { EventNote } from "../../attendance/model/event-note"; -import { BehaviorSubject } from "rxjs"; import { UpdatedEntity } from "../../../core/entity/model/entity-update"; import { ExportService } from "../../../core/export/export-service/export.service"; import { MockSessionModule } from "../../../core/session/mock-session.module"; diff --git a/src/app/child-dev-project/schools/model/school.spec.ts b/src/app/child-dev-project/schools/model/school.spec.ts index dc33704c88..8625220fae 100644 --- a/src/app/child-dev-project/schools/model/school.spec.ts +++ b/src/app/child-dev-project/schools/model/school.spec.ts @@ -15,53 +15,13 @@ * along with ndb-core. If not, see . */ -import { waitForAsync } from "@angular/core/testing"; import { School } from "./school"; -import { Entity } from "../../../core/entity/model/entity"; -import { EntitySchemaService } from "../../../core/entity/schema/entity-schema.service"; +import { testEntitySubclass } from "../../../core/entity/model/entity.spec"; describe("School Entity", () => { - const ENTITY_TYPE = "School"; - let entitySchemaService: EntitySchemaService; - - beforeEach( - waitForAsync(() => { - entitySchemaService = new EntitySchemaService(); - }) - ); - - it("has correct _id and entityId and type", function () { - const id = "test1"; - const entity = new School(id); - - expect(entity.getId()).toBe(id); - expect(Entity.extractEntityIdFromId(entity._id)).toBe(id); - }); - - it("has correct type/prefix", function () { - const id = "test1"; - const entity = new School(id); - - expect(entity.getType()).toBe(ENTITY_TYPE); - expect(Entity.extractTypeFromId(entity._id)).toBe(ENTITY_TYPE); - }); - - it("has all and only defined schema fields in rawData", function () { - const id = "test1"; - const expectedData = { - _id: "School:" + id, - - name: "Max", - - searchIndices: [], - }; - expectedData.searchIndices.push(expectedData.name); - - const entity = new School(id); - entity.name = expectedData.name; - - const rawData = entitySchemaService.transformEntityToDatabaseFormat(entity); - - expect(rawData).toEqual(expectedData); + testEntitySubclass("School", School, { + _id: "School:some-id", + name: "Max", + searchIndices: ["Max"], }); }); diff --git a/src/app/conflict-resolution/conflict-resolution-list/conflict-resolution-list.component.html b/src/app/conflict-resolution/conflict-resolution-list/conflict-resolution-list.component.html index 079ce00d8c..6992dfba96 100644 --- a/src/app/conflict-resolution/conflict-resolution-list/conflict-resolution-list.component.html +++ b/src/app/conflict-resolution/conflict-resolution-list/conflict-resolution-list.component.html @@ -12,7 +12,7 @@ -
+
diff --git a/src/app/conflict-resolution/conflict-resolution.module.ts b/src/app/conflict-resolution/conflict-resolution.module.ts index 11a21d7d6a..a9cd6a0947 100644 --- a/src/app/conflict-resolution/conflict-resolution.module.ts +++ b/src/app/conflict-resolution/conflict-resolution.module.ts @@ -12,7 +12,6 @@ import { MatInputModule } from "@angular/material/input"; import { FormsModule } from "@angular/forms"; import { MatTooltipModule } from "@angular/material/tooltip"; import { ConflictResolutionRoutingModule } from "./conflict-resolution-routing.module"; -import { ConflictResolutionStrategy } from "./auto-resolution/conflict-resolution-strategy"; import { MatProgressBarModule } from "@angular/material/progress-bar"; /** diff --git a/src/app/core/admin/admin/admin.component.html b/src/app/core/admin/admin/admin.component.html index 4a4d589ff5..70dff2f075 100644 --- a/src/app/core/admin/admin/admin.component.html +++ b/src/app/core/admin/admin/admin.component.html @@ -114,7 +114,7 @@

Debug the PouchDB



Alert Log

-
_id {{ row.id }}
+
diff --git a/src/app/core/admin/user-list/user-list.component.html b/src/app/core/admin/user-list/user-list.component.html index 817453beb7..f3f93a7cb4 100644 --- a/src/app/core/admin/user-list/user-list.component.html +++ b/src/app/core/admin/user-list/user-list.component.html @@ -1,4 +1,4 @@ -
{{ alert.type }} {{ alert.message }}
+
diff --git a/src/app/core/app-config/app-config.ts b/src/app/core/app-config/app-config.ts index 83bb58a209..2d904ebbe9 100644 --- a/src/app/core/app-config/app-config.ts +++ b/src/app/core/app-config/app-config.ts @@ -79,7 +79,7 @@ export class AppConfig { }) .catch((response: any) => { reject( - $localize`Could not load file '${jsonFileLocation}': ${JSON.stringify( + `Could not load file '${jsonFileLocation}': ${JSON.stringify( response )}` ); diff --git a/src/app/core/config/config-fix.ts b/src/app/core/config/config-fix.ts index 4be9f6f26e..3e80438e7e 100644 --- a/src/app/core/config/config-fix.ts +++ b/src/app/core/config/config-fix.ts @@ -9,7 +9,7 @@ import { materials } from "../../child-dev-project/children/educational-material import { mathLevels } from "../../child-dev-project/children/aser/model/mathLevels"; import { readingLevels } from "../../child-dev-project/children/aser/model/readingLevels"; import { warningLevels } from "../../child-dev-project/warning-levels"; -import { ratingAnswers } from "../../features/historical-data/rating-answers"; +import { ratingAnswers } from "../../features/historical-data/model/rating-answers"; // prettier-ignore export const defaultJsonConfig = { diff --git a/src/app/core/config/config.spec.ts b/src/app/core/config/config.spec.ts new file mode 100644 index 0000000000..9283cb59d6 --- /dev/null +++ b/src/app/core/config/config.spec.ts @@ -0,0 +1,13 @@ +import { testEntitySubclass } from "../entity/model/entity.spec"; +import { Config } from "./config"; + +// TODO active this once User Roles PR is merged, currently config has wrong constructor parameters +xdescribe("Config", () => { + testEntitySubclass("Config", Config, { + _id: "Config:" + Config.CONFIG_KEY, + data: { + some: "data", + without: "structure", + }, + }); +}); diff --git a/src/app/core/demo-data/demo-data-generator.spec.ts b/src/app/core/demo-data/demo-data-generator.spec.ts index 9ce7ae9aa7..764df3a490 100644 --- a/src/app/core/demo-data/demo-data-generator.spec.ts +++ b/src/app/core/demo-data/demo-data-generator.spec.ts @@ -1,15 +1,7 @@ -import { DemoDataGenerator } from "./demo-data-generator"; import { faker } from "./faker"; -class DemoDataGeneratorImplementation extends DemoDataGenerator { - protected generateEntities(): any[] { - return []; - } -} - describe("DemoDataGenerator", () => { it("should getEarlierDateOrToday", () => { - const generator = new DemoDataGeneratorImplementation(); const TODAY = new Date(); const earlierDate = new Date(2019, 0, 1); diff --git a/src/app/core/entity-components/entity-details/form/form.component.spec.ts b/src/app/core/entity-components/entity-details/form/form.component.spec.ts index 3b1cb887e2..1d0c78e09f 100644 --- a/src/app/core/entity-components/entity-details/form/form.component.spec.ts +++ b/src/app/core/entity-components/entity-details/form/form.component.spec.ts @@ -52,12 +52,12 @@ describe("FormComponent", () => { expect(component.creatingNew).toBe(true); }); - it("calls router once a new child is saved", async () => { + it("calls router once a new child is saved", () => { const testChild = new Child(); const router = fixture.debugElement.injector.get(Router); spyOn(router, "navigate"); component.creatingNew = true; - await component.saveClicked(testChild); + component.saveClicked(testChild); expect(router.navigate).toHaveBeenCalledWith(["", testChild.getId()]); }); }); diff --git a/src/app/core/entity-components/entity-form/entity-form.service.ts b/src/app/core/entity-components/entity-form/entity-form.service.ts index 94a6b6d96b..dda835cb34 100644 --- a/src/app/core/entity-components/entity-form/entity-form.service.ts +++ b/src/app/core/entity-components/entity-form/entity-form.service.ts @@ -29,7 +29,7 @@ export class EntityFormService { this.addFormFields(formField, entity, forTable); } catch (err) { throw new Error( - $localize`Could not create form config for ${formField.id}\: ${err}` + `Could not create form config for ${formField.id}\: ${err}` ); } }); diff --git a/src/app/core/entity-components/entity-form/entity-form/entity-form.component.scss b/src/app/core/entity-components/entity-form/entity-form/entity-form.component.scss index 6c3f3ee37d..479b529a56 100644 --- a/src/app/core/entity-components/entity-form/entity-form/entity-form.component.scss +++ b/src/app/core/entity-components/entity-form/entity-form/entity-form.component.scss @@ -27,9 +27,6 @@ $max-screen-width: $min-block-width + $margin-main-view-right + $margin-main-vie margin-right: 0.5em; } -.action-button { -} - .buttons-wrapper { padding-bottom: $padding-buttons-bottom; } diff --git a/src/app/core/entity-components/entity-list/filter-generator.service.ts b/src/app/core/entity-components/entity-list/filter-generator.service.ts index ffd7a1ec08..4188a457df 100644 --- a/src/app/core/entity-components/entity-list/filter-generator.service.ts +++ b/src/app/core/entity-components/entity-list/filter-generator.service.ts @@ -97,7 +97,7 @@ export class FilterGeneratorService { this.entities.has(config.type) || this.entities.has(schema.additional) ) { - return await this.createEntityFilterOption( + return this.createEntityFilterOption( config.id, config.type || schema.additional ); diff --git a/src/app/core/entity-components/entity-list/list-filter/list-filter.component.ts b/src/app/core/entity-components/entity-list/list-filter/list-filter.component.ts index 3282e0297e..9053dfcddb 100644 --- a/src/app/core/entity-components/entity-list/list-filter/list-filter.component.ts +++ b/src/app/core/entity-components/entity-list/list-filter/list-filter.component.ts @@ -13,8 +13,6 @@ export class ListFilterComponent { @Input() selectedOption: string; @Output() selectedOptionChange = new EventEmitter(); - constructor() {} - selectOption(selectedOptionKey: string) { this.selectedOption = selectedOptionKey; this.selectedOptionChange.emit(selectedOptionKey); diff --git a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-single-entity/edit-single-entity.component.ts b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-single-entity/edit-single-entity.component.ts index 40d7ff149e..d05e361664 100644 --- a/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-single-entity/edit-single-entity.component.ts +++ b/src/app/core/entity-components/entity-utils/dynamic-form-components/edit-single-entity/edit-single-entity.component.ts @@ -59,11 +59,8 @@ export class EditSingleEntityComponent extends EditComponent { this.placeholder = $localize`:Placeholder for input to set an entity|context Select User:Select ${this.label}`; const entityType: string = config.formFieldConfig.additional || config.propertySchema.additional; - this.entities = await this.entityMapperService - .loadType(entityType) - .then((entities) => - entities.sort((e1, e2) => e1.toString().localeCompare(e2.toString())) - ); + this.entities = await this.entityMapperService.loadType(entityType); + this.entities.sort((e1, e2) => e1.toString().localeCompare(e2.toString())); this.entityNameFormControl.setValidators(this.formControl.validator); const selectedEntity = this.entities.find( (entity) => entity.getId() === this.formControl.value diff --git a/src/app/core/entity/database-indexing/database-indexing.service.ts b/src/app/core/entity/database-indexing/database-indexing.service.ts index 20a0cdf1ed..aa68c4721d 100644 --- a/src/app/core/entity/database-indexing/database-indexing.service.ts +++ b/src/app/core/entity/database-indexing/database-indexing.service.ts @@ -120,14 +120,14 @@ export class DatabaseIndexingService { }); } - async queryIndexStats( + queryIndexStats( indexName: string, options: QueryOptions = { reduce: true, group: true, } ): Promise { - return await this.queryIndexRaw(indexName, options); + return this.queryIndexRaw(indexName, options); } /** @@ -149,7 +149,7 @@ export class DatabaseIndexingService { await this.waitForIndexAvailable(indexName); } - return await this.db.query(indexName, options); + return this.db.query(indexName, options); } /** diff --git a/src/app/core/entity/mock-entity-mapper-service.ts b/src/app/core/entity/mock-entity-mapper-service.ts index 703f35cbb0..6faf33fa5e 100644 --- a/src/app/core/entity/mock-entity-mapper-service.ts +++ b/src/app/core/entity/mock-entity-mapper-service.ts @@ -3,6 +3,7 @@ import { EntityMapperService } from "./entity-mapper.service"; import { UpdatedEntity } from "./model/entity-update"; import { NEVER, Observable } from "rxjs"; import { entityRegistry } from "./database-entity.decorator"; +import { HttpErrorResponse } from "@angular/common/http"; export function mockEntityMapper( withData: Entity[] = [] @@ -61,7 +62,7 @@ export class MockEntityMapperService extends EntityMapperService { public get(entityType: string, id: string): Entity { const result = this.data.get(entityType)?.get(id); if (!result) { - throw { status: 404 }; + throw new HttpErrorResponse({ status: 404 }); } return result; } @@ -104,7 +105,7 @@ export class MockEntityMapperService extends EntityMapperService { ): Promise { const ctor = this.resolveConstructor(entityType); const type = new ctor().getType(); - return this.getAll(type) as T[]; + return this.getAll(type); } async save( diff --git a/src/app/core/entity/model/entity.spec.ts b/src/app/core/entity/model/entity.spec.ts index afd1bf6c67..ec5ad7d774 100644 --- a/src/app/core/entity/model/entity.spec.ts +++ b/src/app/core/entity/model/entity.spec.ts @@ -15,10 +15,13 @@ * along with ndb-core. If not, see . */ -import { Entity } from "./entity"; +import { Entity, EntityConstructor } from "./entity"; import { waitForAsync } from "@angular/core/testing"; import { EntitySchemaService } from "../schema/entity-schema.service"; import { DatabaseField } from "../database-field.decorator"; +import { ConfigurableEnumDatatype } from "../../configurable-enum/configurable-enum-datatype/configurable-enum-datatype"; +import { ConfigService } from "../../config/config.service"; +import { LoggingService } from "../../logging/logging.service"; describe("Entity", () => { let entitySchemaService: EntitySchemaService; @@ -29,48 +32,7 @@ describe("Entity", () => { }) ); - it("has ID and entityId", function () { - const id = "test1"; - const entity = new Entity(id); - - expect(entity.getId()).toBe(id); - expect(Entity.extractEntityIdFromId(entity._id)).toBe(id); - }); - - it("has correct type/prefix", function () { - const entity = new Entity(); - - expect(entity.getType()).toBe("Entity"); - expect(Entity.extractTypeFromId(entity._id)).toBe("Entity"); - }); - - it("all schema fields exist", function () { - const id = "test1"; - const entity = new Entity(id); - entity._rev = "XYZ"; - - const rawData = entitySchemaService.transformEntityToDatabaseFormat(entity); - - expect(rawData._id).toBe(Entity.createPrefixedId(Entity.ENTITY_TYPE, id)); - expect(rawData._rev).toBe("XYZ"); - }); - - it("load() assigns all data", function () { - const id = "test1"; - const entity = new Entity(id); - - const data = { - _id: "test2", - _rev: "1.2.3", - other: "x", - }; - entitySchemaService.loadDataIntoEntity(entity, data); - - expect(entity._id).toBe(data._id); - expect(entity._rev).toBe(data._rev); - // @ts-ignore because other is not a defined property of the class (-> TypeScript error) and only added from load(data) - expect(entity.other).toBe(data.other); - }); + testEntitySubclass("Entity", Entity, { _id: "someId", _rev: "some_rev" }); it("rawData() returns only data matching the schema", function () { class TestEntity extends Entity { @@ -124,3 +86,44 @@ describe("Entity", () => { expect(otherEntity).toBeInstanceOf(TestEntity); }); }); + +export function testEntitySubclass( + entityType: string, + entityClass: EntityConstructor, + expectedDatabaseFormat: any +) { + it("should be a valid entity subclass", () => { + const id = "test1"; + const entity = new entityClass(id); + + // correct ID + expect(entity.getId()).toBe(id); + expect(Entity.extractEntityIdFromId(entity._id)).toBe(id); + + // correct Type + expect(entity).toBeInstanceOf(entityClass); + expect(entity).toBeInstanceOf(Entity); + expect(entity.getType()).toBe(entityType); + expect(Entity.extractTypeFromId(entity._id)).toBe(entityType); + }); + + it("should only load and store properties defined in the schema", () => { + const schemaService = new EntitySchemaService(); + const configService = new ConfigService(new LoggingService()); + configService.loadConfig({ load: () => Promise.reject() } as any); + schemaService.registerSchemaDatatype( + new ConfigurableEnumDatatype(configService) + ); + const entity = new entityClass(); + + schemaService.loadDataIntoEntity( + entity, + JSON.parse(JSON.stringify(expectedDatabaseFormat)) + ); + const rawData = schemaService.transformEntityToDatabaseFormat(entity); + if (rawData.searchIndices.length === 0) { + delete rawData.searchIndices; + } + expect(rawData).toEqual(expectedDatabaseFormat); + }); +} diff --git a/src/app/core/form-dialog/form-dialog.service.ts b/src/app/core/form-dialog/form-dialog.service.ts index fca4f1f40c..2e7a91fa5f 100644 --- a/src/app/core/form-dialog/form-dialog.service.ts +++ b/src/app/core/form-dialog/form-dialog.service.ts @@ -2,7 +2,6 @@ import { Injectable } from "@angular/core"; import { MatDialog, MatDialogRef } from "@angular/material/dialog"; import { ComponentType } from "@angular/cdk/overlay"; import { ConfirmationDialogService } from "../confirmation-dialog/confirmation-dialog.service"; -import { FormDialogWrapperComponent } from "./form-dialog-wrapper/form-dialog-wrapper.component"; import { ShowsEntity } from "./shows-entity.interface"; import { OnInitDynamicComponent } from "../view/dynamic-components/on-init-dynamic-component.interface"; import { Entity } from "../entity/model/entity"; diff --git a/src/app/core/latest-changes/latest-changes.service.ts b/src/app/core/latest-changes/latest-changes.service.ts index 786c1f005a..2b1a16a8bc 100644 --- a/src/app/core/latest-changes/latest-changes.service.ts +++ b/src/app/core/latest-changes/latest-changes.service.ts @@ -62,10 +62,10 @@ export class LatestChangesService { } if (previousVersion) { - const releasesBackToPreviousVersion = releasesUpToCurrentVersion.filter( + relevantReleases = releasesUpToCurrentVersion.filter( (r) => r.tag_name > previousVersion ); - relevantReleases = releasesBackToPreviousVersion.sort((a, b) => + relevantReleases.sort((a, b) => (b.tag_name as string).localeCompare(a.tag_name, "en") ); } else { @@ -94,8 +94,6 @@ export class LatestChangesService { version: string, count: number ) { - let relevantReleases; - const releasesUpToCurrentVersion = releases.filter( (r) => r.tag_name < version ); @@ -103,17 +101,19 @@ export class LatestChangesService { return []; } - relevantReleases = releasesUpToCurrentVersion.sort((a, b) => + releasesUpToCurrentVersion.sort((a, b) => (b.tag_name as string).localeCompare(a.tag_name, "en") ); - return relevantReleases.slice(0, count); + return releasesUpToCurrentVersion.slice(0, count); } /** * Load release information from GitHub based on a given filter to select relevant releases. * @param releaseFilter Filter function that is selecting relevant objects from the array of GitHub releases */ - private getChangelogs(releaseFilter: ([]) => any[]): Observable { + private getChangelogs( + releaseFilter: (releases: any[]) => any[] + ): Observable { return this.http .get( LatestChangesService.GITHUB_API + environment.repositoryId + "/releases" diff --git a/src/app/core/user/user.spec.ts b/src/app/core/user/user.spec.ts index 66ee248840..ddcd21c90a 100644 --- a/src/app/core/user/user.spec.ts +++ b/src/app/core/user/user.spec.ts @@ -16,58 +16,18 @@ */ import { User } from "./user"; -import { waitForAsync } from "@angular/core/testing"; -import { Entity } from "../entity/model/entity"; -import { EntitySchemaService } from "../entity/schema/entity-schema.service"; +import { testEntitySubclass } from "../entity/model/entity.spec"; describe("User", () => { - const ENTITY_TYPE = "User"; - let entitySchemaService: EntitySchemaService; + testEntitySubclass("User", User, { + _id: "User:some-id", - beforeEach( - waitForAsync(() => { - entitySchemaService = new EntitySchemaService(); - }) - ); + name: "tester", + cloudPasswordEnc: "encryptedPassword", + cloudBaseFolder: "/aam-digital/", + paginatorSettingsPageSize: {}, - it("has correct _id and entityId and type", function () { - const id = "test1"; - const entity = new User(id); - - expect(entity.getId()).toBe(id); - expect(Entity.extractEntityIdFromId(entity._id)).toBe(id); - }); - - it("has correct type/prefix", function () { - const id = "test1"; - const entity = new User(id); - - expect(entity.getType()).toBe(ENTITY_TYPE); - expect(Entity.extractTypeFromId(entity._id)).toBe(ENTITY_TYPE); - }); - - it("has all and only defined schema fields in rawData", function () { - const id = "test1"; - const expectedData = { - _id: ENTITY_TYPE + ":" + id, - - name: "tester", - cloudPasswordEnc: "encryptedPassword", - cloudBaseFolder: "/aam-digital/", - paginatorSettingsPageSize: {}, - - searchIndices: [], - }; - expectedData.searchIndices.push(expectedData.name); - - const entity = new User(id); - entity.name = expectedData.name; - // @ts-ignore - entity.cloudPasswordEnc = expectedData.cloudPasswordEnc; - - const rawData = entitySchemaService.transformEntityToDatabaseFormat(entity); - - expect(rawData).toEqual(expectedData); + searchIndices: ["tester"], }); it("sets cloud passwords", () => { diff --git a/src/app/features/historical-data/demo-historical-data-generator.ts b/src/app/features/historical-data/demo-historical-data-generator.ts index a8d31ed9cb..a70a928088 100644 --- a/src/app/features/historical-data/demo-historical-data-generator.ts +++ b/src/app/features/historical-data/demo-historical-data-generator.ts @@ -1,5 +1,5 @@ import { DemoDataGenerator } from "../../core/demo-data/demo-data-generator"; -import { HistoricalEntityData } from "./historical-entity-data"; +import { HistoricalEntityData } from "./model/historical-entity-data"; import { Injectable } from "@angular/core"; import { DemoChildGenerator } from "../../child-dev-project/children/demo-data-generators/demo-child-generator.service"; import { ConfigService } from "../../core/config/config.service"; diff --git a/src/app/features/historical-data/historical-data.service.spec.ts b/src/app/features/historical-data/historical-data.service.spec.ts index 56063708fc..887ba9b8f5 100644 --- a/src/app/features/historical-data/historical-data.service.spec.ts +++ b/src/app/features/historical-data/historical-data.service.spec.ts @@ -3,7 +3,7 @@ import { TestBed } from "@angular/core/testing"; import { HistoricalDataService } from "./historical-data.service"; import { EntityMapperService } from "../../core/entity/entity-mapper.service"; import { Entity } from "../../core/entity/model/entity"; -import { HistoricalEntityData } from "./historical-entity-data"; +import { HistoricalEntityData } from "./model/historical-entity-data"; import { expectEntitiesToMatch } from "../../utils/expect-entity-data.spec"; import { PouchDatabase } from "../../core/database/pouch-database"; import { EntitySchemaService } from "../../core/entity/schema/entity-schema.service"; diff --git a/src/app/features/historical-data/historical-data.service.ts b/src/app/features/historical-data/historical-data.service.ts index 3d754d47e3..ddc765dfe4 100644 --- a/src/app/features/historical-data/historical-data.service.ts +++ b/src/app/features/historical-data/historical-data.service.ts @@ -1,5 +1,5 @@ import { Injectable } from "@angular/core"; -import { HistoricalEntityData } from "./historical-entity-data"; +import { HistoricalEntityData } from "./model/historical-entity-data"; import { DatabaseIndexingService } from "../../core/entity/database-indexing/database-indexing.service"; @Injectable({ 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 1c1e144ae4..fab7ff5f38 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 @@ -9,7 +9,7 @@ import { HistoricalDataComponent } from "./historical-data.component"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { HistoricalDataModule } from "../historical-data.module"; import { Entity } from "../../../core/entity/model/entity"; -import { HistoricalEntityData } from "../historical-entity-data"; +import { HistoricalEntityData } from "../model/historical-entity-data"; import moment from "moment"; import { DatePipe } from "@angular/common"; import { HistoricalDataService } from "../historical-data.service"; 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 63211cbb72..2c945d2402 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 @@ -1,6 +1,6 @@ import { Component } from "@angular/core"; import { OnInitDynamicComponent } from "../../../core/view/dynamic-components/on-init-dynamic-component.interface"; -import { HistoricalEntityData } from "../historical-entity-data"; +import { HistoricalEntityData } from "../model/historical-entity-data"; import { PanelConfig } from "../../../core/entity-components/entity-details/EntityDetailsConfig"; import { Entity } from "../../../core/entity/model/entity"; import { HistoricalDataService } from "../historical-data.service"; diff --git a/src/app/features/historical-data/historical-data/historical-data.stories.ts b/src/app/features/historical-data/historical-data/historical-data.stories.ts index ec09213eaf..c6db88ff57 100644 --- a/src/app/features/historical-data/historical-data/historical-data.stories.ts +++ b/src/app/features/historical-data/historical-data/historical-data.stories.ts @@ -2,12 +2,12 @@ import { Meta, Story } from "@storybook/angular/types-6-0"; import { moduleMetadata } from "@storybook/angular"; import { DatePipe } from "@angular/common"; import { ConfigService } from "../../../core/config/config.service"; -import { HistoricalEntityData } from "../historical-entity-data"; +import { HistoricalEntityData } from "../model/historical-entity-data"; import { HistoricalDataComponent } from "./historical-data.component"; import { HistoricalDataModule } from "../historical-data.module"; import { HistoricalDataService } from "../historical-data.service"; import { EntityPermissionsService } from "../../../core/permissions/entity-permissions.service"; -import { ratingAnswers } from "../rating-answers"; +import { ratingAnswers } from "../model/rating-answers"; import { StorybookBaseModule } from "../../../utils/storybook-base.module"; import { MockSessionModule } from "../../../core/session/mock-session.module"; diff --git a/src/app/features/historical-data/model/historical-entity-data.spec.ts b/src/app/features/historical-data/model/historical-entity-data.spec.ts new file mode 100644 index 0000000000..c3a95dc5ac --- /dev/null +++ b/src/app/features/historical-data/model/historical-entity-data.spec.ts @@ -0,0 +1,10 @@ +import { testEntitySubclass } from "../../../core/entity/model/entity.spec"; +import { HistoricalEntityData } from "./historical-entity-data"; + +describe("HistoricalEntityData", () => { + testEntitySubclass("HistoricalEntityData", HistoricalEntityData, { + _id: "HistoricalEntityData:some-id", + date: new Date(), + relatedEntity: "Child:some-other-id", + }); +}); diff --git a/src/app/features/historical-data/historical-entity-data.ts b/src/app/features/historical-data/model/historical-entity-data.ts similarity index 63% rename from src/app/features/historical-data/historical-entity-data.ts rename to src/app/features/historical-data/model/historical-entity-data.ts index c1843c703a..85367cad9d 100644 --- a/src/app/features/historical-data/historical-entity-data.ts +++ b/src/app/features/historical-data/model/historical-entity-data.ts @@ -1,6 +1,6 @@ -import { Entity } from "../../core/entity/model/entity"; -import { DatabaseEntity } from "../../core/entity/database-entity.decorator"; -import { DatabaseField } from "../../core/entity/database-field.decorator"; +import { Entity } from "../../../core/entity/model/entity"; +import { DatabaseEntity } from "../../../core/entity/database-entity.decorator"; +import { DatabaseField } from "../../../core/entity/database-field.decorator"; /** * A general class that represents data that is collected for a entity over time. diff --git a/src/app/features/historical-data/rating-answers.ts b/src/app/features/historical-data/model/rating-answers.ts similarity index 100% rename from src/app/features/historical-data/rating-answers.ts rename to src/app/features/historical-data/model/rating-answers.ts diff --git a/src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard-config.spec.ts b/src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard-config.spec.ts index c3c178c179..5377294acf 100644 --- a/src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard-config.spec.ts +++ b/src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard-config.spec.ts @@ -15,54 +15,13 @@ * along with ndb-core. If not, see . */ -import { waitForAsync } from "@angular/core/testing"; -import { Entity } from "../../../core/entity/model/entity"; import { ProgressDashboardConfig } from "./progress-dashboard-config"; -import { EntitySchemaService } from "../../../core/entity/schema/entity-schema.service"; +import { testEntitySubclass } from "../../../core/entity/model/entity.spec"; describe("ProgressDashboardConfig Entity", () => { - const ENTITY_TYPE = "ProgressDashboardConfig"; - let entitySchemaService: EntitySchemaService; - - beforeEach( - waitForAsync(() => { - entitySchemaService = new EntitySchemaService(); - }) - ); - - it("has correct _id and entityId and type", function () { - const id = "test1"; - const entity = new ProgressDashboardConfig(id); - - expect(entity.getId()).toBe(id); - expect(Entity.extractEntityIdFromId(entity._id)).toBe(id); - }); - - it("has correct type/prefix", function () { - const id = "test1"; - const entity = new ProgressDashboardConfig(id); - - expect(entity.getType()).toBe(ENTITY_TYPE); - expect(Entity.extractTypeFromId(entity._id)).toBe(ENTITY_TYPE); - }); - - it("has all and only defined schema fields in rawData", function () { - const id = "test1"; - const expectedData = { - _id: ENTITY_TYPE + ":" + id, - - title: "test", - parts: [], - - searchIndices: [], - }; - - const entity = new ProgressDashboardConfig(id); - entity.title = expectedData.title; - entity.parts = expectedData.parts; - - const rawData = entitySchemaService.transformEntityToDatabaseFormat(entity); - - expect(rawData).toEqual(expectedData); + testEntitySubclass("ProgressDashboardConfig", ProgressDashboardConfig, { + _id: "ProgressDashboardConfig:some-id", + title: "test", + parts: [{ label: "Part Label", currentValue: 3, targetValue: 10 }], }); }); diff --git a/src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html b/src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html index 5e5790a108..2f887016de 100644 --- a/src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html +++ b/src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html @@ -5,7 +5,7 @@ [subtitle]="data.title" > -
ID {{ user.getId() }}
+
{{ entry.label }} diff --git a/src/app/features/reporting/reporting/object-table/object-table.component.html b/src/app/features/reporting/reporting/object-table/object-table.component.html index 2979ceb656..0d83316054 100644 --- a/src/app/features/reporting/reporting/object-table/object-table.component.html +++ b/src/app/features/reporting/reporting/object-table/object-table.component.html @@ -1,6 +1,8 @@ From 86f6b39cae3e4c8999b88529a6159ece86ab4452 Mon Sep 17 00:00:00 2001 From: Simon <33730997+TheSlimvReal@users.noreply.github.com> Date: Tue, 5 Apr 2022 17:03:32 +0200 Subject: [PATCH 05/13] refactor: added document id's in PouchDB error objects --- src/app/core/database/pouch-database.spec.ts | 41 ++++++++++++++++++++ src/app/core/database/pouch-database.ts | 8 +++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/app/core/database/pouch-database.spec.ts b/src/app/core/database/pouch-database.spec.ts index 2cb24e1e0f..dbed3a1da0 100644 --- a/src/app/core/database/pouch-database.spec.ts +++ b/src/app/core/database/pouch-database.spec.ts @@ -215,4 +215,45 @@ describe("PouchDatabase tests", () => { expect(result).toEqual(testQueryResults); expect(pouchDB.query).toHaveBeenCalledWith(testQuery, {}); }); + + it("should log the ID when requesting a not existing entity", async () => { + const notExistingId = "Not-existing-id"; + return expectPromiseToRejectWithDocId( + database.get(notExistingId), + notExistingId + ); + }); + + it("should log the ID when putting a doc with wrong rev", async () => { + const doc = { + _id: "someId", + _rev: "1-invalidRev", + }; + return expectPromiseToRejectWithDocId(database.put(doc), doc._id); + }); + + it("should log the ID when removing a not existing doc", () => { + const notExistingDoc = { + _id: "not_existing_id", + _rev: "1-someRev", + }; + return expectPromiseToRejectWithDocId( + database.remove(notExistingDoc), + notExistingDoc._id + ); + }); + + it("should log the view name when querying a not existing view", () => { + const notExistingView = "search_index/by_name"; + return expectPromiseToRejectWithDocId( + database.query(notExistingView, {}), + notExistingView + ); + }); + + function expectPromiseToRejectWithDocId(prom: Promise, id: string) { + return prom + .then(() => fail()) + .catch((e) => expect(e.affectedDocument).toBe(id)); + } }); diff --git a/src/app/core/database/pouch-database.ts b/src/app/core/database/pouch-database.ts index 8ed94efb5b..4361474ce4 100644 --- a/src/app/core/database/pouch-database.ts +++ b/src/app/core/database/pouch-database.ts @@ -89,6 +89,7 @@ export class PouchDatabase extends Database { return undefined; } } + err.affectedDocument = id; throw err; }); } @@ -130,6 +131,7 @@ export class PouchDatabase extends Database { if (err.status === 409) { return this.resolveConflict(object, forceOverwrite, err); } else { + err.affectedDocument = object._id; throw err; } }); @@ -143,6 +145,7 @@ export class PouchDatabase extends Database { */ remove(object: any) { return this._pouchDB.remove(object).catch((err) => { + err.affectedDocument = object._id; throw err; }); } @@ -177,7 +180,10 @@ export class PouchDatabase extends Database { fun: string | ((doc: any, emit: any) => void), options: QueryOptions ): Promise { - return this._pouchDB.query(fun, options); + return this._pouchDB.query(fun, options).catch((err) => { + err.affectedDocument = fun; + throw err; + }); } /** From 0904c9d5a927d5ddc9cf61242911271d14f26db8 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Wed, 6 Apr 2022 13:28:49 +0100 Subject: [PATCH 06/13] refactor: upgrade moment from 2.29.1 to 2.29.2 The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-MOMENT-2440688 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index d21f041e76..08123fcb2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,7 @@ "json-query": "^2.2.2", "lodash": "^4.17.21", "md5": "^2.3.0", - "moment": "^2.29.1", + "moment": "^2.29.2", "ngx-markdown": "^11.2.0", "ngx-papaparse": "^5.0.0", "pouchdb-adapter-memory": "^7.2.2", @@ -20157,9 +20157,9 @@ } }, "node_modules/moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "version": "2.29.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", + "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==", "engines": { "node": "*" } @@ -46943,9 +46943,9 @@ "dev": true }, "moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + "version": "2.29.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", + "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==" }, "morgan": { "version": "1.10.0", diff --git a/package.json b/package.json index 6d5c9636f7..02b99101a6 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "json-query": "^2.2.2", "lodash": "^4.17.21", "md5": "^2.3.0", - "moment": "^2.29.1", + "moment": "^2.29.2", "ngx-markdown": "^11.2.0", "ngx-papaparse": "^5.0.0", "pouchdb-adapter-memory": "^7.2.2", From 408754b01af201a5822b5bc582694f67f701a480 Mon Sep 17 00:00:00 2001 From: Simon <33730997+TheSlimvReal@users.noreply.github.com> Date: Thu, 7 Apr 2022 17:25:15 +0200 Subject: [PATCH 07/13] fix: attendance overview shows total attendance summary and calender also shows previous months fixes: #1149, #1146 Co-authored-by: Sebastian Leidig --- .storybook/main.js | 14 +- ...activity-attendance-section.component.html | 25 +- ...ivity-attendance-section.component.spec.ts | 45 ++++ .../activity-attendance-section.component.ts | 34 ++- .../activity-attendance-section.stories.ts | 57 +++-- .../attendance-details.component.html | 80 ++++--- .../attendance-details.component.ts | 2 +- .../attendance-summary.component.html | 25 ++ .../attendance-summary.component.scss | 29 +++ .../attendance-summary.component.spec.ts | 42 ++++ .../attendance-summary.component.ts | 22 ++ .../attendance/attendance.module.ts | 2 + .../model/activity-attendance.spec.ts | 47 +++- .../attendance/model/activity-attendance.ts | 84 ++++--- .../grouped-child-attendance.component.html | 4 + .../grouped-child-attendance.component.ts | 3 + src/app/child-dev-project/notes/model/note.ts | 8 +- src/locale/messages.de.xlf | 201 ++++++++++++---- src/locale/messages.fr.xlf | 221 +++++++++++++----- src/locale/messages.xlf | 163 +++++++++---- src/styles.scss | 3 +- 21 files changed, 842 insertions(+), 269 deletions(-) create mode 100644 src/app/child-dev-project/attendance/attendance-summary/attendance-summary.component.html create mode 100644 src/app/child-dev-project/attendance/attendance-summary/attendance-summary.component.scss create mode 100644 src/app/child-dev-project/attendance/attendance-summary/attendance-summary.component.spec.ts create mode 100644 src/app/child-dev-project/attendance/attendance-summary/attendance-summary.component.ts diff --git a/.storybook/main.js b/.storybook/main.js index 26dfeaf66b..d6400dd08d 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,10 +1,8 @@ module.exports = { - "stories": [ - "../src/**/*.stories.mdx", - "../src/**/*.stories.@(js|jsx|ts|tsx)" - ], - "addons": [ + stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], + addons: [ "@storybook/addon-links", - "@storybook/addon-essentials" - ] -} \ No newline at end of file + // See {@link https://github.com/storybookjs/storybook/issues/17004#issuecomment-993210351} + { name: "@storybook/addon-essentials", options: { docs: false } }, + ], +}; diff --git a/src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.html b/src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.html index 7bcfbaa41f..4f1c789476 100644 --- a/src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.html +++ b/src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.html @@ -1,9 +1,22 @@ - - +
+ +
+ +
+ + +
{ let component: ActivityAttendanceSectionComponent; @@ -104,4 +105,48 @@ describe("ActivityAttendanceSectionComponent", () => { component.updateDisplayedRecords(true); expect(component.records).toEqual(component.allRecords); }); + + it("should combine all activity attendances to have an all-time overview", async () => { + const oldestEvent = EventNote.create( + moment().subtract(2, "months").toDate() + ); + const someEvent1 = EventNote.create( + moment().subtract(1, "months").toDate() + ); + const someEvent2 = EventNote.create( + moment().subtract(1, "months").toDate() + ); + const latestEvent = EventNote.create(new Date()); + const oldestAttendance = ActivityAttendance.create(oldestEvent.date, [ + oldestEvent, + ]); + oldestAttendance.periodTo = oldestEvent.date; + const middleAttendance = ActivityAttendance.create(someEvent1.date, [ + someEvent1, + someEvent2, + ]); + middleAttendance.periodTo = someEvent2.date; + const latestAttendance = ActivityAttendance.create(latestEvent.date, [ + latestEvent, + ]); + latestAttendance.periodTo = latestEvent.date; + mockAttendanceService.getActivityAttendances.and.resolveTo([ + oldestAttendance, + middleAttendance, + latestAttendance, + ]); + + await component.init(); + + expect(component.combinedAttendance.periodFrom).toBe(oldestEvent.date); + expect(component.combinedAttendance.periodTo).toBe(latestEvent.date); + expect(component.combinedAttendance.events).toEqual( + jasmine.arrayWithExactContents([ + oldestEvent, + someEvent1, + someEvent2, + latestEvent, + ]) + ); + }); }); diff --git a/src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts b/src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts index ab76d8b18f..72a276f9fe 100644 --- a/src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts +++ b/src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts @@ -5,7 +5,6 @@ import { AttendanceDetailsComponent } from "../attendance-details/attendance-det import { AttendanceService } from "../attendance.service"; import { PercentPipe } from "@angular/common"; import { ActivityAttendance } from "../model/activity-attendance"; -import { Note } from "../../notes/model/note"; import moment from "moment"; import { FormFieldConfig } from "../../../core/entity-components/entity-form/entity-form/FormConfig"; import { FormDialogService } from "../../../core/form-dialog/form-dialog.service"; @@ -22,9 +21,10 @@ export class ActivityAttendanceSectionComponent @Input() activity: RecurringActivity; @Input() forChild?: string; + loading: boolean = true; records: ActivityAttendance[] = []; allRecords: ActivityAttendance[] = []; - displayedEvents: Note[] = []; + combinedAttendance: ActivityAttendance; columns: FormFieldConfig[] = [ { @@ -40,7 +40,7 @@ export class ActivityAttendanceSectionComponent additional: (e: ActivityAttendance) => this.forChild ? e.countEventsPresent(this.forChild) - : e.countEventsPresentAverage(true), + : e.countTotalPresent(), }, { id: "totalEvents", @@ -83,6 +83,7 @@ export class ActivityAttendanceSectionComponent } async init(loadAll: boolean = false) { + this.loading = true; if (loadAll) { this.allRecords = await this.attendanceService.getActivityAttendances( this.activity @@ -94,6 +95,32 @@ export class ActivityAttendanceSectionComponent ); } this.updateDisplayedRecords(false); + this.createCombinedAttendance(); + this.loading = false; + } + + private createCombinedAttendance() { + this.combinedAttendance = new ActivityAttendance(); + this.combinedAttendance.activity = this.activity; + this.allRecords.forEach((record) => { + this.combinedAttendance.events.push(...record.events); + if ( + !this.combinedAttendance.periodFrom || + moment(record.periodFrom).isBefore( + this.combinedAttendance.periodFrom, + "day" + ) + ) { + this.combinedAttendance.periodFrom = record.periodFrom; + } + + if ( + !this.combinedAttendance.periodTo || + moment(record.periodTo).isAfter(this.combinedAttendance.periodTo, "day") + ) { + this.combinedAttendance.periodTo = record.periodTo; + } + }); } updateDisplayedRecords(includeRecordsWithoutParticipation: boolean) { @@ -112,7 +139,6 @@ export class ActivityAttendanceSectionComponent this.records.sort( (a, b) => b.periodFrom.getTime() - a.periodFrom.getTime() ); - this.displayedEvents = this.records[0].events; } } diff --git a/src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.stories.ts b/src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.stories.ts index 2dc1b7ee7a..c777417913 100644 --- a/src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.stories.ts +++ b/src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.stories.ts @@ -10,53 +10,64 @@ import { import { AttendanceLogicalStatus } from "../model/attendance-status"; import { StorybookBaseModule } from "../../../utils/storybook-base.module"; import { MockSessionModule } from "../../../core/session/mock-session.module"; +import { AttendanceService } from "../attendance.service"; +import { ChildrenService } from "../../children/children.service"; +import { of } from "rxjs"; +import { Child } from "../../children/model/child"; +import moment from "moment"; const demoActivity = RecurringActivity.create("Coaching Batch C"); const attendanceRecords = [ - ActivityAttendance.create(new Date("2020-01-01"), [ + ActivityAttendance.create( + moment().subtract(1, "month").startOf("month").toDate(), + [ + generateEventWithAttendance([ + ["1", AttendanceLogicalStatus.ABSENT], + ["2", AttendanceLogicalStatus.ABSENT], + ]), + generateEventWithAttendance([ + ["1", AttendanceLogicalStatus.PRESENT], + ["2", AttendanceLogicalStatus.ABSENT], + ]), + ] + ), + + ActivityAttendance.create(moment().startOf("month").toDate(), [ generateEventWithAttendance( [ ["1", AttendanceLogicalStatus.PRESENT], ["2", AttendanceLogicalStatus.PRESENT], ["3", AttendanceLogicalStatus.ABSENT], ], - new Date("2020-01-01") + moment().subtract(5, "days").toDate() ), generateEventWithAttendance( [ ["1", AttendanceLogicalStatus.PRESENT], ["2", AttendanceLogicalStatus.ABSENT], ], - new Date("2020-01-02") + moment().subtract(4, "days").toDate() ), generateEventWithAttendance( [ ["1", AttendanceLogicalStatus.ABSENT], ["2", AttendanceLogicalStatus.ABSENT], ], - new Date("2020-01-03") + moment().subtract(3, "days").toDate() ), generateEventWithAttendance( [ ["1", AttendanceLogicalStatus.PRESENT], ["2", AttendanceLogicalStatus.ABSENT], ], - new Date("2020-01-04") + moment().subtract(2, "days").toDate() ), ]), - - ActivityAttendance.create(new Date("2020-02-01"), [ - generateEventWithAttendance([ - ["1", AttendanceLogicalStatus.ABSENT], - ["2", AttendanceLogicalStatus.ABSENT], - ]), - generateEventWithAttendance([ - ["1", AttendanceLogicalStatus.PRESENT], - ["2", AttendanceLogicalStatus.ABSENT], - ]), - ]), ]; -attendanceRecords.forEach((a) => (a.activity = demoActivity)); +attendanceRecords.forEach((a) => { + a.activity = demoActivity; + a.periodTo = moment(a.periodFrom).endOf("month").toDate(); +}); export default { title: "Attendance/Sections/ActivityAttendanceSection", @@ -68,6 +79,18 @@ export default { StorybookBaseModule, MockSessionModule.withState(), ], + providers: [ + { + provide: AttendanceService, + useValue: { + getActivityAttendances: () => Promise.resolve(attendanceRecords), + }, + }, + { + provide: ChildrenService, + useValue: { getChild: () => of(Child.create("John Doe")) }, + }, + ], }), ], } as Meta; diff --git a/src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html b/src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html index 6ae895d7d5..c06a9c6cd3 100644 --- a/src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html +++ b/src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html @@ -40,22 +40,15 @@

-
+
@@ -66,16 +59,9 @@

@@ -86,20 +72,50 @@

+ +

+
+ +
+
+ + + +
+ +
+ + + +
+ +
+ + 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 d9574fa181..a31e73ce2b 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 @@ -33,7 +33,7 @@ export class AttendanceDetailsComponent return note.getAttendance(this.focusedChild).status.label; } else { return ( - Math.round(calculateAverageAttendance(note).average * 10) / 10 || + (calculateAverageAttendance(note).average * 100).toFixed(0) + "%" || "N/A" ); } diff --git a/src/app/child-dev-project/attendance/attendance-summary/attendance-summary.component.html b/src/app/child-dev-project/attendance/attendance-summary/attendance-summary.component.html new file mode 100644 index 0000000000..8a72a585d2 --- /dev/null +++ b/src/app/child-dev-project/attendance/attendance-summary/attendance-summary.component.html @@ -0,0 +1,25 @@ +
+

Overall Attendance

+

+ {{ attendance.periodFrom | date: "shortDate" }} - + {{ attendance.periodTo | date: "shortDate" }} +

+ +

{{ col }}
+ + + + +
{{ col.label }}: +
+
+ diff --git a/src/app/child-dev-project/attendance/attendance-summary/attendance-summary.component.scss b/src/app/child-dev-project/attendance/attendance-summary/attendance-summary.component.scss new file mode 100644 index 0000000000..9f14aa8fb8 --- /dev/null +++ b/src/app/child-dev-project/attendance/attendance-summary/attendance-summary.component.scss @@ -0,0 +1,29 @@ +.wrapper { + padding: 1em; +} + +.summary-title { + margin-bottom: 0.25em; +} + +.summary-period { + color: #606060; + margin: 0; +} + +.summary-table { + border-spacing: 0 8px; +} + +.summary-row { + background-color: #b0b0b040; +} + +.summary-cell-label { + padding: 0.5em; +} +.summary-cell { + font-size: 1.5em; + text-align: right; + padding: 0.5em; + } diff --git a/src/app/child-dev-project/attendance/attendance-summary/attendance-summary.component.spec.ts b/src/app/child-dev-project/attendance/attendance-summary/attendance-summary.component.spec.ts new file mode 100644 index 0000000000..11aedf04ed --- /dev/null +++ b/src/app/child-dev-project/attendance/attendance-summary/attendance-summary.component.spec.ts @@ -0,0 +1,42 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { AttendanceSummaryComponent } from "./attendance-summary.component"; +import { AttendanceModule } from "../attendance.module"; + +describe("AttendanceSummaryComponent", () => { + let component: AttendanceSummaryComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AttendanceModule], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AttendanceSummaryComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + it("should remove the from and to column", () => { + component.columns = [ + { id: "periodFrom", label: "Month" }, + { id: "totalEvents", label: "Total" }, + { id: "attendancePercentage", label: "Attendance" }, + ]; + + expect(component._columns).toEqual([ + { id: "attendancePercentage", label: "Attendance" }, + { id: "totalEvents", label: "Total" }, + ]); + expect(component._columns).not.toContain({ + id: "periodFrom", + label: "Month", + }); + }); +}); diff --git a/src/app/child-dev-project/attendance/attendance-summary/attendance-summary.component.ts b/src/app/child-dev-project/attendance/attendance-summary/attendance-summary.component.ts new file mode 100644 index 0000000000..c2eb47305e --- /dev/null +++ b/src/app/child-dev-project/attendance/attendance-summary/attendance-summary.component.ts @@ -0,0 +1,22 @@ +import { Component, Input } from "@angular/core"; +import { ActivityAttendance } from "../model/activity-attendance"; +import { FormFieldConfig } from "../../../core/entity-components/entity-form/entity-form/FormConfig"; + +@Component({ + selector: "app-attendance-summary", + templateUrl: "./attendance-summary.component.html", + styleUrls: ["./attendance-summary.component.scss"], +}) +export class AttendanceSummaryComponent { + @Input() attendance: ActivityAttendance; + @Input() forChild: string; + + @Input() set columns(value: FormFieldConfig[]) { + this._columns = value + // hide periodFrom / periodTo as it is displayed in custom styling directly in the template + .filter((col) => !["periodFrom", "periodTo"].includes(col.id)) + // start with most summative column, usually displayed right-most in table + .reverse(); + } + _columns: FormFieldConfig[] = []; +} diff --git a/src/app/child-dev-project/attendance/attendance.module.ts b/src/app/child-dev-project/attendance/attendance.module.ts index 4be01a1a75..c196a03393 100644 --- a/src/app/child-dev-project/attendance/attendance.module.ts +++ b/src/app/child-dev-project/attendance/attendance.module.ts @@ -60,6 +60,7 @@ import { DashboardModule } from "../../core/dashboard/dashboard.module"; import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; import { MatPaginatorModule } from "@angular/material/paginator"; import { ViewModule } from "../../core/view/view.module"; +import { AttendanceSummaryComponent } from "./attendance-summary/attendance-summary.component"; @NgModule({ declarations: [ @@ -78,6 +79,7 @@ import { ViewModule } from "../../core/view/view.module"; AttendanceStatusSelectComponent, AttendanceWeekDashboardComponent, AttendanceManagerComponent, + AttendanceSummaryComponent, ], imports: [ EntityListModule, diff --git a/src/app/child-dev-project/attendance/model/activity-attendance.spec.ts b/src/app/child-dev-project/attendance/model/activity-attendance.spec.ts index 86d6357f74..be4de04d8e 100644 --- a/src/app/child-dev-project/attendance/model/activity-attendance.spec.ts +++ b/src/app/child-dev-project/attendance/model/activity-attendance.spec.ts @@ -69,24 +69,23 @@ describe("ActivityAttendance", () => { ["2", AttendanceLogicalStatus.PRESENT], ]), ]); - expect(everyoneInOneEventAbsent.countEventsAbsentAverage()).toBe(1); + expect(everyoneInOneEventAbsent.countTotalAbsent()).toBe(2); const allAbsent = ActivityAttendance.create(new Date(), [ generateEventWithAttendance([ ["1", AttendanceLogicalStatus.ABSENT], ["2", AttendanceLogicalStatus.ABSENT], - ["2", AttendanceLogicalStatus.ABSENT], + ["3", AttendanceLogicalStatus.ABSENT], ]), generateEventWithAttendance([ ["1", AttendanceLogicalStatus.ABSENT], ["2", AttendanceLogicalStatus.ABSENT], ]), ]); - expect(allAbsent.countEventsAbsentAverage()).toBe(2); + expect(allAbsent.countTotalAbsent()).toBe(5); }); - // TODO: what should be the excepted averaged result here? - xit("calculates average present", () => { + it("calculates average present", () => { const presentAct = ActivityAttendance.create(new Date(), [ generateEventWithAttendance([ ["1", AttendanceLogicalStatus.PRESENT], @@ -98,7 +97,7 @@ describe("ActivityAttendance", () => { ]), generateEventWithAttendance([["1", AttendanceLogicalStatus.PRESENT]]), ]); - expect(presentAct.countEventsPresentAverage()).toBe(2.5); + expect(presentAct.countTotalPresent()).toBe(5); const allAbsent = ActivityAttendance.create(new Date(), [ generateEventWithAttendance([ @@ -111,7 +110,20 @@ describe("ActivityAttendance", () => { ["2", AttendanceLogicalStatus.ABSENT], ]), ]); - expect(allAbsent.countEventsPresentAverage()).toBe(0); + expect(allAbsent.countTotalPresent()).toBe(0); + + const halfAbsent = ActivityAttendance.create(new Date(), [ + generateEventWithAttendance([ + ["1", AttendanceLogicalStatus.ABSENT], + ["2", AttendanceLogicalStatus.PRESENT], + ]), + generateEventWithAttendance([ + ["1", AttendanceLogicalStatus.PRESENT], + ["2", AttendanceLogicalStatus.ABSENT], + ["3", AttendanceLogicalStatus.IGNORE], + ]), + ]); + expect(halfAbsent.countTotalPresent()).toBe(2); }); it("calculates logical stats on set of events", () => { @@ -169,4 +181,25 @@ describe("ActivityAttendance", () => { record.individualStatusTypeCounts.get("2")[StatusLate.id] ).toBeUndefined(); }); + + it("calculates events containing unknown/undefined attendance status", () => { + const attendance = ActivityAttendance.create(new Date(), [ + generateEventWithAttendance([ + ["1", AttendanceLogicalStatus.PRESENT], + ["2", AttendanceLogicalStatus.IGNORE], + ]), + generateEventWithAttendance([ + ["1", AttendanceLogicalStatus.PRESENT], + ["2", AttendanceLogicalStatus.PRESENT], + ]), + ]); + + // adding participants without attendance to one event + attendance.events[1].children.push("3"); + attendance.events[1].children.push("4"); + + expect(attendance.countEventsWithUnknownStatus()).toBe(1); // one unique event with undefined attendances + expect(attendance.countEventsWithUnknownStatus("2")).toBe(0); + expect(attendance.countEventsWithUnknownStatus("3")).toBe(1); + }); }); diff --git a/src/app/child-dev-project/attendance/model/activity-attendance.ts b/src/app/child-dev-project/attendance/model/activity-attendance.ts index 80d70d7b75..4017fd889e 100644 --- a/src/app/child-dev-project/attendance/model/activity-attendance.ts +++ b/src/app/child-dev-project/attendance/model/activity-attendance.ts @@ -1,7 +1,4 @@ -import { - AttendanceLogicalStatus, - AttendanceStatusType, -} from "./attendance-status"; +import { AttendanceLogicalStatus } from "./attendance-status"; import { RecurringActivity } from "./recurring-activity"; import { defaultAttendanceStatusTypes } from "../../../core/config/default-config/default-attendance-status-types"; import { EventNote } from "./event-note"; @@ -72,27 +69,6 @@ export class ActivityAttendance extends Entity { return this.events.length; } - countEventsWithStatusForChild( - status: AttendanceStatusType, - childId: string - ): number { - return this.events.reduce( - (prev: number, currentEvent: EventNote) => - currentEvent.getAttendance(childId)?.status === status - ? prev + 1 - : prev, - 0 - ); - } - - countEventsWithUnknownStatus(): number { - return this.events.reduce( - (prev: number, currentEvent: EventNote) => - currentEvent.hasUnknownAttendances() ? prev + 1 : prev, - 0 - ); - } - countEventsPresent(childId: string): number { return this.countIndividual(childId, AttendanceLogicalStatus.PRESENT); } @@ -101,6 +77,16 @@ export class ActivityAttendance extends Entity { return this.countIndividual(childId, AttendanceLogicalStatus.ABSENT); } + private countIndividual( + childId: string, + countingType: AttendanceLogicalStatus + ) { + return this.events.filter( + (eventNote) => + eventNote.getAttendance(childId)?.status.countAs === countingType + ).length; + } + getAttendancePercentage(childId: string): number { const present = this.countEventsPresent(childId); const absent = this.countEventsAbsent(childId); @@ -108,30 +94,26 @@ export class ActivityAttendance extends Entity { return present / (present + absent); } - getAttendancePercentageAverage(): number { - // TODO calculate overall averaged attendance percentage - return NaN; + countTotalPresent() { + return this.countWithStatus(AttendanceLogicalStatus.PRESENT); } - countEventsPresentAverage(rounded: boolean = false) { - return this.countAverage(AttendanceLogicalStatus.PRESENT, rounded); + countTotalAbsent() { + return this.countWithStatus(AttendanceLogicalStatus.ABSENT); } - countEventsAbsentAverage(rounded: boolean = false) { - return this.countAverage(AttendanceLogicalStatus.ABSENT, rounded); + private countWithStatus(matchingType: AttendanceLogicalStatus) { + return this.events.reduce( + (total, event) => total + event.countWithStatus(matchingType), + 0 + ); } - private countIndividual( - childId: string, - countingType: AttendanceLogicalStatus - ) { - return this.events.filter( - (eventNote) => - eventNote.getAttendance(childId)?.status.countAs === countingType - ).length; + getAttendancePercentageAverage(): number { + return this.countPercentage(AttendanceLogicalStatus.PRESENT, false); } - private countAverage( + private countPercentage( matchingType: AttendanceLogicalStatus, rounded: boolean = false ) { @@ -161,8 +143,7 @@ export class ActivityAttendance extends Entity { { total: 0, matching: 0 } ); - const result = - calculatedStats.matching / (calculatedStats.total / this.events.length); + const result = calculatedStats.matching / calculatedStats.total; if (rounded) { return Math.round(result * 10) / 10; } else { @@ -170,6 +151,23 @@ export class ActivityAttendance extends Entity { } } + /** + * The number of events that have at least one participant with an undefined status. + * This may occur when the user does not complete the full roll call or skips participants. + * The count of unknown status can indicate if manual checking and corrections are required. + * + * @param forChildId filter the calculation to only include status of the given participant id + */ + countEventsWithUnknownStatus(forChildId?: string): number { + return this.events + .filter((e) => !forChildId || e.children.includes(forChildId)) + .reduce( + (count: number, e: EventNote) => + e.hasUnknownAttendances(forChildId) ? count + 1 : count, + 0 + ); + } + recalculateStats() { this.individualStatusTypeCounts = new Map(); this.individualLogicalStatusCounts = new Map(); diff --git a/src/app/child-dev-project/children/child-details/grouped-child-attendance/grouped-child-attendance.component.html b/src/app/child-dev-project/children/child-details/grouped-child-attendance/grouped-child-attendance.component.html index 7c18702371..667bfe637e 100644 --- a/src/app/child-dev-project/children/child-details/grouped-child-attendance/grouped-child-attendance.component.html +++ b/src/app/child-dev-project/children/child-details/grouped-child-attendance/grouped-child-attendance.component.html @@ -1,3 +1,7 @@ +
+ +
+ Show unrelated tooltip src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.html - 27 + 40 @@ -21,18 +21,18 @@ slider src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.html - 29 + 42 Load all records Lade alle Aufzeichnungen + load all records, not only the ones from the last 6 months + load-all button src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.html - 39,42 + 52 - load all records, not only the ones from the last 6 months - load-all button Month @@ -40,7 +40,7 @@ The month something took place src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 32 + 34 @@ -50,7 +50,7 @@ How many children are present at a meeting src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 38 + 40 @@ -353,33 +353,62 @@ days present Tage anwesend - How many days a child or a class was present + How many days a child was present days present src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html - 53 + 50 days absent Tage abwesend - How many days a child or a class was absent + How many days a child was absent days absent src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html - 73 + 63 unknown status - Status unbekannt - How many days the presence or absence of a child is - unknown + Status unklar + How many days the presence or absence of a child is unknown days absent src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html - 94 + 76 + + + + Total present + Summe anwesend + + src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html + 91 + + How many children were present + Total present + + + Total absent + Summe abwesend + + src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html + 104 + How many children were absent + Total absent + + + Total unknown + Summe unklar + + src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html + 117 + + How many children have an unknown presence or absence status + Total unknown Event @@ -1560,6 +1589,15 @@ resolved Conflicts to be resolved + + Table showing the conflicts + Table showing the conflicts + + src/app/conflict-resolution/conflict-resolution-list/conflict-resolution-list.component.html + 15,17 + + A table showing conflicts between sets of data + Data Datum @@ -1724,6 +1762,14 @@ 116 + + Table showing alerts + Table showing alerts + + src/app/core/admin/admin/admin.component.html + 117,119 + + Overwrite complete database? Komplette Datenbank überschreiben? @@ -2018,47 +2064,47 @@ not at all überhaupt nicht + Rating answer - src/app/features/historical-data/rating-answers.ts + src/app/features/historical-data/model/rating-answers.ts 4 - Rating answer rarely kaum + Rating answer - src/app/features/historical-data/rating-answers.ts + src/app/features/historical-data/model/rating-answers.ts 8 - Rating answer usually normalerweise + Rating answer - src/app/features/historical-data/rating-answers.ts + src/app/features/historical-data/model/rating-answers.ts 12 - Rating answer absolutely absolut + Rating answer - src/app/features/historical-data/rating-answers.ts + src/app/features/historical-data/model/rating-answers.ts 16 - Rating answer N/A k.A. + Rating answer - src/app/features/historical-data/rating-answers.ts + src/app/features/historical-data/model/rating-answers.ts 20 - Rating answer Empty complete database? @@ -2084,6 +2130,14 @@ 171 + + Table showing a list of all users + Table showing a list of all users + + src/app/core/admin/user-list/user-list.component.html + 1,3 + + ID ID @@ -2093,18 +2147,6 @@ User-ID - - Could not load file '': - Datei ': konnte nicht geladen werden - - src/app/core/app-config/app-config.ts - 82,84 - - Coming Soon Bald verfügbar @@ -3225,7 +3267,7 @@ 72 - src/app/features/historical-data/historical-entity-data.ts + src/app/features/historical-data/model/historical-entity-data.ts 11 @@ -3449,6 +3491,19 @@ 28 + + Picture of a child + Picture of a child + + src/app/child-dev-project/children/child-block/child-block.component.html + 9 + + + src/app/child-dev-project/children/child-block/child-block.component.html + 25 + + Alternative text for small picture + PN PN @@ -3959,6 +4014,15 @@ Subtitle of the child-bmi dashboard-component + + Table showing the most critical BMI results + Table showing the most critical BMI results + + src/app/child-dev-project/children/children-bmi-dashboard/children-bmi-dashboard.component.html + 12 + + Label for BMI dashboard + Cases Fälle @@ -3968,6 +4032,33 @@ Dashboard title naming total number of beneficiaries in the system + + + + + src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.html + 10,25 + + Label for children count dashboard + Center Schulzentrum @@ -4072,7 +4163,7 @@ Events of an attendance src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 47 + 49 src/app/core/config/config-fix.ts @@ -4085,7 +4176,7 @@ Percentage of people that attended an event src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 53 + 55 src/app/child-dev-project/attendance/attendance-details/attendance-details.component.ts @@ -4200,6 +4291,15 @@ 24 + + Table showing organization progress + Table showing organization progress + + src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html + 8,10 + + Label for progress dashboard + Coaching Class Coaching-Klasse @@ -4331,14 +4431,6 @@ 124 - - Could not create form config for : - Konnte Formular-Konfiguration für nicht erstellen: - - src/app/core/entity-components/entity-form/entity-form.service.ts - 32 - - Could not save : Speichern von fehlgeschlagen: @@ -4650,7 +4742,7 @@ Save changes header src/app/core/form-dialog/form-dialog.service.ts - 59 + 58 @@ -4659,7 +4751,7 @@ Save changes message src/app/core/form-dialog/form-dialog.service.ts - 60 + 59 @@ -5197,6 +5289,15 @@ e.g. 'without religion' Excluding a certain property + + Table showing the report results + Table showing the report results + + src/app/features/reporting/reporting/object-table/object-table.component.html + 5 + + Label for table showing report result + Reports diff --git a/src/locale/messages.fr.xlf b/src/locale/messages.fr.xlf index 4fc3287dd6..6cc42454ed 100644 --- a/src/locale/messages.fr.xlf +++ b/src/locale/messages.fr.xlf @@ -23,7 +23,7 @@ 72 - src/app/features/historical-data/historical-entity-data.ts + src/app/features/historical-data/model/historical-entity-data.ts 11 @@ -173,37 +173,50 @@ 28 + + Picture of a child + Picture of a child + + src/app/child-dev-project/children/child-block/child-block.component.html + 9 + + + src/app/child-dev-project/children/child-block/child-block.component.html + 25 + + Alternative text for small picture + Activate to also show entries for the activity that do not have any events with actual participation of this person Activer pour afficher également les données de l'activité qui n'ont pas d'événements comportant la participation effective de cette personne. - - src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.html - 27,37 - Tooltip that will appear when hovered over the show-unrelated button Show unrelated tooltip + + src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.html + 40 + Also show unrelated Afficher les éléments non liés - - src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.html - 29,34 - show unrelated attendance-entries for an activity that are not linked to the child of interest slider + + src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.html + 42 + Load all records Charger toutes les données + load all records, not only the ones from the last 6 months + load-all button src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.html - 39,42 + 52 - load all records, not only the ones from the last 6 months - load-all button Month @@ -211,7 +224,7 @@ The month something took place src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 32 + 34 @@ -221,7 +234,7 @@ How many children are present at a meeting src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 38 + 40 @@ -230,7 +243,7 @@ Events of an attendance src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 47 + 49 src/app/core/config/config-fix.ts @@ -243,7 +256,7 @@ Percentage of people that attended an event src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 53 + 55 src/app/child-dev-project/attendance/attendance-details/attendance-details.component.ts @@ -560,33 +573,62 @@ days present jours présent + How many days a child was present + days present src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html - 53 + 50 - How many days a child or a class was present - days present days absent jours absent + How many days a child was absent + days absent src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html - 73 + 63 - How many days a child or a class was absent - days absent unknown status statut inconnu + How many days the presence or absence of a child is unknown + days absent src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html - 94 + 76 - How many days the presence or absence of a child is - unknown - days absent + + + Total present + Total present + + src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html + 91 + + How many children were present + Total present + + + Total absent + Total absent + + src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html + 104 + + How many children were absent + Total absent + + + Total unknown + Total unknown + + src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html + 117 + + How many children have an unknown presence or absence status + Total unknown Event @@ -845,6 +887,15 @@ Subtitle of the child-bmi dashboard-component + + Table showing the most critical BMI results + Table showing the most critical BMI results + + src/app/child-dev-project/children/children-bmi-dashboard/children-bmi-dashboard.component.html + 12 + + Label for BMI dashboard + Cases Participants @@ -854,6 +905,33 @@ Dashboard title naming total number of beneficiaries in the system + + + + + src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.html + 10,25 + + Label for children count dashboard + All Tous @@ -2331,6 +2409,15 @@ 24 + + Table showing organization progress + Table showing organization progress + + src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html + 8,10 + + Label for progress dashboard + Current En ce moment @@ -2694,6 +2781,15 @@ resolved Conflicts to be resolved + + Table showing the conflicts + Table showing the conflicts + + src/app/conflict-resolution/conflict-resolution-list/conflict-resolution-list.component.html + 15,17 + + A table showing conflicts between sets of data + Data Données @@ -2858,6 +2954,14 @@ 116 + + Table showing alerts + Table showing alerts + + src/app/core/admin/admin/admin.component.html + 117,119 + + Overwrite complete database? Écraser la base de donnée entière? @@ -2910,6 +3014,14 @@ 171 + + Table showing a list of all users + Table showing a list of all users + + src/app/core/admin/user-list/user-list.component.html + 1,3 + + ID Identifiant @@ -2944,18 +3056,6 @@ Details of a certain User - - Could not load file '': - Échec lors du chargement du fichier '': - - src/app/core/app-config/app-config.ts - 82,84 - - Coming Soon Bientôt disponible @@ -4095,14 +4195,6 @@ 124 - - Could not create form config for : - Echec pour créer une form config pour : - - src/app/core/entity-components/entity-form/entity-form.service.ts - 32 - - Could not save : Echec pour sauvegarder : @@ -4362,20 +4454,20 @@ Save Changes? Sauvegarder les modifications? + Save changes header src/app/core/form-dialog/form-dialog.service.ts - 59 + 58 - Save changes header Do you want to save the changes you made to the record? Voulez-vous sauvegarder les modifications apportées à cet élément? + Save changes message src/app/core/form-dialog/form-dialog.service.ts - 60 + 59 - Save changes message More Information @@ -5192,47 +5284,47 @@ not at all pas du tout + Rating answer - src/app/features/historical-data/rating-answers.ts + src/app/features/historical-data/model/rating-answers.ts 4 - Rating answer rarely rarement + Rating answer - src/app/features/historical-data/rating-answers.ts + src/app/features/historical-data/model/rating-answers.ts 8 - Rating answer usually souvent + Rating answer - src/app/features/historical-data/rating-answers.ts + src/app/features/historical-data/model/rating-answers.ts 12 - Rating answer absolutely absolument + Rating answer - src/app/features/historical-data/rating-answers.ts + src/app/features/historical-data/model/rating-answers.ts 16 - Rating answer N/A N/D + Rating answer - src/app/features/historical-data/rating-answers.ts + src/app/features/historical-data/model/rating-answers.ts 20 - Rating answer not @@ -5254,6 +5346,15 @@ e.g. 'without religion' Excluding a certain property + + Table showing the report results + Table showing the report results + + src/app/features/reporting/reporting/object-table/object-table.component.html + 5 + + Label for table showing report result + Reports diff --git a/src/locale/messages.xlf b/src/locale/messages.xlf index 050f3cd0a0..d8a74d06c6 100644 --- a/src/locale/messages.xlf +++ b/src/locale/messages.xlf @@ -6,7 +6,7 @@ Activate to also show entries for the activity that do not have any events with actual participation of this person src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.html - 27,37 + 40,50 Tooltip that will appear when hovered over the show-unrelated button @@ -16,7 +16,7 @@ Also show unrelated src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.html - 29,34 + 42,47 show unrelated attendance-entries for an activity that are not linked to the child of interest @@ -26,7 +26,7 @@ Load all records src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.html - 39,42 + 52,55 load all records, not only the ones from the last 6 months load-all button @@ -35,7 +35,7 @@ Month src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 32,30 + 34,32 The month something took place @@ -43,7 +43,7 @@ Present src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 38,36 + 40,38 Title of table column How many children are present at a meeting @@ -52,7 +52,7 @@ Events src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 47,45 + 49,47 src/app/core/config/config-fix.ts @@ -64,7 +64,7 @@ Attended src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 53,51 + 55,53 src/app/child-dev-project/attendance/attendance-details/attendance-details.component.ts @@ -346,30 +346,56 @@ days present src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html - 53 + 50 - How many days a child or a class was present + How many days a child was present days present days absent src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html - 73 + 63 - How many days a child or a class was absent + How many days a child was absent days absent unknown status src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html - 94 + 76 - How many days the presence or absence of a child is - unknown + How many days the presence or absence of a child is unknown days absent + + Total present + + src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html + 91 + + How many children were present + Total present + + + Total absent + + src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html + 104 + + How many children were absent + Total absent + + + Total unknown + + src/app/child-dev-project/attendance/attendance-details/attendance-details.component.html + 117 + + How many children have an unknown presence or absence status + Total unknown + Event @@ -583,7 +609,7 @@ 72 - src/app/features/historical-data/historical-entity-data.ts + src/app/features/historical-data/model/historical-entity-data.ts 11 Label for date of the ASER results @@ -720,6 +746,18 @@ Label reading level + + Picture of a child + + src/app/child-dev-project/children/child-block/child-block.component.html + 9 + + + src/app/child-dev-project/children/child-block/child-block.component.html + 25 + + Alternative text for small picture + class @@ -737,6 +775,14 @@ Subtitle of the child-bmi dashboard-component + + Table showing the most critical BMI results + + src/app/child-dev-project/children/children-bmi-dashboard/children-bmi-dashboard.component.html + 12 + + Label for BMI dashboard + Cases @@ -745,6 +791,23 @@ Dashboard title naming total number of beneficiaries in the system + + + + src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.html + 10,25 + + Label for children count dashboard + All @@ -2202,6 +2265,14 @@ resolved Conflicts to be resolved + + Table showing the conflicts + + src/app/conflict-resolution/conflict-resolution-list/conflict-resolution-list.component.html + 15,17 + + A table showing conflicts between sets of data + Data @@ -2343,10 +2414,17 @@ Alert Log src/app/core/admin/admin/admin.component.html - 116,119 + 116,118 Alert log header + + Table showing alerts + + src/app/core/admin/admin/admin.component.html + 117,119 + + Overwrite complete database? @@ -2391,6 +2469,13 @@ 171,170 + + Table showing a list of all users + + src/app/core/admin/user-list/user-list.component.html + 1,3 + + ID @@ -2422,15 +2507,6 @@ Details of a certain User - - Could not load file '': - - src/app/core/app-config/app-config.ts - 82,84 - - Coming Soon @@ -3418,13 +3494,6 @@ 124 - - Could not create form config for : - - src/app/core/entity-components/entity-form/entity-form.service.ts - 32 - - Could not save : @@ -3717,7 +3786,7 @@ Save Changes? src/app/core/form-dialog/form-dialog.service.ts - 59 + 58 Save changes header @@ -3725,7 +3794,7 @@ Do you want to save the changes you made to the record? src/app/core/form-dialog/form-dialog.service.ts - 60 + 59 Save changes message @@ -4416,7 +4485,7 @@ not at all - src/app/features/historical-data/rating-answers.ts + src/app/features/historical-data/model/rating-answers.ts 4 Rating answer @@ -4424,7 +4493,7 @@ rarely - src/app/features/historical-data/rating-answers.ts + src/app/features/historical-data/model/rating-answers.ts 8 Rating answer @@ -4432,7 +4501,7 @@ usually - src/app/features/historical-data/rating-answers.ts + src/app/features/historical-data/model/rating-answers.ts 12 Rating answer @@ -4440,7 +4509,7 @@ absolutely - src/app/features/historical-data/rating-answers.ts + src/app/features/historical-data/model/rating-answers.ts 16 Rating answer @@ -4448,7 +4517,7 @@ N/A - src/app/features/historical-data/rating-answers.ts + src/app/features/historical-data/model/rating-answers.ts 20 Rating answer @@ -4627,6 +4696,14 @@ 24 + + Table showing organization progress + + src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html + 8,10 + + Label for progress dashboard + Progress of X @@ -4653,6 +4730,14 @@ e.g. 'without religion' Excluding a certain property + + Table showing the report results + + src/app/features/reporting/reporting/object-table/object-table.component.html + 5 + + Label for table showing report result + Reports diff --git a/src/styles.scss b/src/styles.scss index 7ffddf05f6..6e1a58c7a7 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -29,6 +29,7 @@ $max-icon-width: 22px; $standard-margin-small: 8px; body { + font-family: Roboto, "Helvetica Neue", sans-serif; margin: 0; } @@ -143,7 +144,7 @@ $color-warning-urgent: #fd7272; .w-OK { - background-color: adjust-color($color-warning-warning, $alpha: -0.75); + background-color: adjust-color($color-warning-ok, $alpha: -0.75); } .w-WARNING { background-color: adjust-color($color-warning-warning, $alpha: -0.5); From 140a222843f182561b1ce2ea341d08e5b6c2909c Mon Sep 17 00:00:00 2001 From: Simon <33730997+TheSlimvReal@users.noreply.github.com> Date: Fri, 8 Apr 2022 10:04:02 +0200 Subject: [PATCH 08/13] fix(core): better exception details loging (#1181) --- src/app/core/database/pouch-database.spec.ts | 41 --------------- src/app/core/database/pouch-database.ts | 53 ++++++++++++-------- 2 files changed, 33 insertions(+), 61 deletions(-) diff --git a/src/app/core/database/pouch-database.spec.ts b/src/app/core/database/pouch-database.spec.ts index dbed3a1da0..2cb24e1e0f 100644 --- a/src/app/core/database/pouch-database.spec.ts +++ b/src/app/core/database/pouch-database.spec.ts @@ -215,45 +215,4 @@ describe("PouchDatabase tests", () => { expect(result).toEqual(testQueryResults); expect(pouchDB.query).toHaveBeenCalledWith(testQuery, {}); }); - - it("should log the ID when requesting a not existing entity", async () => { - const notExistingId = "Not-existing-id"; - return expectPromiseToRejectWithDocId( - database.get(notExistingId), - notExistingId - ); - }); - - it("should log the ID when putting a doc with wrong rev", async () => { - const doc = { - _id: "someId", - _rev: "1-invalidRev", - }; - return expectPromiseToRejectWithDocId(database.put(doc), doc._id); - }); - - it("should log the ID when removing a not existing doc", () => { - const notExistingDoc = { - _id: "not_existing_id", - _rev: "1-someRev", - }; - return expectPromiseToRejectWithDocId( - database.remove(notExistingDoc), - notExistingDoc._id - ); - }); - - it("should log the view name when querying a not existing view", () => { - const notExistingView = "search_index/by_name"; - return expectPromiseToRejectWithDocId( - database.query(notExistingView, {}), - notExistingView - ); - }); - - function expectPromiseToRejectWithDocId(prom: Promise, id: string) { - return prom - .then(() => fail()) - .catch((e) => expect(e.affectedDocument).toBe(id)); - } }); diff --git a/src/app/core/database/pouch-database.ts b/src/app/core/database/pouch-database.ts index 4361474ce4..93db3a4194 100644 --- a/src/app/core/database/pouch-database.ts +++ b/src/app/core/database/pouch-database.ts @@ -89,8 +89,7 @@ export class PouchDatabase extends Database { return undefined; } } - err.affectedDocument = id; - throw err; + throw new DatabaseException(err); }); } @@ -105,13 +104,18 @@ export class PouchDatabase extends Database { * @param options PouchDB options object as in the normal PouchDB library */ allDocs(options?: GetAllOptions) { - return this._pouchDB.allDocs(options).then((result) => { - const resultArray = []; - for (const row of result.rows) { - resultArray.push(row.doc); - } - return resultArray; - }); + return this._pouchDB + .allDocs(options) + .then((result) => { + const resultArray = []; + for (const row of result.rows) { + resultArray.push(row.doc); + } + return resultArray; + }) + .catch((err) => { + throw new DatabaseException(err); + }); } /** @@ -131,8 +135,7 @@ export class PouchDatabase extends Database { if (err.status === 409) { return this.resolveConflict(object, forceOverwrite, err); } else { - err.affectedDocument = object._id; - throw err; + throw new DatabaseException(err); } }); } @@ -145,8 +148,7 @@ export class PouchDatabase extends Database { */ remove(object: any) { return this._pouchDB.remove(object).catch((err) => { - err.affectedDocument = object._id; - throw err; + throw new DatabaseException(err); }); } @@ -156,9 +158,13 @@ export class PouchDatabase extends Database { * @param remoteDatabase the PouchDB instance of the remote database */ sync(remoteDatabase) { - return this._pouchDB.sync(remoteDatabase, { - batch_size: 500, - }); + return this._pouchDB + .sync(remoteDatabase, { + batch_size: 500, + }) + .catch((err) => { + throw new DatabaseException(err); + }); } public async destroy(): Promise { @@ -181,8 +187,7 @@ export class PouchDatabase extends Database { options: QueryOptions ): Promise { return this._pouchDB.query(fun, options).catch((err) => { - err.affectedDocument = fun; - throw err; + throw new DatabaseException(err); }); } @@ -254,8 +259,7 @@ export class PouchDatabase extends Database { return this.put(newObject); } else { existingError.message = existingError.message + " (unable to resolve)"; - existingError.affectedDocument = newObject._id; - throw existingError; + throw new DatabaseException(existingError); } } @@ -264,3 +268,12 @@ export class PouchDatabase extends Database { return undefined; } } + +/** + * This overwrites PouchDB's error class which only logs limited information + */ +class DatabaseException { + constructor(error: PouchDB.Core.Error) { + Object.assign(this, error); + } +} From 6e93e45aadeea31327a1f72de854f0425b959a8f Mon Sep 17 00:00:00 2001 From: SuttArt <77150155+SuttArt@users.noreply.github.com> Date: Fri, 8 Apr 2022 10:54:20 +0200 Subject: [PATCH 09/13] doc: addded documentation for E2E tests Co-authored-by: NiklasZinngrebe Co-authored-by: Simon <33730997+TheSlimvReal@users.noreply.github.com> Co-authored-by: Simon --- .../how-to-guides/write-e2e-tests.md | 154 ++++++++++++++++++ .../{write-tests.md => write-unit-tests.md} | 0 doc/compodoc_sources/summary.json | 6 +- 3 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 doc/compodoc_sources/how-to-guides/write-e2e-tests.md rename doc/compodoc_sources/how-to-guides/{write-tests.md => write-unit-tests.md} (100%) diff --git a/doc/compodoc_sources/how-to-guides/write-e2e-tests.md b/doc/compodoc_sources/how-to-guides/write-e2e-tests.md new file mode 100644 index 0000000000..2834209d7b --- /dev/null +++ b/doc/compodoc_sources/how-to-guides/write-e2e-tests.md @@ -0,0 +1,154 @@ +# How to write an End-to-End (E2E) test + +## Resources and How-Tos +* [Documentation](https://docs.cypress.io/guides/getting-started/writing-your-first-test) +* [Introduction to Cypress](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress) +* [Writing and Organizing Tests](https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests) + +## Instructions +### 1. Create a file + +#### Manually +All tests are located under `ndb-core/e2e/integration` directory. +Create a file with the ending `.spec.ts` or just `.ts` + +#### With command +Cypress offers a command line tool that automatically creates a test file with a minimal setup for you. +In order to create a test called `loging` run: + +> ng generate @cypress/schematic:e2e --name=login --path=e2e/integration + +#### Organizing tests +If you have tests that fit together, create a separate folder inside for them. +This grouping will help you navigate through them better, e.g. `/../integration/child-tests/` + +### 2. Gherkin Template +#### What is Gherkin and how does it help writing E2E tests + +Gherkin is a formal language that primarily serves as a communication language in agile teams for describing system behaviour based on the concrete examples and thus supports the following goals: + +- Creation of understandable and executable specification for all stakeholders in agile teams. +- Starting point for the automation of tests +- Documentation of the system behaviour + +More about Gherkin: [Gherkin](https://cucumber.io/docs/gherkin/) + + +#### Example of a test description: +``` +Scenario: Linking a child to a school +Given I am on the details page of a child +When I add an entry in the 'Previous Schools' section with a specific school +Then I can see that child in the 'Children Overview' of the details page of this school +``` + +Gherkin uses a set of special keywords to give structure and meaning to executable specifications. + +The primary keywords are: +- Given +- When +- Then +- And +- But + +More info here: [Keywords](https://cucumber.io/docs/gherkin/reference/#keywords) + +Each action from Gherkin is written in the test description (e.g. `it("", function{})`). + +### 3. Write an E2E-Test +#### Some useful links + +* [API: Table of Contents](https://docs.cypress.io/api/table-of-contents) +* [Setup and Teardown: Hooks](https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Hooks) +* [Assertions](https://docs.cypress.io/guides/references/assertions) +* [Catalog of Events](https://docs.cypress.io/api/events/catalog-of-events) + +#### Example +``` +describe("Scenario of the Test", () => { + before("Given I am at login page", function () { + // In the before() function we can describe our start Point and specific variables + //e.g. + cy.visit("http://localhost:4200"); + cy.wrap("Something").as("VariableName"); + ... + }); + + // In the it() function the single tests of the test scenario can be written + it("When I add an entry in the Previous School section", function () { + // get the specific button and click on it + cy.get("buttonElement") + .should("contain", "ButtonName") + .click(); + // choose object from Dropdown menu type our Variable and click on it + cy.get('DropdownMenu') + .type(this.VariableName) + .click(); + ... + }); + + //Write more test cases here by adding aditional it functions tags +}); + +``` +[A solid test generally covers 3 phases](https://docs.cypress.io/guides/getting-started/writing-your-first-test#Write-a-real-test): + +
    +
  1. Set up the application state.
  2. +
  3. Take an action.
  4. +
  5. Make an assertion about the resulting application state.
  6. +
+ +#### What role does Gherkin play? + +We take the previous Scenario as example: +``` +Scenario: Linking a child to a school +Given I am on the details page of a child +When I add an entry in the 'Previous Schools' section with a specific school +Then I can see that child in the 'Children Overview' of the details page of this school +``` + +We can translate this into the following template: +``` +describe("Scenario: Linking a child to a school", () => { + before("Given I am on the details page of a child", function() { + + }); + + it("When I add an entry in the 'Previous Schools' section with a specific school", function () { + + }); + + it("Then I can see that child in the 'Children Overview' of the details page of this school", function () { + + }); + +}); +``` +Now the `before(...)` and `it(...)` blocks should be filled with the code that corresponds to the according description. + +### 4. Run the E2E-Tests + +To open Cypress in the "Open-Mode" run the following command from the project root. + +> npm run e2e-open + +This will start the [browser](https://docs.cypress.io/guides/guides/launching-browsers) with the Cypress Interface. +This Interface is called [Test Runner](https://docs.cypress.io/guides/core-concepts/test-runner) where you can select the tests to be executed and interactively follow and debug the test execution. + +The [Selector Playground](https://docs.cypress.io/guides/core-concepts/test-runner#Selector-Playground) is also available in the interface, which is a useful tool to write new tests by selecting items in the DOM. + +The tests are also executed in each pull-request. +The check with the name `Pipeline / run-e2e-tests (pull_request)` indicates whether the E2E tests were successful. + +### Aam Digital specific best practices +TBD. + +### Outlook: Better Gherkin integration + +The specification of the scenarios with Gherkin is normally stored in so-called Feature Files. +These files are human-readable text files. +It makes sense to use [cypress-cucumber-preprocessor](https://github.com/TheBrainFamily/cypress-cucumber-preprocessor) in the future. +This or a similar framework can speed up test creation because it allows Feature Files to be interpreted into Cypress code + diff --git a/doc/compodoc_sources/how-to-guides/write-tests.md b/doc/compodoc_sources/how-to-guides/write-unit-tests.md similarity index 100% rename from doc/compodoc_sources/how-to-guides/write-tests.md rename to doc/compodoc_sources/how-to-guides/write-unit-tests.md diff --git a/doc/compodoc_sources/summary.json b/doc/compodoc_sources/summary.json index 999a21f374..b7b77b51fe 100644 --- a/doc/compodoc_sources/summary.json +++ b/doc/compodoc_sources/summary.json @@ -55,7 +55,11 @@ }, { "title": "Write Automated Unit Tests", - "file": "how-to-guides/write-tests.md" + "file": "how-to-guides/write-unit-tests.md" + }, + { + "title": "Write E2E Tests", + "file": "how-to-guides/write-e2e-tests.md" }, { "title": "Document Code", From 73744eb74c556e92dd4b548adec84fe5b1bf352d Mon Sep 17 00:00:00 2001 From: christophscheuing <47225324+christophscheuing@users.noreply.github.com> Date: Fri, 8 Apr 2022 21:37:21 +0200 Subject: [PATCH 10/13] feat: Added button to install app from browser fixes: #592 Co-authored-by: Simon Co-authored-by: Simon <33730997+TheSlimvReal@users.noreply.github.com> --- .../navigation/navigation.component.html | 5 +- src/app/core/ui/ui.module.ts | 2 + src/app/core/ui/ui/ui.component.html | 4 + .../pwa-install/pwa-install.component.html | 45 +++++++ .../pwa-install/pwa-install.component.scss | 7 + .../pwa-install/pwa-install.component.spec.ts | 69 ++++++++++ src/app/pwa-install/pwa-install.component.ts | 41 ++++++ src/app/pwa-install/pwa-install.module.ts | 32 +++++ .../pwa-install/pwa-install.service.spec.ts | 74 +++++++++++ src/app/pwa-install/pwa-install.service.ts | 123 ++++++++++++++++++ src/app/pwa-install/pwa-install.stories.ts | 32 +++++ src/app/utils/di-tokens.ts | 6 + src/locale/messages.de.xlf | 66 +++++++++- src/locale/messages.fr.xlf | 66 +++++++++- src/locale/messages.xlf | 60 ++++++++- 15 files changed, 613 insertions(+), 19 deletions(-) create mode 100644 src/app/pwa-install/pwa-install.component.html create mode 100644 src/app/pwa-install/pwa-install.component.scss create mode 100644 src/app/pwa-install/pwa-install.component.spec.ts create mode 100644 src/app/pwa-install/pwa-install.component.ts create mode 100644 src/app/pwa-install/pwa-install.module.ts create mode 100644 src/app/pwa-install/pwa-install.service.spec.ts create mode 100644 src/app/pwa-install/pwa-install.service.ts create mode 100644 src/app/pwa-install/pwa-install.stories.ts create mode 100644 src/app/utils/di-tokens.ts diff --git a/src/app/core/navigation/navigation/navigation.component.html b/src/app/core/navigation/navigation/navigation.component.html index 17ce8aee4a..3ab58d3d60 100644 --- a/src/app/core/navigation/navigation/navigation.component.html +++ b/src/app/core/navigation/navigation/navigation.component.html @@ -26,7 +26,10 @@ [class.primary-background]="item.link === activeLink" > - +
{{ item.label }}
diff --git a/src/app/core/ui/ui.module.ts b/src/app/core/ui/ui.module.ts index 8b654f579a..7b255d7c87 100644 --- a/src/app/core/ui/ui.module.ts +++ b/src/app/core/ui/ui.module.ts @@ -41,6 +41,7 @@ import { PermissionsModule } from "../permissions/permissions.module"; import { EntityUtilsModule } from "../entity-components/entity-utils/entity-utils.module"; import { TranslationModule } from "../translation/translation.module"; import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; +import { PwaInstallModule } from "app/pwa-install/pwa-install.module"; /** * The core user interface structure that ties different components together into the overall app layout. @@ -70,6 +71,7 @@ import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; ReactiveFormsModule, TranslationModule, FontAwesomeModule, + PwaInstallModule, ], declarations: [SearchComponent, UiComponent, PrimaryActionComponent], exports: [UiComponent], diff --git a/src/app/core/ui/ui/ui.component.html b/src/app/core/ui/ui/ui.component.html index 7ab5130bc3..ea710ba79a 100644 --- a/src/app/core/ui/ui/ui.component.html +++ b/src/app/core/ui/ui/ui.component.html @@ -83,6 +83,10 @@
+ +
+
+ + + + + To install the app + + +
    +
  1. + + tap on + + + + + at the bottom + +
  2. +
  3. + + and then tap on + + + + (Add to Homescreen). + +
  4. +
+
diff --git a/src/app/pwa-install/pwa-install.component.scss b/src/app/pwa-install/pwa-install.component.scss new file mode 100644 index 0000000000..caf91319d3 --- /dev/null +++ b/src/app/pwa-install/pwa-install.component.scss @@ -0,0 +1,7 @@ +.pwa-install-button { + border: solid 1px rgba(0, 0, 0, 0.12); + border-right: 0; + border-radius: 0; + overflow: hidden; + border-left: 0; + } diff --git a/src/app/pwa-install/pwa-install.component.spec.ts b/src/app/pwa-install/pwa-install.component.spec.ts new file mode 100644 index 0000000000..4e716e9b50 --- /dev/null +++ b/src/app/pwa-install/pwa-install.component.spec.ts @@ -0,0 +1,69 @@ +import { fakeAsync, TestBed, tick } from "@angular/core/testing"; + +import { PwaInstallComponent } from "./pwa-install.component"; +import { PwaInstallModule } from "./pwa-install.module"; +import { PwaInstallService, PWAInstallType } from "./pwa-install.service"; +import { MatSnackBar } from "@angular/material/snack-bar"; +import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testing"; +import { Subject } from "rxjs"; +import { take } from "rxjs/operators"; + +describe("PwaInstallComponent", () => { + let mockPWAInstallService: jasmine.SpyObj; + let mockSnackbar: jasmine.SpyObj; + const pwaInstallResult = new Subject(); + + beforeEach(async () => { + mockPWAInstallService = jasmine.createSpyObj( + ["getPWAInstallType", "installPWA", "registerPWAInstallListener"], + { canInstallDirectly: pwaInstallResult.pipe(take(1)).toPromise() } + ); + mockSnackbar = jasmine.createSpyObj(["openFromTemplate"]); + await TestBed.configureTestingModule({ + imports: [PwaInstallModule, FontAwesomeTestingModule], + providers: [ + { provide: PwaInstallService, useValue: mockPWAInstallService }, + { provide: MatSnackBar, useValue: mockSnackbar }, + ], + }).compileComponents(); + }); + + it("should create", () => { + expect(createComponent()).toBeTruthy(); + }); + + it("should show the pwa install instructions on iOS devices", () => { + mockPWAInstallService.getPWAInstallType.and.returnValue( + PWAInstallType.ShowiOSInstallInstructions + ); + + const component = createComponent(); + expect(component.showPWAInstallButton).toBeTrue(); + + component.pwaInstallButtonClicked(); + expect(mockSnackbar.openFromTemplate).toHaveBeenCalled(); + }); + + it("should call installPWA when no install instructions are defined and remove button once confirmed", fakeAsync(() => { + pwaInstallResult.next(); + + const component = createComponent(); + console.log("created"); + tick(); + console.log("checking"); + expect(component.showPWAInstallButton).toBeTrue(); + + mockPWAInstallService.installPWA.and.resolveTo({ outcome: "accepted" }); + component.pwaInstallButtonClicked(); + expect(mockPWAInstallService.installPWA).toHaveBeenCalled(); + + tick(); + expect(component.showPWAInstallButton).toBeFalse(); + })); + + function createComponent(): PwaInstallComponent { + const fixture = TestBed.createComponent(PwaInstallComponent); + fixture.detectChanges(); + return fixture.componentInstance; + } +}); diff --git a/src/app/pwa-install/pwa-install.component.ts b/src/app/pwa-install/pwa-install.component.ts new file mode 100644 index 0000000000..94077ba0e9 --- /dev/null +++ b/src/app/pwa-install/pwa-install.component.ts @@ -0,0 +1,41 @@ +import { Component, TemplateRef, ViewChild } from "@angular/core"; +import { MatSnackBar } from "@angular/material/snack-bar"; +import { PwaInstallService, PWAInstallType } from "./pwa-install.service"; +@Component({ + selector: "app-pwa-install", + templateUrl: "./pwa-install.component.html", + styleUrls: ["./pwa-install.component.scss"], +}) +export class PwaInstallComponent { + @ViewChild("iOSInstallInstructions") + templateIOSInstallInstructions: TemplateRef; + + showPWAInstallButton = false; + + private readonly pwaInstallType: PWAInstallType; + + constructor( + public snackBar: MatSnackBar, + private pwaInstallService: PwaInstallService + ) { + this.pwaInstallType = pwaInstallService.getPWAInstallType(); + if (this.pwaInstallType === PWAInstallType.ShowiOSInstallInstructions) { + this.showPWAInstallButton = true; + } + pwaInstallService.canInstallDirectly.then(() => { + this.showPWAInstallButton = true; + }); + } + + pwaInstallButtonClicked() { + if (this.pwaInstallType === PWAInstallType.ShowiOSInstallInstructions) { + this.snackBar.openFromTemplate(this.templateIOSInstallInstructions); + } else { + this.pwaInstallService.installPWA().then((choice) => { + if (choice.outcome === "accepted") { + this.showPWAInstallButton = false; + } + }); + } + } +} diff --git a/src/app/pwa-install/pwa-install.module.ts b/src/app/pwa-install/pwa-install.module.ts new file mode 100644 index 0000000000..a25a6305ca --- /dev/null +++ b/src/app/pwa-install/pwa-install.module.ts @@ -0,0 +1,32 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { PwaInstallComponent } from "./pwa-install.component"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; +import { MatButtonModule } from "@angular/material/button"; +import { FlexLayoutModule } from "@angular/flex-layout"; +import { TranslationModule } from "app/core/translation/translation.module"; +import { MatSidenavModule } from "@angular/material/sidenav"; +import { MatToolbarModule } from "@angular/material/toolbar"; +import { PwaInstallService } from "./pwa-install.service"; +import { MatSnackBarModule } from "@angular/material/snack-bar"; +import { WINDOW_TOKEN } from "../utils/di-tokens"; +@NgModule({ + declarations: [PwaInstallComponent], + imports: [ + CommonModule, + FontAwesomeModule, + MatButtonModule, + FlexLayoutModule, + TranslationModule, + MatSidenavModule, + MatToolbarModule, + MatSnackBarModule, + ], + providers: [PwaInstallService, { provide: WINDOW_TOKEN, useValue: window }], + exports: [PwaInstallComponent], +}) +export class PwaInstallModule { + constructor(pwaInstallService: PwaInstallService) { + pwaInstallService.registerPWAInstallListener(); + } +} diff --git a/src/app/pwa-install/pwa-install.service.spec.ts b/src/app/pwa-install/pwa-install.service.spec.ts new file mode 100644 index 0000000000..a349478a55 --- /dev/null +++ b/src/app/pwa-install/pwa-install.service.spec.ts @@ -0,0 +1,74 @@ +import { TestBed } from "@angular/core/testing"; +import { PwaInstallModule } from "./pwa-install.module"; + +import { PwaInstallService, PWAInstallType } from "./pwa-install.service"; +import { WINDOW_TOKEN } from "../utils/di-tokens"; + +describe("PwaInstallService", () => { + let service: PwaInstallService; + let mockWindow; + + beforeEach(() => { + mockWindow = { + navigator: { + userAgent: "mockAgent", + }, + addEventListener: () => {}, + innerWidth: 2000, + matchMedia: () => ({}), + }; + TestBed.configureTestingModule({ + imports: [PwaInstallModule], + providers: [{ provide: WINDOW_TOKEN, useValue: mockWindow }], + }); + service = TestBed.inject(PwaInstallService); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); + + it("should return install instructions for IOS devices on safari", () => { + mockWindow.navigator.userAgent = "iphone safari"; + mockWindow.innerWidth = 1000; + + const installType = service.getPWAInstallType(); + expect(installType).toBe(PWAInstallType.ShowiOSInstallInstructions); + }); + + it("should detect standalone mode", () => { + spyOn(mockWindow, "matchMedia").and.returnValue({ matches: true } as any); + expect(service.getPWAInstallType()).toBe(PWAInstallType.RunningAsPWA); + }); + + it("should return not available install type for other browsers/devices", () => { + mockWindow.navigator.userAgent = "firefox windows"; + expect(service.getPWAInstallType()).toBe(PWAInstallType.NotAvailable); + }); + + it("should execute install event when calling install", async () => { + const installSpy = jasmine.createSpy(); + spyOn(mockWindow, "addEventListener").and.callFake((_, callback) => + callback({ + prompt: installSpy, + preventDefault: () => {}, + userChoice: Promise.resolve({ outcome: "accepted" }), + }) + ); + + service.registerPWAInstallListener(); + expect(mockWindow.addEventListener).toHaveBeenCalledWith( + "beforeinstallprompt", + jasmine.anything() + ); + await expectAsync(service.canInstallDirectly).toBeResolved(); + + const installPromise = service.installPWA(); + expect(installSpy).toHaveBeenCalled(); + await expectAsync(installPromise).toBeResolvedTo({ outcome: "accepted" }); + }); + + it("should throw an error when trying to install without install prompt", () => { + expect(() => service.installPWA()).toThrowError(); + }); +}); diff --git a/src/app/pwa-install/pwa-install.service.ts b/src/app/pwa-install/pwa-install.service.ts new file mode 100644 index 0000000000..113e31e46d --- /dev/null +++ b/src/app/pwa-install/pwa-install.service.ts @@ -0,0 +1,123 @@ +import { Inject, Injectable } from "@angular/core"; +import { WINDOW_TOKEN } from "../utils/di-tokens"; + +export enum PWAInstallType { + ShowiOSInstallInstructions, + RunningAsPWA, + NotAvailable, +} +enum Browser { + Opera, + MicrosoftInternetExplorer, + Edge, + Safari, + Chrome, + Firefox, + Other, +} +enum OS { + iOS, + MacOS, + Android, + Linux, + Windows, + Other, +} + +@Injectable() +export class PwaInstallService { + /** + * Resolves once/if it is possible to directly install the app + */ + canInstallDirectly: Promise; + + private deferredInstallPrompt: any; + + constructor(@Inject(WINDOW_TOKEN) private window: Window) {} + + registerPWAInstallListener() { + this.canInstallDirectly = new Promise((resolve) => { + this.window.addEventListener("beforeinstallprompt", (e) => { + e.preventDefault(); + this.deferredInstallPrompt = e; + resolve(); + }); + }); + } + + installPWA(): Promise { + if (!this.deferredInstallPrompt) { + throw new Error( + "InstallPWA called, but PWA install prompt has not fired." + ); + } + this.deferredInstallPrompt.prompt(); + return this.deferredInstallPrompt.userChoice; + } + + getPWAInstallType(): PWAInstallType { + const os: OS = this.detectOS(); + const browser: Browser = this.detectBrowser(); + const standaloneMode: boolean = this.detectStandaloneMode(); + let pwaInstallType: PWAInstallType; + if (standaloneMode) { + pwaInstallType = PWAInstallType.RunningAsPWA; + } else if (os === OS.iOS && browser === Browser.Safari) { + pwaInstallType = PWAInstallType.ShowiOSInstallInstructions; + } else { + pwaInstallType = PWAInstallType.NotAvailable; + } + return pwaInstallType; + } + + private detectOS(): OS { + let os: OS; + const userAgent = this.window.navigator.userAgent; + if (/iphone|ipad|ipod|macintosh/i.test(userAgent)) { + if (this.window.innerWidth < 1025) { + os = OS.iOS; + } else { + os = OS.MacOS; + } + } else if (/android/i.test(userAgent)) { + os = OS.Android; + } else if (/windows|win32|win64|WinCE/i.test(userAgent)) { + os = OS.Windows; + } else if (/linux|X11/i.test(userAgent)) { + os = OS.Linux; + } + return os; + } + + private detectBrowser(): Browser { + let browser: Browser; + const userAgent = this.window.navigator.userAgent; + if (/opera/i.test(userAgent)) { + browser = Browser.Opera; + } else if (/msie|trident/i.test(userAgent)) { + browser = Browser.MicrosoftInternetExplorer; + } else if (/edg/i.test(userAgent)) { + browser = Browser.Edge; + } else if (/chrome/i.test(userAgent)) { + browser = Browser.Chrome; + } else if (/safari/i.test(userAgent)) { + browser = Browser.Safari; + if (/crios|fxios/i.test(userAgent)) { + browser = Browser.Chrome; + } + } else if (/firefox/i.test(userAgent)) { + browser = Browser.Firefox; + } else { + browser = Browser.Other; + } + return browser; + } + + private detectStandaloneMode(): boolean { + return ( + ("standalone" in this.window.navigator && + this.window.navigator["standalone"]) || + this.window.matchMedia("(display-mode: standalone)").matches + ); + } +} diff --git a/src/app/pwa-install/pwa-install.stories.ts b/src/app/pwa-install/pwa-install.stories.ts new file mode 100644 index 0000000000..7e82ab697e --- /dev/null +++ b/src/app/pwa-install/pwa-install.stories.ts @@ -0,0 +1,32 @@ +import { Story, Meta } from "@storybook/angular/types-6-0"; +import { moduleMetadata } from "@storybook/angular"; +import { PwaInstallModule } from "./pwa-install.module"; +import { StorybookBaseModule } from "app/utils/storybook-base.module"; +import { PwaInstallComponent } from "./pwa-install.component"; +import { MatSnackBarModule } from "@angular/material/snack-bar"; + +export default { + title: "Core/PwaInstall", + component: PwaInstallComponent, + decorators: [ + moduleMetadata({ + imports: [ + PwaInstallModule, + StorybookBaseModule, + MatSnackBarModule + ], + }), + ], +} as Meta; + +const Template: Story = ( + args: PwaInstallComponent +) => ({ + component: PwaInstallComponent, + props: args, +}); + +export const Primary = Template.bind({}); +Primary.args = { + installText2 : 'Install instructions', +}; diff --git a/src/app/utils/di-tokens.ts b/src/app/utils/di-tokens.ts new file mode 100644 index 0000000000..d22f11646b --- /dev/null +++ b/src/app/utils/di-tokens.ts @@ -0,0 +1,6 @@ +import { InjectionToken } from "@angular/core"; + +/** + * Use this instead of directly referencing the window object for better testability + */ +export const WINDOW_TOKEN = new InjectionToken("Window object"); diff --git a/src/locale/messages.de.xlf b/src/locale/messages.de.xlf index 231104beeb..3a4c6110ff 100644 --- a/src/locale/messages.de.xlf +++ b/src/locale/messages.de.xlf @@ -40,7 +40,7 @@ The month something took place src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 34 + 32 @@ -50,7 +50,7 @@ How many children are present at a meeting src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 40 + 38 @@ -4163,7 +4163,7 @@ Events of an attendance src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 49 + 47 src/app/core/config/config-fix.ts @@ -4176,7 +4176,7 @@ Percentage of people that attended an event src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 55 + 53 src/app/child-dev-project/attendance/attendance-details/attendance-details.component.ts @@ -5022,7 +5022,7 @@ Navigate to user profile page src/app/core/ui/ui/ui.component.html - 94 + 98 @@ -5031,7 +5031,7 @@ Sign out of the app src/app/core/ui/ui/ui.component.html - 99 + 103 @@ -5377,6 +5377,60 @@ Button to download data + + Install App + App installieren + PWA Install Button Label + + src/app/pwa-install/pwa-install.component.html + 10 + + + + To install the app + Um die App zu installieren, klicken Sie + + src/app/pwa-install/pwa-install.component.html + 15,16 + + PWA iOS Install Instractions - Line 1 + + + tap on + unten auf + + src/app/pwa-install/pwa-install.component.html + 27,28 + + PWA iOS Install Instriuctions - Line 2-1 + + + at the bottom + + + src/app/pwa-install/pwa-install.component.html + 32,33 + + PWA iOS Install Instructions - Line 2-2 + + + and then tap on + und dann auf + + src/app/pwa-install/pwa-install.component.html + 37,38 + + PWA iOS Install Instructions - Line 3-1 + + + (Add to Homescreen). + (Zum Home-Bildschirm). + + src/app/pwa-install/pwa-install.component.html + 41,42 + + PWA iOS Install Instructions - Line 3-2 + 0 in 0 von diff --git a/src/locale/messages.fr.xlf b/src/locale/messages.fr.xlf index 6cc42454ed..fb90f9e856 100644 --- a/src/locale/messages.fr.xlf +++ b/src/locale/messages.fr.xlf @@ -224,7 +224,7 @@ The month something took place src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 34 + 32 @@ -234,7 +234,7 @@ How many children are present at a meeting src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 40 + 38 @@ -243,7 +243,7 @@ Events of an attendance src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 49 + 47 src/app/core/config/config-fix.ts @@ -256,7 +256,7 @@ Percentage of people that attended an event src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 55 + 53 src/app/child-dev-project/attendance/attendance-details/attendance-details.component.ts @@ -4778,7 +4778,7 @@ Navigate to user profile page src/app/core/ui/ui/ui.component.html - 94 + 98 @@ -4787,7 +4787,7 @@ Sign out of the app src/app/core/ui/ui/ui.component.html - 99 + 103 @@ -5434,6 +5434,60 @@ Button to download data + + Install App + Installer l'application + PWA Install Button Label + + src/app/pwa-install/pwa-install.component.html + 10 + + + + To install the app + Pour installer l'application + + src/app/pwa-install/pwa-install.component.html + 15,16 + + PWA iOS Install Instractions - Line 1 + + + tap on + cliquez sur + + src/app/pwa-install/pwa-install.component.html + 27,28 + + PWA iOS Install Instriuctions - Line 2-1 + + + at the bottom + en bas + + src/app/pwa-install/pwa-install.component.html + 32,33 + + PWA iOS Install Instructions - Line 2-2 + + + and then tap on + et puis sur + + src/app/pwa-install/pwa-install.component.html + 37,38 + + PWA iOS Install Instructions - Line 3-1 + + + (Add to Homescreen). + (Ajouter à l'écran d'accueil). + + src/app/pwa-install/pwa-install.component.html + 41,42 + + PWA iOS Install Instructions - Line 3-2 + diff --git a/src/locale/messages.xlf b/src/locale/messages.xlf index d8a74d06c6..cae130db81 100644 --- a/src/locale/messages.xlf +++ b/src/locale/messages.xlf @@ -35,7 +35,7 @@ Month src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 34,32 + 32,30 The month something took place @@ -43,7 +43,7 @@ Present src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 40,38 + 38,36 Title of table column How many children are present at a meeting @@ -52,7 +52,7 @@ Events src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 49,47 + 47,45 src/app/core/config/config-fix.ts @@ -64,7 +64,7 @@ Attended src/app/child-dev-project/attendance/activity-attendance-section/activity-attendance-section.component.ts - 55,53 + 53,51 src/app/child-dev-project/attendance/attendance-details/attendance-details.component.ts @@ -4067,7 +4067,7 @@ Profile src/app/core/ui/ui/ui.component.html - 94,96 + 98,100 Navigate to user profile page @@ -4075,7 +4075,7 @@ Sign out src/app/core/ui/ui/ui.component.html - 99,101 + 103,105 Sign out of the app @@ -4808,6 +4808,54 @@ Button to download data + + Install App + + src/app/pwa-install/pwa-install.component.html + 10 + + PWA Install Button Label + + + To install the app + + src/app/pwa-install/pwa-install.component.html + 15,16 + + PWA iOS Install Instractions - Line 1 + + + tap on + + src/app/pwa-install/pwa-install.component.html + 27,28 + + PWA iOS Install Instriuctions - Line 2-1 + + + at the bottom + + src/app/pwa-install/pwa-install.component.html + 32,33 + + PWA iOS Install Instructions - Line 2-2 + + + and then tap on + + src/app/pwa-install/pwa-install.component.html + 37,38 + + PWA iOS Install Instructions - Line 3-1 + + + (Add to Homescreen). + + src/app/pwa-install/pwa-install.component.html + 41,42 + + PWA iOS Install Instructions - Line 3-2 + From 5a8b315da363d6942455f4990632c8c4fa57fef0 Mon Sep 17 00:00:00 2001 From: Simon <33730997+TheSlimvReal@users.noreply.github.com> Date: Fri, 8 Apr 2022 22:01:56 +0200 Subject: [PATCH 11/13] refactor: more flexible aser component fixes: #1171 --- .../aser/demo-aser-generator.service.ts | 3 +- .../children/aser/model/aser.spec.ts | 29 +- .../children/aser/model/aser.ts | 55 +- .../children/aser/model/mathLevels.ts | 30 - .../children/aser/model/readingLevels.ts | 30 - .../children/aser/model/skill-levels.ts | 57 ++ .../children-count-dashboard.component.html | 2 +- .../conflict-resolution-list.component.html | 12 +- src/app/core/admin/admin/admin.component.html | 32 +- src/app/core/admin/admin/admin.component.ts | 28 +- src/app/core/config/config-fix.ts | 6 +- src/locale/messages.de.xlf | 587 +++++------------- src/locale/messages.fr.xlf | 578 +++++------------ src/locale/messages.xlf | 582 ++++++----------- 14 files changed, 623 insertions(+), 1408 deletions(-) delete mode 100644 src/app/child-dev-project/children/aser/model/mathLevels.ts delete mode 100644 src/app/child-dev-project/children/aser/model/readingLevels.ts create mode 100644 src/app/child-dev-project/children/aser/model/skill-levels.ts diff --git a/src/app/child-dev-project/children/aser/demo-aser-generator.service.ts b/src/app/child-dev-project/children/aser/demo-aser-generator.service.ts index 8b1b3cc094..b1dbc02179 100644 --- a/src/app/child-dev-project/children/aser/demo-aser-generator.service.ts +++ b/src/app/child-dev-project/children/aser/demo-aser-generator.service.ts @@ -5,8 +5,7 @@ import { Child } from "../model/child"; import { faker } from "../../../core/demo-data/faker"; import { Aser } from "./model/aser"; import { ConfigurableEnumValue } from "../../../core/configurable-enum/configurable-enum.interface"; -import { mathLevels } from "./model/mathLevels"; -import { readingLevels } from "./model/readingLevels"; +import { mathLevels, readingLevels } from "./model/skill-levels"; import { WarningLevel } from "../../../core/entity/model/warning-level"; /** diff --git a/src/app/child-dev-project/children/aser/model/aser.spec.ts b/src/app/child-dev-project/children/aser/model/aser.spec.ts index 4ed2811dd0..23db0645f3 100644 --- a/src/app/child-dev-project/children/aser/model/aser.spec.ts +++ b/src/app/child-dev-project/children/aser/model/aser.spec.ts @@ -16,8 +16,7 @@ */ import { Aser } from "./aser"; -import { mathLevels } from "./mathLevels"; -import { readingLevels } from "./readingLevels"; +import { mathLevels, readingLevels } from "./skill-levels"; import { WarningLevel } from "../../../../core/entity/model/warning-level"; import { testEntitySubclass } from "../../../../core/entity/model/entity.spec"; @@ -41,7 +40,7 @@ describe("Aser", () => { expect(entity.getWarningLevel()).toBe(WarningLevel.OK); }); - it("warning level WARNING if some bad results", function () { + it("warning level WARNING if some results are not passed", function () { const id = "test1"; const entity = new Aser(id); entity.english = readingLevels[1]; @@ -50,19 +49,29 @@ describe("Aser", () => { expect(entity.getWarningLevel()).toBe(WarningLevel.WARNING); }); - it("has a warning level of OK if english is at it's highest level", () => { + it("has a warning level of OK if english is passed", () => { const entity = new Aser(); - entity.english = readingLevels[readingLevels.length - 1]; + entity.english = readingLevels.find((l) => l.passed); expect(entity.getWarningLevel()).toBe(WarningLevel.OK); }); - it("has a warning level of OK if all values are at it's highest level", () => { + it("has a warning level of OK if all skills are passed", () => { const entity = new Aser(); - entity.math = mathLevels[mathLevels.length - 1]; - entity.english = readingLevels[readingLevels.length - 1]; - entity.hindi = readingLevels[readingLevels.length - 1]; - entity.bengali = readingLevels[readingLevels.length - 1]; + entity.math = mathLevels.find((l) => l.passed); + entity.english = readingLevels.find((l) => l.passed); + entity.hindi = readingLevels.find((l) => l.passed); + entity.bengali = readingLevels.find((l) => l.passed); + + expect(entity.getWarningLevel()).toBe(WarningLevel.OK); + }); + + it("has warning level OK if some subjects are passed and others are empty", () => { + const entity = new Aser(); + entity.math = mathLevels.find((l) => l.passed); + entity.english = readingLevels.find((l) => l.passed); + entity.hindi = readingLevels.find((l) => l.id === ""); + entity.bengali = undefined; expect(entity.getWarningLevel()).toBe(WarningLevel.OK); }); diff --git a/src/app/child-dev-project/children/aser/model/aser.ts b/src/app/child-dev-project/children/aser/model/aser.ts index e4cd90a2f7..a5e54673c7 100644 --- a/src/app/child-dev-project/children/aser/model/aser.ts +++ b/src/app/child-dev-project/children/aser/model/aser.ts @@ -18,36 +18,12 @@ import { Entity } from "../../../../core/entity/model/entity"; import { DatabaseField } from "../../../../core/entity/database-field.decorator"; import { DatabaseEntity } from "../../../../core/entity/database-entity.decorator"; -import { - ConfigurableEnumConfig, - ConfigurableEnumValue, -} from "../../../../core/configurable-enum/configurable-enum.interface"; -import { MathLevel, mathLevels } from "./mathLevels"; -import { ReadingLevel, readingLevels } from "./readingLevels"; +import { SkillLevel } from "./skill-levels"; import { WarningLevel } from "../../../../core/entity/model/warning-level"; +import { ConfigurableEnumDatatype } from "../../../../core/configurable-enum/configurable-enum-datatype/configurable-enum-datatype"; @DatabaseEntity("Aser") export class Aser extends Entity { - static isReadingPassedOrNA(level: ConfigurableEnumValue): boolean { - return this.isHighestLevelOrNA(level, readingLevels); - } - - static isMathPassedOrNA(level: ConfigurableEnumValue): boolean { - return this.isHighestLevelOrNA(level, mathLevels); - } - - static isHighestLevelOrNA( - level: ConfigurableEnumValue, - source: ConfigurableEnumConfig - ): boolean { - if (!level || level.id === "") { - // not applicable - return true; - } - const index = source.findIndex((cEnumValue) => cEnumValue.id === level.id); - return index === source.length - 1; - } - @DatabaseField() child: string; // id of Child entity @DatabaseField({ label: $localize`:Label for date of the ASER results:Date`, @@ -58,40 +34,47 @@ export class Aser extends Entity { dataType: "configurable-enum", innerDataType: "reading-levels", }) - hindi: ReadingLevel; + hindi: SkillLevel; @DatabaseField({ label: $localize`:Label of the Bengali ASER result:Bengali`, dataType: "configurable-enum", innerDataType: "reading-levels", }) - bengali: ReadingLevel; + bengali: SkillLevel; @DatabaseField({ label: $localize`:Label of the English ASER result:English`, dataType: "configurable-enum", innerDataType: "reading-levels", }) - english: ReadingLevel; + english: SkillLevel; @DatabaseField({ label: $localize`:Label of the Math ASER result:Math`, dataType: "configurable-enum", innerDataType: "math-levels", }) - math: MathLevel; + math: SkillLevel; @DatabaseField({ label: $localize`:Label for the remarks of a ASER result:Remarks`, }) remarks: string = ""; getWarningLevel(): WarningLevel { - if ( - Aser.isReadingPassedOrNA(this.english) && - Aser.isReadingPassedOrNA(this.hindi) && - Aser.isReadingPassedOrNA(this.bengali) && - Aser.isMathPassedOrNA(this.math) - ) { + if (this.hasPassedEverything()) { return WarningLevel.OK; } else { return WarningLevel.WARNING; } } + + private hasPassedEverything(): boolean { + const configurableEnum = new ConfigurableEnumDatatype(undefined).name; + const schema = this.getSchema(); + return Object.keys(this) + .filter((key) => schema.get(key)?.dataType === configurableEnum) + .every((key) => this.isPassed(this[key])); + } + + private isPassed(value: SkillLevel): boolean { + return !value || value.id === "" || value.passed; + } } diff --git a/src/app/child-dev-project/children/aser/model/mathLevels.ts b/src/app/child-dev-project/children/aser/model/mathLevels.ts deleted file mode 100644 index 6471749eb2..0000000000 --- a/src/app/child-dev-project/children/aser/model/mathLevels.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - ConfigurableEnumConfig, - ConfigurableEnumValue, - EMPTY, -} from "../../../../core/configurable-enum/configurable-enum.interface"; - -export type MathLevel = ConfigurableEnumValue; -export const mathLevels: ConfigurableEnumConfig = [ - EMPTY, - { - id: "Nothing", - label: $localize`:Label math level:Nothing`, - }, - { - id: "Numbers 1-9", - label: $localize`:Label math level:Numbers 1-9`, - }, - { - id: "Numbers 10-99", - label: $localize`:Label math level:Numbers 10-99`, - }, - { - id: "Subtraction", - label: $localize`:Label math level:Subtraction`, - }, - { - id: "Division", - label: $localize`:Label math level:Division`, - }, -]; diff --git a/src/app/child-dev-project/children/aser/model/readingLevels.ts b/src/app/child-dev-project/children/aser/model/readingLevels.ts deleted file mode 100644 index 22a7c02896..0000000000 --- a/src/app/child-dev-project/children/aser/model/readingLevels.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - ConfigurableEnumConfig, - ConfigurableEnumValue, - EMPTY, -} from "../../../../core/configurable-enum/configurable-enum.interface"; - -export type ReadingLevel = ConfigurableEnumValue; -export const readingLevels: ConfigurableEnumConfig = [ - EMPTY, - { - id: "Nothing", - label: $localize`:Label reading level:Nothing`, - }, - { - id: "Read Letters", - label: $localize`:Label reading level:Read Letters`, - }, - { - id: "Read Words", - label: $localize`:Label reading level:Read Words`, - }, - { - id: "Read Sentence", - label: $localize`:Label reading level:Read Sentence`, - }, - { - id: "Read Paragraph", - label: $localize`:Label reading level:Read Paragraph`, - }, -]; diff --git a/src/app/child-dev-project/children/aser/model/skill-levels.ts b/src/app/child-dev-project/children/aser/model/skill-levels.ts new file mode 100644 index 0000000000..8a7c31ca25 --- /dev/null +++ b/src/app/child-dev-project/children/aser/model/skill-levels.ts @@ -0,0 +1,57 @@ +import { + ConfigurableEnumConfig, + ConfigurableEnumValue, + EMPTY, +} from "../../../../core/configurable-enum/configurable-enum.interface"; + +export type SkillLevel = ConfigurableEnumValue & { passed?: boolean }; + +export const readingLevels: ConfigurableEnumConfig = [ + EMPTY, + { + id: "Nothing", + label: $localize`:Label reading level:Nothing`, + }, + { + id: "Read Letters", + label: $localize`:Label reading level:Read Letters`, + }, + { + id: "Read Words", + label: $localize`:Label reading level:Read Words`, + }, + { + id: "Read Sentence", + label: $localize`:Label reading level:Read Sentence`, + }, + { + id: "Read Paragraph", + label: $localize`:Label reading level:Read Paragraph`, + passed: true, + }, +]; + +export const mathLevels: ConfigurableEnumConfig = [ + EMPTY, + { + id: "Nothing", + label: $localize`:Label math level:Nothing`, + }, + { + id: "Numbers 1-9", + label: $localize`:Label math level:Numbers 1-9`, + }, + { + id: "Numbers 10-99", + label: $localize`:Label math level:Numbers 10-99`, + }, + { + id: "Subtraction", + label: $localize`:Label math level:Subtraction`, + }, + { + id: "Division", + label: $localize`:Label math level:Division`, + passed: true, + }, +]; diff --git a/src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.html b/src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.html index 235a39db67..60cfe01c93 100644 --- a/src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.html +++ b/src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.html @@ -6,7 +6,7 @@ i18n-subtitle="Dashboard title naming total number of beneficiaries in the system" > - +
- conflicts to resolve: -

+

conflicts to resolve:

-
+
@@ -23,7 +16,6 @@ mat-header-cell *matHeaderCellDef mat-sort-header - i18n="Raw data, e.g. from a database" > Data diff --git a/src/app/core/admin/admin/admin.component.html b/src/app/core/admin/admin/admin.component.html index 70dff2f075..0d93f810cb 100644 --- a/src/app/core/admin/admin/admin.component.html +++ b/src/app/core/admin/admin/admin.component.html @@ -1,21 +1,17 @@ -

Administration & Configuration

+

Administration & Configuration

-

+

Warning: This section is intended for system administrators only. Make sure you know what you are doing.



-

Utility Functions

+

Utility Functions

@@ -23,12 +19,11 @@

Utility Functions



-

Backup

+

Backup

@@ -36,7 +31,6 @@

Backup

@@ -50,12 +44,11 @@

Backup



-

Export

+

Export

@@ -63,12 +56,11 @@

Export



-

Application Configuration

+

Application Configuration

@@ -76,7 +68,6 @@

Application Configuration

@@ -90,14 +81,14 @@

Application Configuration



-

AppConfig

+

AppConfig

{{ appConfig | json }}



-

Debug the PouchDB

+

Debug the PouchDB

-

@@ -105,7 +96,6 @@

Debug the PouchDB

@@ -113,8 +103,8 @@

Debug the PouchDB



-

Alert Log

-
_id {{ row.id }}
+

Alert Log

+
diff --git a/src/app/core/admin/admin/admin.component.ts b/src/app/core/admin/admin/admin.component.ts index b251d5532e..509c389c9a 100644 --- a/src/app/core/admin/admin/admin.component.ts +++ b/src/app/core/admin/admin/admin.component.ts @@ -111,8 +111,8 @@ export class AdminComponent implements OnInit { const newData = await readFile(this.getFileFromInputEvent(inputEvent)); const dialogRef = this.confirmationDialog.openDialog( - $localize`Overwrite complete database?`, - $localize`Are you sure you want to restore this backup? This will + `Overwrite complete database?`, + `Are you sure you want to restore this backup? This will delete all ${JSON.parse(restorePoint).length} existing records, restoring ${JSON.parse(newData).length} records from the loaded file.` ); @@ -125,13 +125,9 @@ export class AdminComponent implements OnInit { await this.backupService.clearDatabase(); await this.backupService.importJson(newData, true); - const snackBarRef = this.snackBar.open( - $localize`Backup restored`, - "Undo", - { - duration: 8000, - } - ); + const snackBarRef = this.snackBar.open(`Backup restored`, "Undo", { + duration: 8000, + }); snackBarRef .onAction() .pipe(untilDestroyed(this)) @@ -154,8 +150,8 @@ export class AdminComponent implements OnInit { const restorePoint = await this.backupService.getJsonExport(); const dialogRef = this.confirmationDialog.openDialog( - $localize`Empty complete database?`, - $localize`Are you sure you want to clear the database? This will delete all ${ + `Empty complete database?`, + `Are you sure you want to clear the database? This will delete all ${ restorePoint.split("\n").length } existing records in the database!` ); @@ -167,13 +163,9 @@ export class AdminComponent implements OnInit { await this.backupService.clearDatabase(); - const snackBarRef = this.snackBar.open( - $localize`Import completed`, - "Undo", - { - duration: 8000, - } - ); + const snackBarRef = this.snackBar.open(`Import completed`, "Undo", { + duration: 8000, + }); snackBarRef .onAction() .pipe(untilDestroyed(this)) diff --git a/src/app/core/config/config-fix.ts b/src/app/core/config/config-fix.ts index 3e80438e7e..8295402d8b 100644 --- a/src/app/core/config/config-fix.ts +++ b/src/app/core/config/config-fix.ts @@ -6,8 +6,10 @@ import { ChildSchoolRelation } from "../../child-dev-project/children/model/chil import { EventNote } from "../../child-dev-project/attendance/model/event-note"; import { genders } from "../../child-dev-project/children/model/genders"; import { materials } from "../../child-dev-project/children/educational-material/model/materials"; -import { mathLevels } from "../../child-dev-project/children/aser/model/mathLevels"; -import { readingLevels } from "../../child-dev-project/children/aser/model/readingLevels"; +import { + mathLevels, + readingLevels, +} from "../../child-dev-project/children/aser/model/skill-levels"; import { warningLevels } from "../../child-dev-project/warning-levels"; import { ratingAnswers } from "../../features/historical-data/model/rating-answers"; diff --git a/src/locale/messages.de.xlf b/src/locale/messages.de.xlf index 3a4c6110ff..6c8d84e189 100644 --- a/src/locale/messages.de.xlf +++ b/src/locale/messages.de.xlf @@ -1177,11 +1177,11 @@ src/app/core/config/config-fix.ts - 32 + 34 src/app/core/config/config-fix.ts - 504 + 506 @@ -1577,225 +1577,6 @@ 167 - - conflicts to resolve: - - Aufzulösende Datenbankkonflikte - - src/app/conflict-resolution/conflict-resolution-list/conflict-resolution-list.component.html - 7,8 - - Shows the database conflicts that need to be - resolved - Conflicts to be resolved - - - Table showing the conflicts - Table showing the conflicts - - src/app/conflict-resolution/conflict-resolution-list/conflict-resolution-list.component.html - 15,17 - - A table showing conflicts between sets of data - - - Data - Datum - Raw data, e.g. from a database - - src/app/conflict-resolution/conflict-resolution-list/conflict-resolution-list.component.html - 28 - - - - Administration & Configuration - Administration & Konfiguration - - src/app/core/admin/admin/admin.component.html - 1,3 - - Admin Header - - - Warning: This section is intended for system administrators only. Make sure you know what you are doing. - - Achtung: Dieser Bereich ist nur für System-Administratoren bestimmt. Sie solltest wissen, was Sie tun! - - Admin security paragraph - - src/app/core/admin/admin/admin.component.html - 7 - - - - Utility Functions - Nützliche Funktionen - Utility Functions header - - src/app/core/admin/admin/admin.component.html - 13 - - - - Auto-Update children's photo filenames - Automatisches Updaten der Namen der Foto-Dateien - Auto-Update button - - src/app/core/admin/admin/admin.component.html - 20 - - - - Backup - Backup - Backup header - - src/app/core/admin/admin/admin.component.html - 26 - - - - Download Backup (.json) - Download Backup (.json) - Download-Update button - - src/app/core/admin/admin/admin.component.html - 33 - - - - Restore Backup (.json) - Backup wiederherstellen (.json) - Restore-Update button - - src/app/core/admin/admin/admin.component.html - 41 - - - - Export - Export - Export header - - src/app/core/admin/admin/admin.component.html - 53 - - - - Download whole database (.csv) - Download der ganzen Datenbank (.csv) - Download database button - - src/app/core/admin/admin/admin.component.html - 60 - - - - Application Configuration - Programm-Konfiguration - Application configuration header - - src/app/core/admin/admin/admin.component.html - 66 - - - - Download configuration - Download der Konfiguration - Download configuration button - - src/app/core/admin/admin/admin.component.html - 73 - - - - Upload new configuration - Upload einer neuen Konfiguration - Upload configuration button - - src/app/core/admin/admin/admin.component.html - 81 - - - - AppConfig - App-Konfiguration - App-Config header - - src/app/core/admin/admin/admin.component.html - 93 - - - - Debug the PouchDB - Debugging der PouchDB - Debug PouchDB header - - src/app/core/admin/admin/admin.component.html - 98 - - - - Send to console.log() - An console.log() senden - Log button - - src/app/core/admin/admin/admin.component.html - 101 - - - - Empty Database - Die Datenbank leeren - Empty database button - - src/app/core/admin/admin/admin.component.html - 110 - - - - Alert Log - Hinweis-Log - Alert log header - - src/app/core/admin/admin/admin.component.html - 116 - - - - Table showing alerts - Table showing alerts - - src/app/core/admin/admin/admin.component.html - 117,119 - - - - Overwrite complete database? - Komplette Datenbank überschreiben? - - src/app/core/admin/admin/admin.component.ts - 114 - - - - Are you sure you want to restore this backup? This will - delete all existing records, - restoring records from the loaded file. - Sind Sie sicher, dass Sie dieses Backup übernehmen wollen? Dies wird existierende Einträge löschen und durch Einträge aus dem Backup ersetzen. - - src/app/core/admin/admin/admin.component.ts - 115 - - - - Backup restored - Backup wiederhergestellt - - src/app/core/admin/admin/admin.component.ts - 129 - - Import new data? Neue Daten importieren? @@ -2106,33 +1887,9 @@ 20 - - Empty complete database? - Komplette Datenbank löschen? - - src/app/core/admin/admin/admin.component.ts - 157 - - - - Are you sure you want to clear the database? This will delete all existing records in the database! - Soll wirklich die komplette Datenbank gelöscht werden? Dadurch werden alle Aufzeichnungen in der Datenbank gelöscht! - - src/app/core/admin/admin/admin.component.ts - 158 - - - - Import completed - Import fertig - - src/app/core/admin/admin/admin.component.ts - 171 - - Table showing a list of all users - Table showing a list of all users + Eine Tabelle die eine Liste aller Nutzer:innen zeigt src/app/core/admin/user-list/user-list.component.html 1,3 @@ -2209,7 +1966,7 @@ Menu item src/app/core/config/config-fix.ts - 27 + 29 @@ -2218,11 +1975,11 @@ Menu item src/app/core/config/config-fix.ts - 37 + 39 src/app/core/config/config-fix.ts - 329 + 331 @@ -2231,7 +1988,7 @@ Title of recurring activities overview src/app/core/config/config-fix.ts - 679 + 681 @@ -2240,17 +1997,17 @@ Menu item src/app/core/config/config-fix.ts - 52 + 54 Import Importieren + Menu item src/app/core/config/config-fix.ts - 57 + 59 - Menu item Users @@ -2258,7 +2015,7 @@ Menu item src/app/core/config/config-fix.ts - 62 + 64 @@ -2267,7 +2024,7 @@ Menu item src/app/core/config/config-fix.ts - 67 + 69 @@ -2276,7 +2033,7 @@ Menu item src/app/core/config/config-fix.ts - 72 + 74 @@ -2285,7 +2042,7 @@ Menu item src/app/core/config/config-fix.ts - 77 + 79 @@ -2294,7 +2051,7 @@ Document status src/app/core/config/config-fix.ts - 99 + 101 @@ -2303,7 +2060,7 @@ Document status src/app/core/config/config-fix.ts - 103 + 105 @@ -2312,7 +2069,7 @@ Document status src/app/core/config/config-fix.ts - 107 + 109 @@ -2321,7 +2078,7 @@ Document status src/app/core/config/config-fix.ts - 111 + 113 @@ -2330,7 +2087,7 @@ Document status src/app/core/config/config-fix.ts - 115 + 117 @@ -2339,7 +2096,7 @@ Document status src/app/core/config/config-fix.ts - 119 + 121 @@ -2349,7 +2106,7 @@ Dashboard shortcut widget src/app/core/config/config-fix.ts - 147 + 149 @@ -2359,7 +2116,7 @@ Dashboard shortcut widget src/app/core/config/config-fix.ts - 152 + 154 @@ -2376,7 +2133,7 @@ src/app/core/config/config-fix.ts - 125 + 127 @@ -2389,7 +2146,7 @@ src/app/core/config/config-fix.ts - 129 + 131 @@ -2402,7 +2159,7 @@ src/app/core/config/config-fix.ts - 133 + 135 @@ -2486,7 +2243,7 @@ Attendance week dashboard widget label src/app/core/config/config-fix.ts - 182 + 184 @@ -2495,7 +2252,7 @@ Attendance week dashboard widget label src/app/core/config/config-fix.ts - 189 + 191 @@ -2504,11 +2261,11 @@ Title for notes overview src/app/core/config/config-fix.ts - 207 + 209 src/app/core/config/config-fix.ts - 603 + 605 @@ -2517,11 +2274,11 @@ Translated name of default column group src/app/core/config/config-fix.ts - 217 + 219 src/app/core/config/config-fix.ts - 221 + 223 @@ -2530,19 +2287,19 @@ Translated name of mobile column group src/app/core/config/config-fix.ts - 218 + 220 src/app/core/config/config-fix.ts - 231 + 233 src/app/core/config/config-fix.ts - 437 + 439 src/app/core/config/config-fix.ts - 490 + 492 @@ -2551,7 +2308,7 @@ Filename of markdown help page (make sure the filename you enter as a translation actually exists on the server!) src/app/core/config/config-fix.ts - 308 + 310 @@ -2560,7 +2317,7 @@ Title of schools overview src/app/core/config/config-fix.ts - 320 + 322 @@ -2569,7 +2326,7 @@ Label for private schools filter - true case src/app/core/config/config-fix.ts - 330 + 332 @@ -2578,7 +2335,7 @@ Label for private schools filter - false case src/app/core/config/config-fix.ts - 331 + 333 @@ -2587,15 +2344,15 @@ Panel title src/app/core/config/config-fix.ts - 343 + 345 src/app/core/config/config-fix.ts - 529 + 531 src/app/core/config/config-fix.ts - 698 + 700 @@ -2604,7 +2361,7 @@ Panel title src/app/core/config/config-fix.ts - 371 + 373 @@ -2613,7 +2370,7 @@ Title children overview src/app/core/config/config-fix.ts - 386 + 388 @@ -2622,7 +2379,7 @@ Column label for school attendance of child src/app/core/config/config-fix.ts - 412 + 414 @@ -2631,7 +2388,7 @@ Column label for coaching attendance of child src/app/core/config/config-fix.ts - 421 + 423 @@ -2640,11 +2397,11 @@ Translated name of default column group src/app/core/config/config-fix.ts - 436 + 438 src/app/core/config/config-fix.ts - 453 + 455 @@ -2653,7 +2410,7 @@ Column group name src/app/core/config/config-fix.ts - 440 + 442 @@ -2662,11 +2419,11 @@ Column group name src/app/core/config/config-fix.ts - 476 + 478 src/app/core/config/config-fix.ts - 612 + 614 @@ -2675,7 +2432,7 @@ Active children filter label - true case src/app/core/config/config-fix.ts - 505 + 507 @@ -2684,7 +2441,7 @@ Active children filter label - false case src/app/core/config/config-fix.ts - 506 + 508 @@ -2693,7 +2450,7 @@ Header for form section src/app/core/config/config-fix.ts - 556 + 558 @@ -2702,7 +2459,7 @@ Header for form section src/app/core/config/config-fix.ts - 557 + 559 @@ -2711,7 +2468,7 @@ Header for form section src/app/core/config/config-fix.ts - 558 + 560 @@ -2720,7 +2477,7 @@ Panel title src/app/core/config/config-fix.ts - 565 + 567 @@ -2729,7 +2486,7 @@ Title inside a panel src/app/core/config/config-fix.ts - 568 + 570 @@ -2738,7 +2495,7 @@ Title inside a panel src/app/core/config/config-fix.ts - 588 + 590 @@ -2747,11 +2504,11 @@ Menu item src/app/core/config/config-fix.ts - 42 + 44 src/app/core/config/config-fix.ts - 594 + 596 @@ -2760,7 +2517,7 @@ Title inside a panel src/app/core/config/config-fix.ts - 625 + 627 @@ -2769,7 +2526,7 @@ Panel title src/app/core/config/config-fix.ts - 631 + 633 @@ -2778,7 +2535,7 @@ Panel title src/app/core/config/config-fix.ts - 640 + 642 @@ -2795,7 +2552,7 @@ src/app/core/config/config-fix.ts - 657 + 659 @@ -2804,7 +2561,7 @@ Panel title src/app/core/config/config-fix.ts - 727 + 729 @@ -2813,7 +2570,7 @@ Name of a report src/app/core/config/config-fix.ts - 743 + 745 @@ -2822,7 +2579,7 @@ Label of report query src/app/core/config/config-fix.ts - 747 + 749 @@ -2831,7 +2588,7 @@ Label of report query src/app/core/config/config-fix.ts - 750 + 752 @@ -2840,7 +2597,7 @@ Label of report query src/app/core/config/config-fix.ts - 754 + 756 @@ -2849,7 +2606,7 @@ Label for report query src/app/core/config/config-fix.ts - 761 + 763 @@ -2858,7 +2615,7 @@ Label for report query src/app/core/config/config-fix.ts - 764 + 766 @@ -2867,7 +2624,7 @@ Label for report query src/app/core/config/config-fix.ts - 768 + 770 @@ -2876,7 +2633,7 @@ Label for report query src/app/core/config/config-fix.ts - 773 + 775 @@ -2885,7 +2642,7 @@ Label for report query src/app/core/config/config-fix.ts - 776 + 778 @@ -2894,7 +2651,7 @@ Label for report query src/app/core/config/config-fix.ts - 780 + 782 @@ -2903,7 +2660,7 @@ Label for report query src/app/core/config/config-fix.ts - 786 + 788 @@ -2912,7 +2669,7 @@ Label for report query src/app/core/config/config-fix.ts - 791 + 793 @@ -2921,7 +2678,7 @@ Label for report query src/app/core/config/config-fix.ts - 794 + 796 @@ -2930,7 +2687,7 @@ Label for report query src/app/core/config/config-fix.ts - 798 + 800 @@ -2939,7 +2696,7 @@ Name of a report src/app/core/config/config-fix.ts - 808 + 810 @@ -2948,7 +2705,7 @@ Name of a report src/app/core/config/config-fix.ts - 825 + 827 @@ -2957,11 +2714,11 @@ Name of a column of a report src/app/core/config/config-fix.ts - 840 + 842 src/app/core/config/config-fix.ts - 865 + 867 @@ -2970,11 +2727,11 @@ Name of a column of a report src/app/core/config/config-fix.ts - 844 + 846 src/app/core/config/config-fix.ts - 869 + 871 @@ -2983,11 +2740,11 @@ Name of a column of a report src/app/core/config/config-fix.ts - 848 + 850 src/app/core/config/config-fix.ts - 873 + 875 @@ -2996,7 +2753,7 @@ Label for a child attribute src/app/core/config/config-fix.ts - 899 + 901 @@ -3005,7 +2762,7 @@ Label for a child attribute src/app/core/config/config-fix.ts - 920 + 922 @@ -3014,7 +2771,7 @@ Label for the language of a school src/app/core/config/config-fix.ts - 947 + 949 @@ -3027,7 +2784,7 @@ src/app/core/config/config-fix.ts - 961 + 963 @@ -3036,7 +2793,7 @@ Label for a child attribute src/app/core/config/config-fix.ts - 987 + 989 @@ -3045,7 +2802,7 @@ Description for a child attribute src/app/core/config/config-fix.ts - 988 + 990 @@ -3054,7 +2811,7 @@ Label for a child attribute src/app/core/config/config-fix.ts - 996 + 998 @@ -3063,7 +2820,7 @@ Description for a child attribute src/app/core/config/config-fix.ts - 997 + 999 @@ -3072,7 +2829,7 @@ Label for a child attribute src/app/core/config/config-fix.ts - 1005 + 1007 @@ -3081,7 +2838,7 @@ Description for a child attribute src/app/core/config/config-fix.ts - 1006 + 1008 @@ -3090,7 +2847,7 @@ Label for a child attribute src/app/core/config/config-fix.ts - 1014 + 1016 @@ -3099,7 +2856,7 @@ Description for a child attribute src/app/core/config/config-fix.ts - 1015 + 1017 @@ -3108,7 +2865,7 @@ Label for a child attribute src/app/core/config/config-fix.ts - 1023 + 1025 @@ -3117,7 +2874,7 @@ Description for a child attribute src/app/core/config/config-fix.ts - 1024 + 1026 @@ -3225,7 +2982,7 @@ src/app/core/config/config-fix.ts - 47 + 49 @@ -3252,7 +3009,7 @@ Label for date of the ASER results src/app/child-dev-project/children/aser/model/aser.ts - 53 + 29 src/app/child-dev-project/children/educational-material/model/educational-material.ts @@ -3277,7 +3034,7 @@ Label of the Math ASER result src/app/child-dev-project/children/aser/model/aser.ts - 75 + 51 @@ -3286,7 +3043,7 @@ Label of the English ASER result src/app/child-dev-project/children/aser/model/aser.ts - 69 + 45 src/app/child-dev-project/schools/demo-school-generator.service.ts @@ -3299,7 +3056,7 @@ Label of the Hindi ASER result src/app/child-dev-project/children/aser/model/aser.ts - 57 + 33 src/app/child-dev-project/schools/demo-school-generator.service.ts @@ -3312,7 +3069,7 @@ Label of the Bengali ASER result src/app/child-dev-project/children/aser/model/aser.ts - 63 + 39 src/app/child-dev-project/schools/demo-school-generator.service.ts @@ -3333,19 +3090,19 @@ src/app/core/config/config-fix.ts - 390 + 392 src/app/core/config/config-fix.ts - 836 + 838 src/app/core/config/config-fix.ts - 861 + 863 src/app/core/config/config-fix.ts - 933 + 935 @@ -3374,11 +3131,11 @@ src/app/core/config/config-fix.ts - 332 + 334 src/app/core/config/config-fix.ts - 507 + 509 src/app/core/entity-components/entity-list/filter-generator.service.ts @@ -3399,24 +3156,24 @@ Label for the remarks of a ASER result src/app/child-dev-project/children/aser/model/aser.ts - 81 + 57 src/app/core/config/config-fix.ts - 975 + 977 Nothing Nichts - Label math level + Label reading level - src/app/child-dev-project/children/aser/model/mathLevels.ts - 12 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 13 - src/app/child-dev-project/children/aser/model/readingLevels.ts - 12 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 38 @@ -3424,8 +3181,8 @@ Zahlen 1-9 Label math level - src/app/child-dev-project/children/aser/model/mathLevels.ts - 16 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 42 @@ -3433,8 +3190,8 @@ Zahlen 10-99 Label math level - src/app/child-dev-project/children/aser/model/mathLevels.ts - 20 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 46 @@ -3442,8 +3199,8 @@ Subtraktion Label math level - src/app/child-dev-project/children/aser/model/mathLevels.ts - 24 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 50 @@ -3451,8 +3208,8 @@ Division Label math level - src/app/child-dev-project/children/aser/model/mathLevels.ts - 28 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 54 @@ -3460,8 +3217,8 @@ Kann Buchstaben lsen Label reading level - src/app/child-dev-project/children/aser/model/readingLevels.ts - 16 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 17 @@ -3469,8 +3226,8 @@ Kann Wörter lesen Label reading level - src/app/child-dev-project/children/aser/model/readingLevels.ts - 20 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 21 @@ -3478,8 +3235,8 @@ Kann Sätze lesen Label reading level - src/app/child-dev-project/children/aser/model/readingLevels.ts - 24 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 25 @@ -3487,13 +3244,13 @@ Kann Absätze lesen Label reading level - src/app/child-dev-project/children/aser/model/readingLevels.ts - 28 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 29 Picture of a child - Picture of a child + Das Bild eines Kindes src/app/child-dev-project/children/child-block/child-block.component.html 9 @@ -3537,7 +3294,7 @@ Label for the mother tongue of a child src/app/core/config/config-fix.ts - 913 + 915 @@ -3555,7 +3312,7 @@ Label for the religion of a child src/app/core/config/config-fix.ts - 906 + 908 @@ -3564,7 +3321,7 @@ Column label for age of child src/app/core/config/config-fix.ts - 395 + 397 @@ -3631,11 +3388,11 @@ Label for the address of a child src/app/core/config/config-fix.ts - 892 + 894 src/app/core/config/config-fix.ts - 954 + 956 @@ -3644,7 +3401,7 @@ Label for if a school is a private school src/app/core/config/config-fix.ts - 940 + 942 @@ -3653,7 +3410,7 @@ Label for the timing of a school src/app/core/config/config-fix.ts - 968 + 970 @@ -3728,7 +3485,7 @@ src/app/core/config/config-fix.ts - 400 + 402 @@ -3873,11 +3630,11 @@ src/app/core/config/config-fix.ts - 405 + 407 src/app/core/config/config-fix.ts - 516 + 518 @@ -3953,11 +3710,11 @@ src/app/core/config/config-fix.ts - 832 + 834 src/app/core/config/config-fix.ts - 857 + 859 @@ -3970,11 +3727,11 @@ src/app/core/config/config-fix.ts - 713 + 715 src/app/core/config/config-fix.ts - 818 + 820 @@ -4016,7 +3773,7 @@ Table showing the most critical BMI results - Table showing the most critical BMI results + Eine Tabelle welche Kinder mit kritischen BMI Werte auflistet src/app/child-dev-project/children/children-bmi-dashboard/children-bmi-dashboard.component.html 12 @@ -4032,30 +3789,12 @@ Dashboard title naming total number of beneficiaries in the system - - - + + Table showing disaggregation of the beneficiaries + Tabelle welche eine Aufteilung der Fälle anzeigt src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.html - 10,25 + 9 Label for children count dashboard @@ -4087,7 +3826,7 @@ src/app/core/config/config-fix.ts - 466 + 468 @@ -4145,7 +3884,7 @@ src/app/core/config/config-fix.ts - 430 + 432 @@ -4167,7 +3906,7 @@ src/app/core/config/config-fix.ts - 813 + 815 @@ -4293,7 +4032,7 @@ Table showing organization progress - Table showing organization progress + Eine Tabelle welche den Fortschritt der Organisation anzeigt src/app/features/progress-dashboard-widget/progress-dashboard/progress-dashboard.component.html 8,10 @@ -5291,7 +5030,7 @@ Table showing the report results - Table showing the report results + Eine Tabelle welche die Ergebnisse des Reports anzeigt src/app/features/reporting/reporting/object-table/object-table.component.html 5 @@ -5406,7 +5145,7 @@ at the bottom - + src/app/pwa-install/pwa-install.component.html 32,33 diff --git a/src/locale/messages.fr.xlf b/src/locale/messages.fr.xlf index fb90f9e856..24e1e1d620 100644 --- a/src/locale/messages.fr.xlf +++ b/src/locale/messages.fr.xlf @@ -8,7 +8,7 @@ Label for date of the ASER results src/app/child-dev-project/children/aser/model/aser.ts - 53 + 29 src/app/child-dev-project/children/educational-material/model/educational-material.ts @@ -33,7 +33,7 @@ Label of the Hindi ASER result src/app/child-dev-project/children/aser/model/aser.ts - 57 + 33 src/app/child-dev-project/schools/demo-school-generator.service.ts @@ -46,7 +46,7 @@ Label of the Bengali ASER result src/app/child-dev-project/children/aser/model/aser.ts - 63 + 39 src/app/child-dev-project/schools/demo-school-generator.service.ts @@ -59,7 +59,7 @@ Label of the English ASER result src/app/child-dev-project/children/aser/model/aser.ts - 69 + 45 src/app/child-dev-project/schools/demo-school-generator.service.ts @@ -72,7 +72,7 @@ Label of the Math ASER result src/app/child-dev-project/children/aser/model/aser.ts - 75 + 51 @@ -81,24 +81,24 @@ Label for the remarks of a ASER result src/app/child-dev-project/children/aser/model/aser.ts - 81 + 57 src/app/core/config/config-fix.ts - 975 + 977 Nothing Aucune compétence - Label math level + Label reading level - src/app/child-dev-project/children/aser/model/mathLevels.ts - 12 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 13 - src/app/child-dev-project/children/aser/model/readingLevels.ts - 12 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 38 @@ -106,8 +106,8 @@ Chiffres de 1 à 9 Label math level - src/app/child-dev-project/children/aser/model/mathLevels.ts - 16 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 42 @@ -115,8 +115,8 @@ Chiffres de 10 à 99 Label math level - src/app/child-dev-project/children/aser/model/mathLevels.ts - 20 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 46 @@ -124,8 +124,8 @@ Soustraction Label math level - src/app/child-dev-project/children/aser/model/mathLevels.ts - 24 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 50 @@ -133,8 +133,8 @@ Division Label math level - src/app/child-dev-project/children/aser/model/mathLevels.ts - 28 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 54 @@ -142,8 +142,8 @@ Lecture de lettres uniques Label reading level - src/app/child-dev-project/children/aser/model/readingLevels.ts - 16 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 17 @@ -151,8 +151,8 @@ Lecture de mots Label reading level - src/app/child-dev-project/children/aser/model/readingLevels.ts - 20 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 21 @@ -160,8 +160,8 @@ Lecture de phrases Label reading level - src/app/child-dev-project/children/aser/model/readingLevels.ts - 24 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 25 @@ -169,8 +169,8 @@ Lecture de paragraphes Label reading level - src/app/child-dev-project/children/aser/model/readingLevels.ts - 28 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 29 @@ -247,7 +247,7 @@ src/app/core/config/config-fix.ts - 813 + 815 @@ -746,11 +746,11 @@ src/app/core/config/config-fix.ts - 405 + 407 src/app/core/config/config-fix.ts - 516 + 518 @@ -826,11 +826,11 @@ src/app/core/config/config-fix.ts - 832 + 834 src/app/core/config/config-fix.ts - 857 + 859 @@ -843,11 +843,11 @@ src/app/core/config/config-fix.ts - 713 + 715 src/app/core/config/config-fix.ts - 818 + 820 @@ -905,30 +905,12 @@ Dashboard title naming total number of beneficiaries in the system - - - + + Table showing disaggregation of the beneficiaries + Table showing disaggregation of the beneficiaries src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.html - 10,25 + 9 Label for children count dashboard @@ -949,11 +931,11 @@ src/app/core/config/config-fix.ts - 332 + 334 src/app/core/config/config-fix.ts - 507 + 509 src/app/core/entity-components/entity-list/filter-generator.service.ts @@ -982,7 +964,7 @@ src/app/core/config/config-fix.ts - 657 + 659 @@ -999,7 +981,7 @@ src/app/core/config/config-fix.ts - 125 + 127 @@ -1012,7 +994,7 @@ src/app/core/config/config-fix.ts - 129 + 131 @@ -1025,7 +1007,7 @@ src/app/core/config/config-fix.ts - 133 + 135 @@ -1117,19 +1099,19 @@ src/app/core/config/config-fix.ts - 390 + 392 src/app/core/config/config-fix.ts - 836 + 838 src/app/core/config/config-fix.ts - 861 + 863 src/app/core/config/config-fix.ts - 933 + 935 @@ -1205,7 +1187,7 @@ src/app/core/config/config-fix.ts - 466 + 468 @@ -1254,7 +1236,7 @@ src/app/core/config/config-fix.ts - 961 + 963 @@ -1276,7 +1258,7 @@ src/app/core/config/config-fix.ts - 400 + 402 @@ -1562,7 +1544,7 @@ src/app/core/config/config-fix.ts - 430 + 432 @@ -2081,11 +2063,11 @@ src/app/core/config/config-fix.ts - 32 + 34 src/app/core/config/config-fix.ts - 504 + 506 @@ -2107,7 +2089,7 @@ src/app/core/config/config-fix.ts - 47 + 49 @@ -2768,252 +2750,6 @@ 167 - - conflicts to resolve: - - conflits à résoudre: - - - src/app/conflict-resolution/conflict-resolution-list/conflict-resolution-list.component.html - 7,8 - - Shows the database conflicts that need to be - resolved - Conflicts to be resolved - - - Table showing the conflicts - Table showing the conflicts - - src/app/conflict-resolution/conflict-resolution-list/conflict-resolution-list.component.html - 15,17 - - A table showing conflicts between sets of data - - - Data - Données - - src/app/conflict-resolution/conflict-resolution-list/conflict-resolution-list.component.html - 28,32 - - Raw data, e.g. from a database - - - Administration & Configuration - Administration & Configuration - - src/app/core/admin/admin/admin.component.html - 1,5 - - Admin Header - - - Warning: This section is intended for system administrators only. Make sure you know what you are doing. - - Attention: cette section est destinée aux administrateurs systèmes uniquement. Assurez-vous de savoir ce que vous faites. - - - src/app/core/admin/admin/admin.component.html - 7,9 - - Admin security paragraph - - - Utility Functions - Fonctions utilitaires - - src/app/core/admin/admin/admin.component.html - 13,18 - - Utility Functions header - - - Auto-Update children's photo filenames - Mise à jour automatique du nom de fichier de l'image des enfants - - src/app/core/admin/admin/admin.component.html - 20,23 - - Auto-Update button - - - Backup - Backup - Backup header - - src/app/core/admin/admin/admin.component.html - 26 - - - - Download Backup (.json) - Télécharger le backup (.json) - Download-Update button - - src/app/core/admin/admin/admin.component.html - 33 - - - - Restore Backup (.json) - Restauration du backup (.json) - Restore-Update button - - src/app/core/admin/admin/admin.component.html - 41 - - - - Export - Exporter - Export header - - src/app/core/admin/admin/admin.component.html - 53 - - - - Download whole database (.csv) - Télécharger la base de données entière (.csv) - Download database button - - src/app/core/admin/admin/admin.component.html - 60 - - - - Application Configuration - Configuration de l'application - Application configuration header - - src/app/core/admin/admin/admin.component.html - 66 - - - - Download configuration - Télécharger la configuration - Download configuration button - - src/app/core/admin/admin/admin.component.html - 73 - - - - Upload new configuration - Téléverser la nouvelle configuration - Upload configuration button - - src/app/core/admin/admin/admin.component.html - 81 - - - - AppConfig - AppConfig - App-Config header - - src/app/core/admin/admin/admin.component.html - 93 - - - - Debug the PouchDB - Débeug du PouchDB - Debug PouchDB header - - src/app/core/admin/admin/admin.component.html - 98 - - - - Send to console.log() - Envoyer à console.log() - Log button - - src/app/core/admin/admin/admin.component.html - 101 - - - - Empty Database - Effacer la base de données - Empty database button - - src/app/core/admin/admin/admin.component.html - 110 - - - - Alert Log - Alert Log - Alert log header - - src/app/core/admin/admin/admin.component.html - 116 - - - - Table showing alerts - Table showing alerts - - src/app/core/admin/admin/admin.component.html - 117,119 - - - - Overwrite complete database? - Écraser la base de donnée entière? - - src/app/core/admin/admin/admin.component.ts - 114 - - - - Are you sure you want to restore this backup? This will - delete all existing records, - restoring records from the loaded file. - Êtes-vous sûr de vouloir restaurer ce backup? Ceci va - supprimer l'entièreté des enregistrements existants, - restaurant enregistrements du fichier téléchargé. - - src/app/core/admin/admin/admin.component.ts - 115 - - - - Backup restored - Backup restauré - - src/app/core/admin/admin/admin.component.ts - 129 - - - - Empty complete database? - Vider l'entièreté de la base de donnée? - - src/app/core/admin/admin/admin.component.ts - 157 - - - - Are you sure you want to clear the database? This will delete all existing records in the database! - Êtes-vous sûr de vouloir effacer la base de données? Ceci va supprimer les enregistrements existants dans la base de données! - - src/app/core/admin/admin/admin.component.ts - 158 - - - - Import completed - Importation terminée - - src/app/core/admin/admin/admin.component.ts - 171 - - Table showing a list of all users Table showing a list of all users @@ -3118,7 +2854,7 @@ Menu item src/app/core/config/config-fix.ts - 27 + 29 @@ -3127,11 +2863,11 @@ Menu item src/app/core/config/config-fix.ts - 37 + 39 src/app/core/config/config-fix.ts - 329 + 331 @@ -3140,7 +2876,7 @@ Title of recurring activities overview src/app/core/config/config-fix.ts - 679 + 681 @@ -3149,17 +2885,17 @@ Menu item src/app/core/config/config-fix.ts - 52 + 54 Import Import + Menu item src/app/core/config/config-fix.ts - 57 + 59 - Menu item Users @@ -3167,7 +2903,7 @@ Menu item src/app/core/config/config-fix.ts - 62 + 64 @@ -3176,7 +2912,7 @@ Menu item src/app/core/config/config-fix.ts - 67 + 69 @@ -3185,7 +2921,7 @@ Menu item src/app/core/config/config-fix.ts - 72 + 74 @@ -3194,7 +2930,7 @@ Menu item src/app/core/config/config-fix.ts - 77 + 79 @@ -3203,7 +2939,7 @@ Document status src/app/core/config/config-fix.ts - 99 + 101 @@ -3212,7 +2948,7 @@ Document status src/app/core/config/config-fix.ts - 103 + 105 @@ -3221,7 +2957,7 @@ Document status src/app/core/config/config-fix.ts - 107 + 109 @@ -3230,7 +2966,7 @@ Document status src/app/core/config/config-fix.ts - 111 + 113 @@ -3239,7 +2975,7 @@ Document status src/app/core/config/config-fix.ts - 115 + 117 @@ -3248,7 +2984,7 @@ Document status src/app/core/config/config-fix.ts - 119 + 121 @@ -3258,7 +2994,7 @@ Dashboard shortcut widget src/app/core/config/config-fix.ts - 147 + 149 @@ -3268,7 +3004,7 @@ Dashboard shortcut widget src/app/core/config/config-fix.ts - 152 + 154 @@ -3277,7 +3013,7 @@ Attendance week dashboard widget label src/app/core/config/config-fix.ts - 182 + 184 @@ -3286,7 +3022,7 @@ Attendance week dashboard widget label src/app/core/config/config-fix.ts - 189 + 191 @@ -3295,11 +3031,11 @@ Title for notes overview src/app/core/config/config-fix.ts - 207 + 209 src/app/core/config/config-fix.ts - 603 + 605 @@ -3308,11 +3044,11 @@ Translated name of default column group src/app/core/config/config-fix.ts - 217 + 219 src/app/core/config/config-fix.ts - 221 + 223 @@ -3321,19 +3057,19 @@ Translated name of mobile column group src/app/core/config/config-fix.ts - 218 + 220 src/app/core/config/config-fix.ts - 231 + 233 src/app/core/config/config-fix.ts - 437 + 439 src/app/core/config/config-fix.ts - 490 + 492 @@ -3342,7 +3078,7 @@ Filename of markdown help page (make sure the filename you enter as a translation actually exists on the server!) src/app/core/config/config-fix.ts - 308 + 310 @@ -3351,7 +3087,7 @@ Title of schools overview src/app/core/config/config-fix.ts - 320 + 322 @@ -3360,7 +3096,7 @@ Label for private schools filter - true case src/app/core/config/config-fix.ts - 330 + 332 @@ -3369,7 +3105,7 @@ Label for private schools filter - false case src/app/core/config/config-fix.ts - 331 + 333 @@ -3378,7 +3114,7 @@ Label for if a school is a private school src/app/core/config/config-fix.ts - 940 + 942 @@ -3387,15 +3123,15 @@ Panel title src/app/core/config/config-fix.ts - 343 + 345 src/app/core/config/config-fix.ts - 529 + 531 src/app/core/config/config-fix.ts - 698 + 700 @@ -3404,7 +3140,7 @@ Panel title src/app/core/config/config-fix.ts - 371 + 373 @@ -3413,7 +3149,7 @@ Title children overview src/app/core/config/config-fix.ts - 386 + 388 @@ -3422,7 +3158,7 @@ Column label for age of child src/app/core/config/config-fix.ts - 395 + 397 @@ -3431,7 +3167,7 @@ Column label for school attendance of child src/app/core/config/config-fix.ts - 412 + 414 @@ -3440,7 +3176,7 @@ Column label for coaching attendance of child src/app/core/config/config-fix.ts - 421 + 423 @@ -3449,11 +3185,11 @@ Translated name of default column group src/app/core/config/config-fix.ts - 436 + 438 src/app/core/config/config-fix.ts - 453 + 455 @@ -3462,7 +3198,7 @@ Column group name src/app/core/config/config-fix.ts - 440 + 442 @@ -3471,11 +3207,11 @@ Column group name src/app/core/config/config-fix.ts - 476 + 478 src/app/core/config/config-fix.ts - 612 + 614 @@ -3484,7 +3220,7 @@ Active children filter label - true case src/app/core/config/config-fix.ts - 505 + 507 @@ -3493,7 +3229,7 @@ Active children filter label - false case src/app/core/config/config-fix.ts - 506 + 508 @@ -3502,7 +3238,7 @@ Header for form section src/app/core/config/config-fix.ts - 556 + 558 @@ -3511,7 +3247,7 @@ Header for form section src/app/core/config/config-fix.ts - 557 + 559 @@ -3520,7 +3256,7 @@ Header for form section src/app/core/config/config-fix.ts - 558 + 560 @@ -3529,7 +3265,7 @@ Panel title src/app/core/config/config-fix.ts - 565 + 567 @@ -3538,7 +3274,7 @@ Title inside a panel src/app/core/config/config-fix.ts - 568 + 570 @@ -3547,7 +3283,7 @@ Title inside a panel src/app/core/config/config-fix.ts - 588 + 590 @@ -3556,11 +3292,11 @@ Menu item src/app/core/config/config-fix.ts - 42 + 44 src/app/core/config/config-fix.ts - 594 + 596 @@ -3569,7 +3305,7 @@ Title inside a panel src/app/core/config/config-fix.ts - 625 + 627 @@ -3578,7 +3314,7 @@ Panel title src/app/core/config/config-fix.ts - 631 + 633 @@ -3587,7 +3323,7 @@ Panel title src/app/core/config/config-fix.ts - 640 + 642 @@ -3596,7 +3332,7 @@ Panel title src/app/core/config/config-fix.ts - 727 + 729 @@ -3605,7 +3341,7 @@ Name of a report src/app/core/config/config-fix.ts - 743 + 745 @@ -3614,7 +3350,7 @@ Label of report query src/app/core/config/config-fix.ts - 747 + 749 @@ -3623,7 +3359,7 @@ Label of report query src/app/core/config/config-fix.ts - 750 + 752 @@ -3632,7 +3368,7 @@ Label of report query src/app/core/config/config-fix.ts - 754 + 756 @@ -3641,7 +3377,7 @@ Label for report query src/app/core/config/config-fix.ts - 761 + 763 @@ -3650,7 +3386,7 @@ Label for report query src/app/core/config/config-fix.ts - 764 + 766 @@ -3659,7 +3395,7 @@ Label for report query src/app/core/config/config-fix.ts - 768 + 770 @@ -3668,7 +3404,7 @@ Label for report query src/app/core/config/config-fix.ts - 773 + 775 @@ -3677,7 +3413,7 @@ Label for report query src/app/core/config/config-fix.ts - 776 + 778 @@ -3686,7 +3422,7 @@ Label for report query src/app/core/config/config-fix.ts - 780 + 782 @@ -3695,7 +3431,7 @@ Label for report query src/app/core/config/config-fix.ts - 786 + 788 @@ -3704,7 +3440,7 @@ Label for report query src/app/core/config/config-fix.ts - 791 + 793 @@ -3713,7 +3449,7 @@ Label for report query src/app/core/config/config-fix.ts - 794 + 796 @@ -3722,7 +3458,7 @@ Label for report query src/app/core/config/config-fix.ts - 798 + 800 @@ -3731,7 +3467,7 @@ Name of a report src/app/core/config/config-fix.ts - 808 + 810 @@ -3740,7 +3476,7 @@ Name of a report src/app/core/config/config-fix.ts - 825 + 827 @@ -3749,11 +3485,11 @@ Name of a column of a report src/app/core/config/config-fix.ts - 840 + 842 src/app/core/config/config-fix.ts - 865 + 867 @@ -3762,11 +3498,11 @@ Name of a column of a report src/app/core/config/config-fix.ts - 844 + 846 src/app/core/config/config-fix.ts - 869 + 871 @@ -3775,11 +3511,11 @@ Name of a column of a report src/app/core/config/config-fix.ts - 848 + 850 src/app/core/config/config-fix.ts - 873 + 875 @@ -3788,11 +3524,11 @@ Label for the address of a child src/app/core/config/config-fix.ts - 892 + 894 src/app/core/config/config-fix.ts - 954 + 956 @@ -3801,7 +3537,7 @@ Label for a child attribute src/app/core/config/config-fix.ts - 899 + 901 @@ -3810,7 +3546,7 @@ Label for the religion of a child src/app/core/config/config-fix.ts - 906 + 908 @@ -3819,7 +3555,7 @@ Label for the mother tongue of a child src/app/core/config/config-fix.ts - 913 + 915 @@ -3828,7 +3564,7 @@ Label for a child attribute src/app/core/config/config-fix.ts - 920 + 922 @@ -3837,7 +3573,7 @@ Label for the language of a school src/app/core/config/config-fix.ts - 947 + 949 @@ -3846,7 +3582,7 @@ Label for the timing of a school src/app/core/config/config-fix.ts - 968 + 970 @@ -3855,7 +3591,7 @@ Label for a child attribute src/app/core/config/config-fix.ts - 987 + 989 @@ -3864,7 +3600,7 @@ Description for a child attribute src/app/core/config/config-fix.ts - 988 + 990 @@ -3873,7 +3609,7 @@ Label for a child attribute src/app/core/config/config-fix.ts - 996 + 998 @@ -3882,7 +3618,7 @@ Description for a child attribute src/app/core/config/config-fix.ts - 997 + 999 @@ -3891,7 +3627,7 @@ Label for a child attribute src/app/core/config/config-fix.ts - 1005 + 1007 @@ -3900,7 +3636,7 @@ Description for a child attribute src/app/core/config/config-fix.ts - 1006 + 1008 @@ -3909,7 +3645,7 @@ Label for a child attribute src/app/core/config/config-fix.ts - 1014 + 1016 @@ -3918,7 +3654,7 @@ Description for a child attribute src/app/core/config/config-fix.ts - 1015 + 1017 @@ -3927,7 +3663,7 @@ Label for a child attribute src/app/core/config/config-fix.ts - 1023 + 1025 @@ -3936,7 +3672,7 @@ Description for a child attribute src/app/core/config/config-fix.ts - 1024 + 1026 diff --git a/src/locale/messages.xlf b/src/locale/messages.xlf index cae130db81..863537aa4f 100644 --- a/src/locale/messages.xlf +++ b/src/locale/messages.xlf @@ -56,7 +56,7 @@ src/app/core/config/config-fix.ts - 813 + 815 Events of an attendance @@ -550,11 +550,11 @@ src/app/core/config/config-fix.ts - 832 + 834 src/app/core/config/config-fix.ts - 857 + 859 Label for the interaction type of a recurring activity @@ -566,11 +566,11 @@ src/app/core/config/config-fix.ts - 713 + 715 src/app/core/config/config-fix.ts - 818 + 820 Label for the participants of a recurring activity @@ -594,7 +594,7 @@ Date src/app/child-dev-project/children/aser/model/aser.ts - 53 + 29 src/app/child-dev-project/children/educational-material/model/educational-material.ts @@ -618,7 +618,7 @@ Hindi src/app/child-dev-project/children/aser/model/aser.ts - 57 + 33 src/app/child-dev-project/schools/demo-school-generator.service.ts @@ -630,7 +630,7 @@ Bengali src/app/child-dev-project/children/aser/model/aser.ts - 63 + 39 src/app/child-dev-project/schools/demo-school-generator.service.ts @@ -642,7 +642,7 @@ English src/app/child-dev-project/children/aser/model/aser.ts - 69 + 45 src/app/child-dev-project/schools/demo-school-generator.service.ts @@ -654,7 +654,7 @@ Math src/app/child-dev-project/children/aser/model/aser.ts - 75 + 51 Label of the Math ASER result @@ -662,90 +662,90 @@ Remarks src/app/child-dev-project/children/aser/model/aser.ts - 81 + 57 src/app/core/config/config-fix.ts - 975 + 977 Label for the remarks of a ASER result Nothing - src/app/child-dev-project/children/aser/model/mathLevels.ts - 12 - - - src/app/child-dev-project/children/aser/model/readingLevels.ts - 12 - - Label math level - - - Numbers 1-9 - - src/app/child-dev-project/children/aser/model/mathLevels.ts - 16 - - Label math level - - - Numbers 10-99 - - src/app/child-dev-project/children/aser/model/mathLevels.ts - 20 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 13 - Label math level - - - Subtraction - src/app/child-dev-project/children/aser/model/mathLevels.ts - 24 - - Label math level - - - Division - - src/app/child-dev-project/children/aser/model/mathLevels.ts - 28 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 38 - Label math level + Label reading level Read Letters - src/app/child-dev-project/children/aser/model/readingLevels.ts - 16 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 17 Label reading level Read Words - src/app/child-dev-project/children/aser/model/readingLevels.ts - 20 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 21 Label reading level Read Sentence - src/app/child-dev-project/children/aser/model/readingLevels.ts - 24 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 25 Label reading level Read Paragraph - src/app/child-dev-project/children/aser/model/readingLevels.ts - 28 + src/app/child-dev-project/children/aser/model/skill-levels.ts + 29 Label reading level + + Numbers 1-9 + + src/app/child-dev-project/children/aser/model/skill-levels.ts + 42 + + Label math level + + + Numbers 10-99 + + src/app/child-dev-project/children/aser/model/skill-levels.ts + 46 + + Label math level + + + Subtraction + + src/app/child-dev-project/children/aser/model/skill-levels.ts + 50 + + Label math level + + + Division + + src/app/child-dev-project/children/aser/model/skill-levels.ts + 54 + + Label math level + Picture of a child @@ -791,20 +791,11 @@ Dashboard title naming total number of beneficiaries in the system - - + + Table showing disaggregation of the beneficiaries src/app/child-dev-project/children/children-count-dashboard/children-count-dashboard.component.html - 10,25 + 9 Label for children count dashboard @@ -824,11 +815,11 @@ src/app/core/config/config-fix.ts - 332 + 334 src/app/core/config/config-fix.ts - 507 + 509 src/app/core/entity-components/entity-list/filter-generator.service.ts @@ -855,7 +846,7 @@ src/app/core/config/config-fix.ts - 657 + 659 Child status @@ -871,7 +862,7 @@ src/app/core/config/config-fix.ts - 125 + 127 center @@ -883,7 +874,7 @@ src/app/core/config/config-fix.ts - 129 + 131 center @@ -895,7 +886,7 @@ src/app/core/config/config-fix.ts - 133 + 135 center @@ -1137,7 +1128,7 @@ src/app/core/config/config-fix.ts - 430 + 432 Table header, Short for Body Mass Index @@ -1177,19 +1168,19 @@ src/app/core/config/config-fix.ts - 390 + 392 src/app/core/config/config-fix.ts - 836 + 838 src/app/core/config/config-fix.ts - 861 + 863 src/app/core/config/config-fix.ts - 933 + 935 Label for the name of a child @@ -1257,7 +1248,7 @@ src/app/core/config/config-fix.ts - 466 + 468 Label for the status of a child @@ -1301,7 +1292,7 @@ src/app/core/config/config-fix.ts - 961 + 963 Label for the phone number of a child @@ -1325,11 +1316,11 @@ src/app/core/config/config-fix.ts - 405 + 407 src/app/core/config/config-fix.ts - 516 + 518 Label for the school of a relation @@ -1341,7 +1332,7 @@ src/app/core/config/config-fix.ts - 400 + 402 Label for the class of a relation @@ -1852,11 +1843,11 @@ src/app/core/config/config-fix.ts - 32 + 34 src/app/core/config/config-fix.ts - 504 + 506 Label for the children of a note @@ -1876,7 +1867,7 @@ src/app/core/config/config-fix.ts - 47 + 49 Label for the actual notes of a note @@ -2254,221 +2245,6 @@ 167 - - conflicts to resolve: - - - src/app/conflict-resolution/conflict-resolution-list/conflict-resolution-list.component.html - 7,8 - - Shows the database conflicts that need to be - resolved - Conflicts to be resolved - - - Table showing the conflicts - - src/app/conflict-resolution/conflict-resolution-list/conflict-resolution-list.component.html - 15,17 - - A table showing conflicts between sets of data - - - Data - - src/app/conflict-resolution/conflict-resolution-list/conflict-resolution-list.component.html - 28,32 - - Raw data, e.g. from a database - - - Administration & Configuration - - src/app/core/admin/admin/admin.component.html - 1,5 - - Admin Header - - - Warning: This section is intended for system administrators only. Make sure you know what you are doing. - - - src/app/core/admin/admin/admin.component.html - 7,9 - - Admin security paragraph - - - Utility Functions - - src/app/core/admin/admin/admin.component.html - 13,17 - - Utility Functions header - - - Auto-Update children's photo filenames - - src/app/core/admin/admin/admin.component.html - 20,28 - - Auto-Update button - - - Backup - - src/app/core/admin/admin/admin.component.html - 26,31 - - Backup header - - - Download Backup (.json) - - src/app/core/admin/admin/admin.component.html - 33,38 - - Download-Update button - - - Restore Backup (.json) - - src/app/core/admin/admin/admin.component.html - 41,47 - - Restore-Update button - - - Export - - src/app/core/admin/admin/admin.component.html - 53,58 - - Export header - - - Download whole database (.csv) - - src/app/core/admin/admin/admin.component.html - 60,66 - - Download database button - - - Application Configuration - - src/app/core/admin/admin/admin.component.html - 66,71 - - Application configuration header - - - Download configuration - - src/app/core/admin/admin/admin.component.html - 73,79 - - Download configuration button - - - Upload new configuration - - src/app/core/admin/admin/admin.component.html - 81,87 - - Upload configuration button - - - AppConfig - - src/app/core/admin/admin/admin.component.html - 93,98 - - App-Config header - - - Debug the PouchDB - - src/app/core/admin/admin/admin.component.html - 98,100 - - Debug PouchDB header - - - Send to console.log() - - src/app/core/admin/admin/admin.component.html - 101,107 - - Log button - - - Empty Database - - src/app/core/admin/admin/admin.component.html - 110,117 - - Empty database button - - - Alert Log - - src/app/core/admin/admin/admin.component.html - 116,118 - - Alert log header - - - Table showing alerts - - src/app/core/admin/admin/admin.component.html - 117,119 - - - - Overwrite complete database? - - src/app/core/admin/admin/admin.component.ts - 114,113 - - - - Are you sure you want to restore this backup? This will - delete all existing records, - restoring records from the loaded file. - - src/app/core/admin/admin/admin.component.ts - 115,113 - - - - Backup restored - - src/app/core/admin/admin/admin.component.ts - 129,128 - - - - Empty complete database? - - src/app/core/admin/admin/admin.component.ts - 157,156 - - - - Are you sure you want to clear the database? This will delete all existing records in the database! - - src/app/core/admin/admin/admin.component.ts - 158,156 - - - - Import completed - - src/app/core/admin/admin/admin.component.ts - 171,170 - - Table showing a list of all users @@ -2557,7 +2333,7 @@ Dashboard src/app/core/config/config-fix.ts - 27 + 29 Menu item @@ -2565,11 +2341,11 @@ Schools src/app/core/config/config-fix.ts - 37 + 39 src/app/core/config/config-fix.ts - 329 + 331 Menu item @@ -2577,11 +2353,11 @@ Attendance src/app/core/config/config-fix.ts - 42 + 44 src/app/core/config/config-fix.ts - 594 + 596 Menu item @@ -2589,7 +2365,7 @@ Admin src/app/core/config/config-fix.ts - 52 + 54 Menu item @@ -2597,7 +2373,7 @@ Import src/app/core/config/config-fix.ts - 57 + 59 Menu item @@ -2605,7 +2381,7 @@ Users src/app/core/config/config-fix.ts - 62 + 64 Menu item @@ -2613,7 +2389,7 @@ Reports src/app/core/config/config-fix.ts - 67 + 69 Menu item @@ -2621,7 +2397,7 @@ Database Conflicts src/app/core/config/config-fix.ts - 72 + 74 Menu item @@ -2629,7 +2405,7 @@ Help src/app/core/config/config-fix.ts - 77 + 79 Menu item @@ -2637,7 +2413,7 @@ OK (copy with us) src/app/core/config/config-fix.ts - 99 + 101 Document status @@ -2645,7 +2421,7 @@ OK (copy needed for us) src/app/core/config/config-fix.ts - 103 + 105 Document status @@ -2653,7 +2429,7 @@ needs correction src/app/core/config/config-fix.ts - 107 + 109 Document status @@ -2661,7 +2437,7 @@ applied src/app/core/config/config-fix.ts - 111 + 113 Document status @@ -2669,7 +2445,7 @@ doesn't have src/app/core/config/config-fix.ts - 115 + 117 Document status @@ -2677,7 +2453,7 @@ not eligible src/app/core/config/config-fix.ts - 119 + 121 Document status @@ -2685,7 +2461,7 @@ Record Attendance src/app/core/config/config-fix.ts - 147 + 149 record attendance shortcutDashboard shortcut widget @@ -2694,7 +2470,7 @@ Add Child src/app/core/config/config-fix.ts - 152 + 154 record attendance shortcutDashboard shortcut widget @@ -2703,7 +2479,7 @@ last week src/app/core/config/config-fix.ts - 182 + 184 Attendance week dashboard widget label @@ -2711,7 +2487,7 @@ this week src/app/core/config/config-fix.ts - 189 + 191 Attendance week dashboard widget label @@ -2719,11 +2495,11 @@ Notes & Reports src/app/core/config/config-fix.ts - 207 + 209 src/app/core/config/config-fix.ts - 603 + 605 Title for notes overview @@ -2731,11 +2507,11 @@ Standard src/app/core/config/config-fix.ts - 217 + 219 src/app/core/config/config-fix.ts - 221 + 223 Translated name of default column group @@ -2743,19 +2519,19 @@ Mobile src/app/core/config/config-fix.ts - 218 + 220 src/app/core/config/config-fix.ts - 231 + 233 src/app/core/config/config-fix.ts - 437 + 439 src/app/core/config/config-fix.ts - 490 + 492 Translated name of mobile column group @@ -2763,7 +2539,7 @@ assets/help/help.en.md src/app/core/config/config-fix.ts - 308 + 310 Filename of markdown help page (make sure the filename you enter as a translation actually exists on the server!) @@ -2771,7 +2547,7 @@ Schools List src/app/core/config/config-fix.ts - 320 + 322 Title of schools overview @@ -2779,7 +2555,7 @@ Private src/app/core/config/config-fix.ts - 330 + 332 Label for private schools filter - true case @@ -2787,7 +2563,7 @@ Government src/app/core/config/config-fix.ts - 331 + 333 Label for private schools filter - false case @@ -2795,15 +2571,15 @@ Basic Information src/app/core/config/config-fix.ts - 343 + 345 src/app/core/config/config-fix.ts - 529 + 531 src/app/core/config/config-fix.ts - 698 + 700 Panel title @@ -2811,7 +2587,7 @@ Students src/app/core/config/config-fix.ts - 371 + 373 Panel title @@ -2819,7 +2595,7 @@ Children List src/app/core/config/config-fix.ts - 386 + 388 Title children overview @@ -2827,7 +2603,7 @@ Age src/app/core/config/config-fix.ts - 395 + 397 Column label for age of child @@ -2835,7 +2611,7 @@ Attendance (School) src/app/core/config/config-fix.ts - 412 + 414 Column label for school attendance of child @@ -2843,7 +2619,7 @@ Attendance (Coaching) src/app/core/config/config-fix.ts - 421 + 423 Column label for coaching attendance of child @@ -2851,11 +2627,11 @@ School Info src/app/core/config/config-fix.ts - 436 + 438 src/app/core/config/config-fix.ts - 453 + 455 Translated name of default column group @@ -2863,7 +2639,7 @@ Basic Info src/app/core/config/config-fix.ts - 440 + 442 Column group name @@ -2871,11 +2647,11 @@ Health src/app/core/config/config-fix.ts - 476 + 478 src/app/core/config/config-fix.ts - 612 + 614 Column group name @@ -2883,7 +2659,7 @@ Active src/app/core/config/config-fix.ts - 505 + 507 Active children filter label - true case @@ -2891,7 +2667,7 @@ Inactive src/app/core/config/config-fix.ts - 506 + 508 Active children filter label - false case @@ -2899,7 +2675,7 @@ Personal Information src/app/core/config/config-fix.ts - 556 + 558 Header for form section @@ -2907,7 +2683,7 @@ Additional src/app/core/config/config-fix.ts - 557 + 559 Header for form section @@ -2915,7 +2691,7 @@ Scholar activities src/app/core/config/config-fix.ts - 558 + 560 Header for form section @@ -2923,7 +2699,7 @@ Education src/app/core/config/config-fix.ts - 565 + 567 Panel title @@ -2931,7 +2707,7 @@ School History src/app/core/config/config-fix.ts - 568 + 570 Title inside a panel @@ -2939,7 +2715,7 @@ ASER Results src/app/core/config/config-fix.ts - 588 + 590 Title inside a panel @@ -2947,7 +2723,7 @@ Height & Weight Tracking src/app/core/config/config-fix.ts - 625 + 627 Title inside a panel @@ -2955,7 +2731,7 @@ Educational Materials src/app/core/config/config-fix.ts - 631 + 633 Panel title @@ -2963,7 +2739,7 @@ Observations src/app/core/config/config-fix.ts - 640 + 642 Panel title @@ -2971,7 +2747,7 @@ Recurring Activities src/app/core/config/config-fix.ts - 679 + 681 Title of recurring activities overview @@ -2979,7 +2755,7 @@ Events & Attendance src/app/core/config/config-fix.ts - 727 + 729 Panel title @@ -2987,7 +2763,7 @@ Basic Report src/app/core/config/config-fix.ts - 743 + 745 Name of a report @@ -2995,7 +2771,7 @@ All children src/app/core/config/config-fix.ts - 747 + 749 Label of report query @@ -3003,7 +2779,7 @@ Male children src/app/core/config/config-fix.ts - 750 + 752 Label of report query @@ -3011,7 +2787,7 @@ Female children src/app/core/config/config-fix.ts - 754 + 756 Label of report query @@ -3019,7 +2795,7 @@ All schools src/app/core/config/config-fix.ts - 761 + 763 Label for report query @@ -3027,7 +2803,7 @@ Children attending a school src/app/core/config/config-fix.ts - 764 + 766 Label for report query @@ -3035,7 +2811,7 @@ Governmental schools src/app/core/config/config-fix.ts - 768 + 770 Label for report query @@ -3043,7 +2819,7 @@ Children attending a governmental school src/app/core/config/config-fix.ts - 773 + 775 Label for report query @@ -3051,7 +2827,7 @@ Male children attending a governmental school src/app/core/config/config-fix.ts - 776 + 778 Label for report query @@ -3059,7 +2835,7 @@ Female children attending a governmental school src/app/core/config/config-fix.ts - 780 + 782 Label for report query @@ -3067,7 +2843,7 @@ Private schools src/app/core/config/config-fix.ts - 786 + 788 Label for report query @@ -3075,7 +2851,7 @@ Children attending a private school src/app/core/config/config-fix.ts - 791 + 793 Label for report query @@ -3083,7 +2859,7 @@ Male children attending a private school src/app/core/config/config-fix.ts - 794 + 796 Label for report query @@ -3091,7 +2867,7 @@ Female children attending a private school src/app/core/config/config-fix.ts - 798 + 800 Label for report query @@ -3099,7 +2875,7 @@ Event Report src/app/core/config/config-fix.ts - 808 + 810 Name of a report @@ -3107,7 +2883,7 @@ Attendance Report src/app/core/config/config-fix.ts - 825 + 827 Name of a report @@ -3115,11 +2891,11 @@ Total src/app/core/config/config-fix.ts - 840 + 842 src/app/core/config/config-fix.ts - 865 + 867 Name of a column of a report @@ -3127,11 +2903,11 @@ Present src/app/core/config/config-fix.ts - 844 + 846 src/app/core/config/config-fix.ts - 869 + 871 Name of a column of a report @@ -3139,11 +2915,11 @@ Rate src/app/core/config/config-fix.ts - 848 + 850 src/app/core/config/config-fix.ts - 873 + 875 Name of a column of a report @@ -3151,11 +2927,11 @@ Address src/app/core/config/config-fix.ts - 892 + 894 src/app/core/config/config-fix.ts - 954 + 956 Label for the address of a child @@ -3163,7 +2939,7 @@ Blood Group src/app/core/config/config-fix.ts - 899 + 901 Label for a child attribute @@ -3171,7 +2947,7 @@ Religion src/app/core/config/config-fix.ts - 906 + 908 Label for the religion of a child @@ -3179,7 +2955,7 @@ Mother Tongue src/app/core/config/config-fix.ts - 913 + 915 Label for the mother tongue of a child @@ -3187,7 +2963,7 @@ Last Dental Check-Up src/app/core/config/config-fix.ts - 920 + 922 Label for a child attribute @@ -3195,7 +2971,7 @@ Private School src/app/core/config/config-fix.ts - 940 + 942 Label for if a school is a private school @@ -3203,7 +2979,7 @@ Language src/app/core/config/config-fix.ts - 947 + 949 Label for the language of a school @@ -3211,7 +2987,7 @@ School Timing src/app/core/config/config-fix.ts - 968 + 970 Label for the timing of a school @@ -3219,7 +2995,7 @@ Motivated src/app/core/config/config-fix.ts - 987 + 989 Label for a child attribute @@ -3227,7 +3003,7 @@ The child is motivated during the class. src/app/core/config/config-fix.ts - 988 + 990 Description for a child attribute @@ -3235,7 +3011,7 @@ Participating src/app/core/config/config-fix.ts - 996 + 998 Label for a child attribute @@ -3243,7 +3019,7 @@ The child is actively participating in the class. src/app/core/config/config-fix.ts - 997 + 999 Description for a child attribute @@ -3251,7 +3027,7 @@ Interacting src/app/core/config/config-fix.ts - 1005 + 1007 Label for a child attribute @@ -3259,7 +3035,7 @@ The child interacts with other students during the class. src/app/core/config/config-fix.ts - 1006 + 1008 Description for a child attribute @@ -3267,7 +3043,7 @@ Homework src/app/core/config/config-fix.ts - 1014 + 1016 Label for a child attribute @@ -3275,7 +3051,7 @@ The child does its homework. src/app/core/config/config-fix.ts - 1015 + 1017 Description for a child attribute @@ -3283,7 +3059,7 @@ Asking Questions src/app/core/config/config-fix.ts - 1023 + 1025 Label for a child attribute @@ -3291,7 +3067,7 @@ The child is asking questions during the class. src/app/core/config/config-fix.ts - 1024 + 1026 Description for a child attribute From fbfc016b1ce2e8ee5e1fa9bd957a6a936f08ff20 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 8 Apr 2022 22:18:25 +0200 Subject: [PATCH 12/13] feat(*): event/note details can now be exported individually (#1186) closes #1160 Co-authored-by: Simon --- .../note-details/note-details.component.html | 35 +++++++++++++++++-- .../note-details/note-details.component.scss | 6 ++++ .../note-details/note-details.component.ts | 16 ++++++--- .../child-dev-project/notes/notes.module.ts | 2 ++ .../export-service/export.service.spec.ts | 33 +++-------------- .../export/export-service/export.service.ts | 5 +-- src/locale/messages.de.xlf | 31 ++++++++++------ src/locale/messages.fr.xlf | 31 ++++++++++------ src/locale/messages.xlf | 30 ++++++++++------ 9 files changed, 119 insertions(+), 70 deletions(-) diff --git a/src/app/child-dev-project/notes/note-details/note-details.component.html b/src/app/child-dev-project/notes/note-details/note-details.component.html index 98b376fe44..01b822eb4b 100644 --- a/src/app/child-dev-project/notes/note-details/note-details.component.html +++ b/src/app/child-dev-project/notes/note-details/note-details.component.html @@ -22,6 +22,37 @@

{{ entity.date | date }}: {{ entity.subject }}

+
+ + + + +
+
@@ -125,7 +156,7 @@

{{ entity.date | date }}: {{ entity.subject }}

[(selection)]="entity.children" (selectionChange)="entityForm.form.markAsDirty()" [additionalFilter]="filterInactiveChildren" - [showEntities]="!isMeeting" + [showEntities]="!entity.category?.isMeeting" label="Participants" i18n-label="Participants of a note" placeholder="Add participant ..." @@ -143,7 +174,7 @@

{{ entity.date | date }}: {{ entity.subject }}

-
+
{ readonly INTERACTION_TYPE_CONFIG = INTERACTION_TYPE_CONFIG_ID; includeInactiveChildren: boolean = false; + /** export format for notes to be used for downloading the individual details */ + exportConfig: ExportColumnConfig[]; + + constructor(private configService: ConfigService) { + this.exportConfig = this.configService.getConfig<{ + config: EntityListConfig; + }>("view:note").config.exportConfig; + } + toggleIncludeInactiveChildren() { this.includeInactiveChildren = !this.includeInactiveChildren; // This needs to be set so that the filtering will start immediately @@ -35,8 +47,4 @@ export class NoteDetailsComponent implements ShowsEntity { } filterInactiveChildren: (Child) => boolean = (c: Child) => c.isActive; - - get isMeeting(): boolean { - return this.entity.category?.isMeeting || false; - } } diff --git a/src/app/child-dev-project/notes/notes.module.ts b/src/app/child-dev-project/notes/notes.module.ts index 7c6cf03233..94eb56d568 100644 --- a/src/app/child-dev-project/notes/notes.module.ts +++ b/src/app/child-dev-project/notes/notes.module.ts @@ -46,6 +46,7 @@ import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; import { NotesDashboardComponent } from "./dashboard-widgets/notes-dashboard/notes-dashboard.component"; import { NotesOfChildComponent } from "./notes-of-child/notes-of-child.component"; import { DashboardModule } from "../../core/dashboard/dashboard.module"; +import { ExportModule } from "../../core/export/export.module"; @NgModule({ declarations: [ @@ -103,6 +104,7 @@ import { DashboardModule } from "../../core/dashboard/dashboard.module"; FontAwesomeModule, MatMenuModule, DashboardModule, + ExportModule, ], exports: [NoteDetailsComponent], }) diff --git a/src/app/core/export/export-service/export.service.spec.ts b/src/app/core/export/export-service/export.service.spec.ts index 5ee783094a..17b9264bea 100644 --- a/src/app/core/export/export-service/export.service.spec.ts +++ b/src/app/core/export/export-service/export.service.spec.ts @@ -176,9 +176,7 @@ describe("ExportService", () => { const columnValues = rows[1].split(ExportService.SEPARATOR_COL); expect(columnValues).toHaveSize(3 + 1); // Properties + _id expect(columnValues).toContain('"' + testEnumValue.label + '"'); - expect(columnValues).toContain( - '"' + moment(new Date(testDate)).toISOString(true) + '"' - ); + expect(columnValues).toContain('"' + testDate + '"'); expect(columnValues).toContain('"true"'); }); @@ -277,11 +275,10 @@ describe("ExportService", () => { ]); }); - it("should export a date according to the local format", async () => { - // Create date at midnight on first of january 2021 + it("should export a date as YYYY-MM-dd only", async () => { const dateString = "2021-01-01"; const dateObject = new Date(dateString); - dateObject.setHours(0, 0, 0); + dateObject.setHours(10, 11, 12); const exportData = [ { @@ -294,12 +291,9 @@ describe("ExportService", () => { const csv = await service.createCsv(exportData); const results = csv.split(ExportService.SEPARATOR_ROW); - // Format: yyyy-mm-ddThh:mm:ss.mmm+hh:mm - const expectedDateFormat = - dateString + "T00:00:00.000" + getTimezoneOffset(dateObject); expect(results).toEqual([ '"date","number","string"', - `"${expectedDateFormat}","10","someString"`, + `"${dateString}","10","someString"`, ]); }); @@ -529,23 +523,4 @@ describe("ExportService", () => { return school; } - - /** - * Returns the timezone offset in hours and minutes. - * E.g. german date object => "+02:00" or "+01:00" depending on time of the year - * @param date object for which the offset should be calculated - */ - function getTimezoneOffset(date: Date): string { - // from https://usefulangle.com/post/30/javascript-get-date-time-with-offset-hours-minutes - const offset = date.getTimezoneOffset(); - - const offsetHrs = parseInt(Math.abs(offset / 60).toString(), 10); - const offsetMin = Math.abs(offset % 60); - - const hrsString = offsetHrs > 10 ? offsetHrs.toString() : "0" + offsetHrs; - const minString = offsetMin > 10 ? offsetMin.toString() : "0" + offsetMin; - - const sign = offset > 0 ? "-" : "+"; - return sign + hrsString + ":" + minString; - } }); diff --git a/src/app/core/export/export-service/export.service.ts b/src/app/core/export/export-service/export.service.ts index 76f55aa7d2..e0220e87fc 100644 --- a/src/app/core/export/export-service/export.service.ts +++ b/src/app/core/export/export-service/export.service.ts @@ -111,8 +111,9 @@ export class ExportService { const readableRow = {}; Object.keys(row).forEach((key) => { if (row[key] instanceof Date) { - // Export data according to local timezone offset - readableRow[key] = moment(row[key]).toISOString(true); + // Export data according to local timezone offset - data is loaded through Entity Schema system and thereby has the correct date in the current device's timezone + // TODO: make this output format configurable or use the different date schema types [GITHUB #1185] + readableRow[key] = moment(row[key]).format("YYYY-MM-DD"); } else { readableRow[key] = getReadableValue(row, key); } diff --git a/src/locale/messages.de.xlf b/src/locale/messages.de.xlf index 6c8d84e189..4da114a92b 100644 --- a/src/locale/messages.de.xlf +++ b/src/locale/messages.de.xlf @@ -125,7 +125,7 @@ Groups that belong to a note src/app/child-dev-project/notes/note-details/note-details.component.html - 162 + 193 @@ -134,7 +134,7 @@ Add a group to a note src/app/child-dev-project/notes/note-details/note-details.component.html - 164 + 195 @@ -154,7 +154,7 @@ Participants of a note src/app/child-dev-project/notes/note-details/note-details.component.html - 129 + 160 @@ -163,7 +163,7 @@ Add participants of a note src/app/child-dev-project/notes/note-details/note-details.component.html - 131 + 162 @@ -1201,6 +1201,15 @@ 22 + + Download details + Details herunterladen + + src/app/child-dev-project/notes/note-details/note-details.component.html + 51 + + Download note details as CSV + since the beginning of the week seit Anfang der Woche @@ -1228,7 +1237,7 @@ Date input src/app/child-dev-project/notes/note-details/note-details.component.html - 31 + 62 @@ -1237,7 +1246,7 @@ Status of a note src/app/child-dev-project/notes/note-details/note-details.component.html - 44 + 75 @@ -1246,7 +1255,7 @@ Type of Interaction when adding event src/app/child-dev-project/notes/note-details/note-details.component.html - 58 + 89 @@ -1255,7 +1264,7 @@ placeholder when adding multiple authors src/app/child-dev-project/notes/note-details/note-details.component.html - 80 + 111 @@ -1264,7 +1273,7 @@ Authors of a note src/app/child-dev-project/notes/note-details/note-details.component.html - 82 + 113 @@ -1275,7 +1284,7 @@ Placeholder src/app/child-dev-project/notes/note-details/note-details.component.html - 97 + 128 @@ -1286,7 +1295,7 @@ Placeholder src/app/child-dev-project/notes/note-details/note-details.component.html - 112 + 143 diff --git a/src/locale/messages.fr.xlf b/src/locale/messages.fr.xlf index 24e1e1d620..b39b6f2337 100644 --- a/src/locale/messages.fr.xlf +++ b/src/locale/messages.fr.xlf @@ -2118,6 +2118,15 @@ 22 + + Download details + Download details + + src/app/child-dev-project/notes/note-details/note-details.component.html + 51 + + Download note details as CSV + Date Date @@ -2125,7 +2134,7 @@ Date input src/app/child-dev-project/notes/note-details/note-details.component.html - 31 + 62 @@ -2134,7 +2143,7 @@ Status of a note src/app/child-dev-project/notes/note-details/note-details.component.html - 44 + 75 @@ -2143,7 +2152,7 @@ Type of Interaction when adding event src/app/child-dev-project/notes/note-details/note-details.component.html - 58 + 89 @@ -2152,7 +2161,7 @@ placeholder when adding multiple authors src/app/child-dev-project/notes/note-details/note-details.component.html - 80 + 111 @@ -2161,7 +2170,7 @@ Authors of a note src/app/child-dev-project/notes/note-details/note-details.component.html - 82 + 113 @@ -2172,7 +2181,7 @@ Placeholder src/app/child-dev-project/notes/note-details/note-details.component.html - 97 + 128 @@ -2183,7 +2192,7 @@ Placeholder src/app/child-dev-project/notes/note-details/note-details.component.html - 112 + 143 @@ -2192,7 +2201,7 @@ Participants of a note src/app/child-dev-project/notes/note-details/note-details.component.html - 129 + 160 @@ -2201,7 +2210,7 @@ Add participants of a note src/app/child-dev-project/notes/note-details/note-details.component.html - 131 + 162 @@ -2210,7 +2219,7 @@ Groups that belong to a note src/app/child-dev-project/notes/note-details/note-details.component.html - 162 + 193 @@ -2219,7 +2228,7 @@ Add a group to a note src/app/child-dev-project/notes/note-details/note-details.component.html - 164 + 195 diff --git a/src/locale/messages.xlf b/src/locale/messages.xlf index 863537aa4f..ffb341086b 100644 --- a/src/locale/messages.xlf +++ b/src/locale/messages.xlf @@ -1894,11 +1894,19 @@ 22 + + Download details + + src/app/child-dev-project/notes/note-details/note-details.component.html + 51 + + Download note details as CSV + Date src/app/child-dev-project/notes/note-details/note-details.component.html - 31 + 62 Placeholder for a date-input Date input @@ -1907,7 +1915,7 @@ Status src/app/child-dev-project/notes/note-details/note-details.component.html - 44 + 75 Status of a note @@ -1915,7 +1923,7 @@ Type of Interaction src/app/child-dev-project/notes/note-details/note-details.component.html - 58 + 89 Type of Interaction when adding event @@ -1923,7 +1931,7 @@ Add Author... src/app/child-dev-project/notes/note-details/note-details.component.html - 80 + 111 placeholder when adding multiple authors @@ -1931,7 +1939,7 @@ Authors src/app/child-dev-project/notes/note-details/note-details.component.html - 82 + 113 Authors of a note @@ -1939,7 +1947,7 @@ Topic / Summary src/app/child-dev-project/notes/note-details/note-details.component.html - 97 + 128 Placeholder informing that this is the Topic/Summary of the note @@ -1949,7 +1957,7 @@ Notes src/app/child-dev-project/notes/note-details/note-details.component.html - 112 + 143 Placeholder informing that this is textarea the actual note can be entered into @@ -1959,7 +1967,7 @@ Participants src/app/child-dev-project/notes/note-details/note-details.component.html - 129 + 160 Participants of a note @@ -1967,7 +1975,7 @@ Add participant ... src/app/child-dev-project/notes/note-details/note-details.component.html - 131 + 162 Add participants of a note @@ -1975,7 +1983,7 @@ Groups src/app/child-dev-project/notes/note-details/note-details.component.html - 162 + 193 Groups that belong to a note @@ -1983,7 +1991,7 @@ Add group ... src/app/child-dev-project/notes/note-details/note-details.component.html - 164 + 195 Add a group to a note From 848659665811abe610da2ad32970a9645665965a Mon Sep 17 00:00:00 2001 From: Schottkyc137 <45085299+Schottkyc137@users.noreply.github.com> Date: Fri, 8 Apr 2022 22:36:34 +0200 Subject: [PATCH 13/13] refactor: Implement batch save for demo data closes #1168 Co-authored-by: Simon <33730997+TheSlimvReal@users.noreply.github.com> Co-authored-by: Simon --- src/app/core/database/database.ts | 8 ++ src/app/core/database/pouch-database.spec.ts | 97 ++++++++++++--- src/app/core/database/pouch-database.ts | 41 ++++++- src/app/core/demo-data/demo-data.service.ts | 10 +- .../core/entity/entity-mapper.service.spec.ts | 115 +++++++++++++----- src/app/core/entity/entity-mapper.service.ts | 24 +++- 6 files changed, 228 insertions(+), 67 deletions(-) diff --git a/src/app/core/database/database.ts b/src/app/core/database/database.ts index ed897660ee..bc22da9fc7 100644 --- a/src/app/core/database/database.ts +++ b/src/app/core/database/database.ts @@ -48,6 +48,14 @@ export abstract class Database { */ abstract put(object: any, forceUpdate?: boolean): Promise; + /** + * Save a bunch of documents at once to the database + * @param objects The documents to be saved + * @param forceUpdate (Optional) Whether conflicts should be ignored and existing conflicting documents forcefully overwritten. + * @returns array holding success responses or errors depending on the success of the operation + */ + abstract putAll(objects: any[], forceUpdate?: boolean): Promise; + /** * Delete a document from the database * @param object The document to be deleted (usually this object must at least contain the _id and _rev) diff --git a/src/app/core/database/pouch-database.spec.ts b/src/app/core/database/pouch-database.spec.ts index 2cb24e1e0f..92a8e28357 100644 --- a/src/app/core/database/pouch-database.spec.ts +++ b/src/app/core/database/pouch-database.spec.ts @@ -157,22 +157,19 @@ describe("PouchDatabase tests", () => { it("saveDatabaseIndex updates existing index", async () => { const testIndex = { _id: "_design/test_index", views: { a: {}, b: {} } }; - const existingIndex = { - _id: "_design/test_index", - _rev: "01", + await database.put({ + _id: testIndex._id, views: { a: {} }, - }; - // @ts-ignore - const pouchDB = database._pouchDB; - spyOn(pouchDB, "get").and.resolveTo(existingIndex); + }); + const existingIndex = await database.get(testIndex._id); spyOn(database, "put").and.resolveTo(); const spyOnQuery = spyOn(database, "query").and.resolveTo(); await database.saveDatabaseIndex(testIndex); expect(database.put).toHaveBeenCalledWith({ _id: testIndex._id, - views: testIndex.views, _rev: existingIndex._rev, + views: testIndex.views, }); // expect all indices to be queried @@ -181,33 +178,24 @@ describe("PouchDatabase tests", () => { ["test_index/a", { key: "1" }], ["test_index/b", { key: "1" }], ]); - - // reset pouchDB function - pouchDB.get.and.callThrough(); }); it("saveDatabaseIndex does not update unchanged index", async () => { const testIndex = { _id: "_design/test_index", views: { a: {}, b: {} } }; const existingIndex = { _id: "_design/test_index", - _rev: "01", views: testIndex.views, }; - // @ts-ignore - const pouchDB = database._pouchDB; - spyOn(pouchDB, "get").and.resolveTo(existingIndex); + await database.put(existingIndex); spyOn(database, "put").and.resolveTo(); await database.saveDatabaseIndex(testIndex); expect(database.put).not.toHaveBeenCalled(); - - // reset pouchDB function - pouchDB.get.and.callThrough(); }); - it("query simply calls through to query", async () => { + it("query simply calls through to pouchDB query", async () => { const testQuery = "testquery"; - const testQueryResults = { rows: [] }; + const testQueryResults = { rows: [] } as any; // @ts-ignore const pouchDB = database._pouchDB; spyOn(pouchDB, "query").and.resolveTo(testQueryResults); @@ -215,4 +203,73 @@ describe("PouchDatabase tests", () => { expect(result).toEqual(testQueryResults); expect(pouchDB.query).toHaveBeenCalledWith(testQuery, {}); }); + + it("writes all the docs to the database with putAll", async () => { + await database.putAll([ + { + _id: "5", + name: "The Grinch", + }, + { + _id: "8", + name: "Santa Claus", + }, + ]); + + await expectAsync(database.get("5")).toBeResolvedTo( + jasmine.objectContaining({ + _id: "5", + name: "The Grinch", + }) + ); + await expectAsync(database.get("8")).toBeResolvedTo( + jasmine.objectContaining({ + _id: "8", + name: "Santa Claus", + }) + ); + }); + + it("Throws errors for each conflict individually", async () => { + const resolveConflictSpy = spyOn(database, "resolveConflict"); + const conflictError = new Error(); + resolveConflictSpy.and.rejectWith(conflictError); + await database.put({ + _id: "3", + name: "Rudolph, the Red-Nosed Reindeer", + }); + const dataWithConflicts = [ + { + _id: "3", + name: "Rudolph, the Pink-Nosed Reindeer", + _rev: "1-invalid-rev", + }, + { + _id: "4", + name: "Dasher", + }, + { + _id: "5", + name: "Dancer", + }, + ]; + + const results = await database.putAll(dataWithConflicts); + expect(resolveConflictSpy.calls.allArgs()).toEqual([ + [ + { + _id: "3", + name: "Rudolph, the Pink-Nosed Reindeer", + _rev: "1-invalid-rev", + }, + false, + jasmine.objectContaining({ status: 409 }), + ], + ]); + expect(results).toEqual([ + conflictError, + jasmine.objectContaining({ id: "4", ok: true }), + jasmine.objectContaining({ id: "5", ok: true }), + ]); + }); }); diff --git a/src/app/core/database/pouch-database.ts b/src/app/core/database/pouch-database.ts index 93db3a4194..652569d3a8 100644 --- a/src/app/core/database/pouch-database.ts +++ b/src/app/core/database/pouch-database.ts @@ -66,7 +66,10 @@ export class PouchDatabase extends Database { * @param _pouchDB An (initialized) PouchDB database instance from the PouchDB library. * @param loggingService The LoggingService instance of the app to log and report problems. */ - constructor(private _pouchDB: any, private loggingService: LoggingService) { + constructor( + private _pouchDB: PouchDB.Database, + private loggingService: LoggingService + ) { super(); } @@ -125,13 +128,12 @@ export class PouchDatabase extends Database { * @param object The document to be saved * @param forceOverwrite (Optional) Whether conflicts should be ignored and an existing conflicting document forcefully overwritten. */ - put(object: any, forceOverwrite?: boolean): Promise { - const options: any = {}; + put(object: any, forceOverwrite = false): Promise { if (forceOverwrite) { object._rev = undefined; } - return this._pouchDB.put(object, options).catch((err) => { + return this._pouchDB.put(object).catch((err) => { if (err.status === 409) { return this.resolveConflict(object, forceOverwrite, err); } else { @@ -140,6 +142,33 @@ export class PouchDatabase extends Database { }); } + /** + * Save an array of documents to the database + * @param objects the documents to be saved + * @param forceOverwrite whether conflicting versions should be overwritten + * @returns array holding `{ ok: true, ... }` or `{ error: true, ... }` depending on whether the document could be saved + */ + async putAll(objects: any[], forceOverwrite = false): Promise { + if (forceOverwrite) { + objects.forEach((obj) => (obj._rev = undefined)); + } + + const results = await this._pouchDB.bulkDocs(objects); + + for (let i = 0; i < results.length; i++) { + // Check if document update conflicts happened in the request + const result = results[i] as PouchDB.Core.Error; + if (result.status === 409) { + results[i] = await this.resolveConflict( + objects.find((obj) => obj._id === result.id), + false, + result + ).catch((e) => e); + } + } + return results; + } + /** * Delete a document from the database * (see {@link Database}) @@ -241,8 +270,8 @@ export class PouchDatabase extends Database { */ private async resolveConflict( newObject: any, - overwriteChanges: boolean, - existingError: any + overwriteChanges = false, + existingError: any = {} ): Promise { const existingObject = await this.get(newObject._id); const resolvedObject = this.mergeObjects(existingObject, newObject); diff --git a/src/app/core/demo-data/demo-data.service.ts b/src/app/core/demo-data/demo-data.service.ts index dd99135be1..60ddf09bc9 100644 --- a/src/app/core/demo-data/demo-data.service.ts +++ b/src/app/core/demo-data/demo-data.service.ts @@ -24,7 +24,6 @@ import { } from "@angular/core"; import { DemoDataGenerator } from "./demo-data-generator"; import { EntityMapperService } from "../entity/entity-mapper.service"; -import { LoggingService } from "../logging/logging.service"; import { User } from "../user/user"; /** @@ -60,7 +59,6 @@ export class DemoDataService { constructor( private entityMapper: EntityMapperService, - private loggingService: LoggingService, private injector: Injector, private config: DemoDataServiceConfig ) { @@ -92,13 +90,7 @@ export class DemoDataService { // save the generated data for (const generator of this.dataGenerators) { - for (const entity of generator.entities) { - try { - await this.entityMapper.save(entity); - } catch (e) { - this.loggingService.warn(e); - } - } + await this.entityMapper.saveAll(generator.entities); } } diff --git a/src/app/core/entity/entity-mapper.service.spec.ts b/src/app/core/entity/entity-mapper.service.spec.ts index c565f56a0c..7335ac9399 100644 --- a/src/app/core/entity/entity-mapper.service.spec.ts +++ b/src/app/core/entity/entity-mapper.service.spec.ts @@ -20,7 +20,7 @@ import { Entity } from "./model/entity"; import { EntitySchemaService } from "./schema/entity-schema.service"; import { waitForAsync } from "@angular/core/testing"; import { PouchDatabase } from "../database/pouch-database"; -import { entityRegistry } from "./database-entity.decorator"; +import { DatabaseEntity, entityRegistry } from "./database-entity.decorator"; describe("EntityMapperService", () => { let entityMapper: EntityMapperService; @@ -178,55 +178,108 @@ describe("EntityMapperService", () => { expect(loadedByFullId._rev).toBe(loadedByEntityId._rev); }); - it("publishes updates to any listeners", (done) => { + it("publishes updates to any listeners", () => { const testId = "t1"; - receiveUpdatesAndTestTypeAndId(done, undefined, testId); - const testEntity = new Entity(testId); entityMapper .save(testEntity, true) .then(() => entityMapper.remove(testEntity)); - }); - it("publishes when an existing entity is updated", (done) => { - receiveUpdatesAndTestTypeAndId(done, "update", existingEntity.entityId); + return receiveUpdatesAndTestTypeAndId(undefined, testId); + }); + it("publishes when an existing entity is updated", () => { entityMapper - .load(Entity, existingEntity.entityId) - .then((loadedEntity) => entityMapper.save(loadedEntity)); - }); + .load(Entity, existingEntity.entityId) + .then((loadedEntity) => entityMapper.save(loadedEntity)); - it("publishes when an existing entity is deleted", (done) => { - receiveUpdatesAndTestTypeAndId(done, "remove", existingEntity.entityId); + return receiveUpdatesAndTestTypeAndId("update", existingEntity.entityId); + }); + it("publishes when an existing entity is deleted", () => { entityMapper - .load(Entity, existingEntity.entityId) - .then((loadedEntity) => entityMapper.remove(loadedEntity)); + .load(Entity, existingEntity.entityId) + .then((loadedEntity) => entityMapper.remove(loadedEntity)); + + return receiveUpdatesAndTestTypeAndId("remove", existingEntity.entityId); }); - it("publishes when a new entity is being saved", (done) => { + it("publishes when a new entity is being saved", () => { const testId = "t1"; - receiveUpdatesAndTestTypeAndId(done, "new", testId); - const testEntity = new Entity(testId); entityMapper.save(testEntity, true); + + return receiveUpdatesAndTestTypeAndId("new", testId); }); - function receiveUpdatesAndTestTypeAndId( - done: any, - type?: string, - entityId?: string - ) { - entityMapper.receiveUpdates(Entity).subscribe((e) => { - if (e) { - if (type) { - expect(e.type).toBe(type); - } - if (entityId) { - expect(e.entity.entityId).toBe(entityId); + it("correctly behaves when en empty array is given to the saveAll function", async () => { + const result = await entityMapper.saveAll([]); + expect(result).toHaveSize(0); + }); + + it("correctly saves an array of heterogeneous entities", async () => { + const result = await entityMapper.saveAll([ + new MockEntityA("1"), + new MockEntityA("10"), + new MockEntityA("42"), + ]); + expect(result).toEqual([ + jasmine.objectContaining({ + ok: true, + id: "EntityA:1", + }), + jasmine.objectContaining({ + ok: true, + id: "EntityA:10", + }), + jasmine.objectContaining({ + ok: true, + id: "EntityA:42", + }), + ]); + }); + + it("correctly saves an array of homogeneous entities", async () => { + const result = await entityMapper.saveAll([ + new MockEntityA("1"), + new MockEntityB("10"), + new MockEntityA("42"), + ]); + expect(result).toEqual([ + jasmine.objectContaining({ + ok: true, + id: "EntityA:1", + }), + jasmine.objectContaining({ + ok: true, + id: "EntityB:10", + }), + jasmine.objectContaining({ + ok: true, + id: "EntityA:42", + }), + ]); + }); + + function receiveUpdatesAndTestTypeAndId(type?: string, entityId?: string) { + return new Promise((resolve) => { + entityMapper.receiveUpdates(Entity).subscribe((e) => { + if (e) { + if (type) { + expect(e.type).toBe(type); + } + if (entityId) { + expect(e.entity.entityId).toBe(entityId); + } + resolve(); } - done(); - } + }); }); } + + @DatabaseEntity("EntityA") + class MockEntityA extends Entity {} + + @DatabaseEntity("EntityB") + class MockEntityB extends Entity {} }); diff --git a/src/app/core/entity/entity-mapper.service.ts b/src/app/core/entity/entity-mapper.service.ts index b1bd872195..53a83b5e56 100644 --- a/src/app/core/entity/entity-mapper.service.ts +++ b/src/app/core/entity/entity-mapper.service.ts @@ -133,14 +133,36 @@ export class EntityMapperService { const rawData = this.entitySchemaService.transformEntityToDatabaseFormat( entity ); - this.sendUpdate(entity, entity._rev === undefined ? "new" : "update"); const result = await this._db.put(rawData, forceUpdate); if (result?.ok) { + this.sendUpdate(entity, entity._rev === undefined ? "new" : "update"); entity._rev = result.rev; } return result; } + /** + * Saves an array of entities that are possibly heterogeneous, i.e. + * the entity-type of all the entities does not have to be the same. + * This method should be chosen whenever a bigger number of entities needs to be + * saved + * @param entities The entities to save + */ + public async saveAll(entities: Entity[]): Promise { + const rawData = entities.map((e) => + this.entitySchemaService.transformEntityToDatabaseFormat(e) + ); + const results = await this._db.putAll(rawData); + results.forEach((res, idx) => { + if (res.ok) { + const entity = entities[idx]; + this.sendUpdate(entity, entity._rev === undefined ? "new" : "update"); + entity._rev = res.rev; + } + }); + return results; + } + /** * Delete an entity from the database. * @param entity The entity to be deleted
{{ alert.type }} {{ alert.message }}