From 213aa511a367ceb20da2b2a0638b036b24154bb8 Mon Sep 17 00:00:00 2001 From: David Losada Carballo Date: Sun, 31 Mar 2024 12:07:14 +0200 Subject: [PATCH 01/13] USH-1253: fixate humanitarian layer URL scheme --- apps/mobile-mzima-client/src/app/core/helpers/map.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/mobile-mzima-client/src/app/core/helpers/map.ts b/apps/mobile-mzima-client/src/app/core/helpers/map.ts index 047c5aeaa6..822b405990 100644 --- a/apps/mobile-mzima-client/src/app/core/helpers/map.ts +++ b/apps/mobile-mzima-client/src/app/core/helpers/map.ts @@ -52,7 +52,7 @@ export const getMapLayers = () => { MapQuest: mapboxStaticTiles('Streets', 'mapbox/streets-v11'), hOSM: { name: 'Humanitarian', - url: '//{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', + url: 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', layerOptions: { attribution: '© OpenStreetMap, © Humanitarian OpenStreetMap | Improve the underlying map', From 78088029be2c783e868bb08740cf09e9065ab646 Mon Sep 17 00:00:00 2001 From: Blessing Peters Date: Fri, 5 Apr 2024 15:44:16 +0100 Subject: [PATCH 02/13] Reword survey error message and highlight survey name field (#4736) (#853) * Reword survey error message and highlight survey name field * Enhance survey name field validation --------- Co-authored-by: Anna Iosif --- apps/web-mzima-client/src/app/core/helpers/regex.ts | 2 +- .../surveys/survey-item/survey-item.component.html | 2 +- .../settings/surveys/survey-item/survey-item.component.ts | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/web-mzima-client/src/app/core/helpers/regex.ts b/apps/web-mzima-client/src/app/core/helpers/regex.ts index 0dda8b95d0..329635b145 100644 --- a/apps/web-mzima-client/src/app/core/helpers/regex.ts +++ b/apps/web-mzima-client/src/app/core/helpers/regex.ts @@ -9,7 +9,7 @@ export const decimalPattern = (value: string) => { }; export const alphaNumeric = (value: string) => { - const pattern = XRegExp('^[\\p{L}\\p{N}\\s\\-".?!;:,#+_=*&№<>@\'()“”«»\\\\/|]*$', 'gu'); + const pattern = XRegExp('^[\\p{L}\\p{N}\\s\\-".?!;:,#_=*&№<>@\'()“”«»\\\\/|]*$', 'gu'); return pattern.test(value); }; diff --git a/apps/web-mzima-client/src/app/settings/surveys/survey-item/survey-item.component.html b/apps/web-mzima-client/src/app/settings/surveys/survey-item/survey-item.component.html index fad542cf3a..f157b54247 100644 --- a/apps/web-mzima-client/src/app/settings/surveys/survey-item/survey-item.component.html +++ b/apps/web-mzima-client/src/app/settings/surveys/survey-item/survey-item.component.html @@ -2,7 +2,7 @@ novalidate [formGroup]="form" class="main-form" - (ngSubmit)="save()" + (ngSubmit)="form.valid && save()" [ngStyle]="{ '--color': this.form.get('color')?.value }" diff --git a/apps/web-mzima-client/src/app/settings/surveys/survey-item/survey-item.component.ts b/apps/web-mzima-client/src/app/settings/surveys/survey-item/survey-item.component.ts index a271900918..27e3b3b0cf 100644 --- a/apps/web-mzima-client/src/app/settings/surveys/survey-item/survey-item.component.ts +++ b/apps/web-mzima-client/src/app/settings/surveys/survey-item/survey-item.component.ts @@ -8,7 +8,7 @@ import { LanguageInterface } from '@models'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { BreakpointService, SessionService } from '@services'; import { BaseComponent } from '../../../base.component'; -import { noWhitespaceValidator } from '../../../core/validators'; +import { AlphanumericValidatorValidator, noWhitespaceValidator } from '../../../core/validators'; import { SelectLanguagesModalComponent } from '../../../shared/components'; import { CreateTaskModalComponent } from '../create-task-modal/create-task-modal.component'; import { SurveyTaskComponent } from '../survey-task/survey-task.component'; @@ -68,7 +68,7 @@ export class SurveyItemComponent extends BaseComponent implements OnInit { this.checkDesktop(); this.form = this.formBuilder.group({ - name: ['', [Validators.required, noWhitespaceValidator]], + name: ['', [Validators.required, noWhitespaceValidator, AlphanumericValidatorValidator()]], description: [''], color: [null], enabled_languages: this.formBuilder.group({ @@ -289,7 +289,8 @@ export class SurveyItemComponent extends BaseComponent implements OnInit { error: ({ error }) => { this.submitted = false; if (error.errors.status === 422) { - this.notification.showError(JSON.stringify(error.errors.message)); + this.form.controls['name'].setErrors({ invalidCharacters: true }); + this.notification.showError('Please remove invalid characters (e.g. +, $, ^, =)'); } else { this.notification.showError(JSON.stringify(error.name[0])); } From f440fc15de232f8938a048d1ad37ff11f2b38260 Mon Sep 17 00:00:00 2001 From: Ifycode Date: Thu, 28 Mar 2024 13:35:36 +0100 Subject: [PATCH 03/13] Fix added: Remove mobile menu from DOM when button hasn't toggled it on/visble --- .../fragments/mobile-menu/mobile-menu.component.ts | 9 ++++++++- .../app/shared/components/toolbar/toolbar.component.html | 3 ++- .../app/shared/components/toolbar/toolbar.component.ts | 9 +++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/apps/web-mzima-client/src/app/shared/components/fragments/mobile-menu/mobile-menu.component.ts b/apps/web-mzima-client/src/app/shared/components/fragments/mobile-menu/mobile-menu.component.ts index 20bae11664..f84d130492 100644 --- a/apps/web-mzima-client/src/app/shared/components/fragments/mobile-menu/mobile-menu.component.ts +++ b/apps/web-mzima-client/src/app/shared/components/fragments/mobile-menu/mobile-menu.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, EventEmitter, Output } from '@angular/core'; import { Observable, fromEvent } from 'rxjs'; import { NavToolbarService } from '../../../helpers/navtoolbar.service'; import { BaseComponent } from '../../../../base.component'; @@ -10,6 +10,7 @@ import { BreakpointService, SessionService } from '@services'; styleUrls: ['./mobile-menu.component.scss'], }) export class MobileMenuComponent extends BaseComponent { + @Output() isBurgerMenuOpenEvent = new EventEmitter(); public isBurgerMenuOpen: boolean; public clickObservable: Observable = fromEvent(document, 'click'); @@ -31,6 +32,12 @@ export class MobileMenuComponent extends BaseComponent { this.isBurgerMenuOpen ? document.body.classList.add('burger-menu-open') : document.body.classList.remove('burger-menu-open'); + // Get isBurgerMenuOpen for use in parent component to hide (the whole of) mobile-menu + this.getIsBurgerMenuOpen(this.isBurgerMenuOpen); }); } + + public getIsBurgerMenuOpen(value: boolean) { + this.isBurgerMenuOpenEvent.emit(value); + } } diff --git a/apps/web-mzima-client/src/app/shared/components/toolbar/toolbar.component.html b/apps/web-mzima-client/src/app/shared/components/toolbar/toolbar.component.html index 462052444d..83c32566d6 100644 --- a/apps/web-mzima-client/src/app/shared/components/toolbar/toolbar.component.html +++ b/apps/web-mzima-client/src/app/shared/components/toolbar/toolbar.component.html @@ -35,4 +35,5 @@

+ diff --git a/apps/web-mzima-client/src/app/shared/components/toolbar/toolbar.component.ts b/apps/web-mzima-client/src/app/shared/components/toolbar/toolbar.component.ts index db77afce26..ad85ae95ce 100644 --- a/apps/web-mzima-client/src/app/shared/components/toolbar/toolbar.component.ts +++ b/apps/web-mzima-client/src/app/shared/components/toolbar/toolbar.component.ts @@ -87,6 +87,15 @@ export class ToolbarComponent extends BaseComponent implements OnInit { public toggleBurgerMenu(): void { this.navToolbarService.toggleBurgerMenu(); //toggles true & false for hamburger button + + // Since the mobile-menu element is removed from the DOM initially and every other time that this.isBurgerMenuOpen is false, + // We are therefore not able to access the @Output EventEmitter on it + // Calling this method here helps to restore the mobile-menu element back through the toggle button click, so that we can have access to the mobile-menu element and the @Output EventEmitter on it + this.getIsBurgerMenuOpen(!this.isBurgerMenuOpen); + } + + public getIsBurgerMenuOpen(value: boolean) { + this.isBurgerMenuOpen = value; } public back(): void { From f14d85f07de527fba0886546872afd14fa1cf844 Mon Sep 17 00:00:00 2001 From: Rwubakwanayo Olivier Date: Mon, 8 Apr 2024 15:50:01 +0200 Subject: [PATCH 04/13] check if input is touched before throw error (#969) Co-authored-by: Anna Iosif --- .../src/app/profile/information/information.page.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/mobile-mzima-client/src/app/profile/information/information.page.html b/apps/mobile-mzima-client/src/app/profile/information/information.page.html index c9bf5aa465..2888ba287b 100644 --- a/apps/mobile-mzima-client/src/app/profile/information/information.page.html +++ b/apps/mobile-mzima-client/src/app/profile/information/information.page.html @@ -74,7 +74,7 @@ [required]="true" formControlName="currentEmail" placeholder="Enter your current email" - [errors]="changeEmailForm.hasError('notSameAsCurrent') ? ['The entered email does not match the current email. Please make sure to enter the correct email address.'] : undefined" + [errors]="changeEmailForm.get('currentEmail')?.touched && changeEmailForm.hasError('notSameAsCurrent') ? ['The entered email does not match the current email. Please make sure to enter the correct email address.'] : undefined" > Date: Mon, 8 Apr 2024 16:24:59 +0200 Subject: [PATCH 05/13] modify media query style for collection (#970) Co-authored-by: Anna Iosif --- .../shared/components/collections/collections.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web-mzima-client/src/app/shared/components/collections/collections.component.scss b/apps/web-mzima-client/src/app/shared/components/collections/collections.component.scss index f1a95eebd1..e224e8e71f 100644 --- a/apps/web-mzima-client/src/app/shared/components/collections/collections.component.scss +++ b/apps/web-mzima-client/src/app/shared/components/collections/collections.component.scss @@ -37,7 +37,7 @@ margin: 0 -8px -24px; justify-content: flex-start; - @include breakpoint-max($mobile) { + @include breakpoint-max($tablet) { margin: 0; display: block; } From 51ecbab82f762a5da8dee1cc0b1c8c21b58f324c Mon Sep 17 00:00:00 2001 From: Mugo Brian Date: Mon, 8 Apr 2024 17:52:52 +0300 Subject: [PATCH 06/13] Removed unnecessary ng-templates. Adhering to DRY principle. (#973) Co-authored-by: Anna Iosif --- .../menu-list-links.component.html | 19 ++-------------- .../menu-list-non-links.component.html | 22 ++++--------------- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/apps/web-mzima-client/src/app/shared/components/fragments/menu-list-links/menu-list-links.component.html b/apps/web-mzima-client/src/app/shared/components/fragments/menu-list-links/menu-list-links.component.html index a9559dd443..164cb3f2da 100644 --- a/apps/web-mzima-client/src/app/shared/components/fragments/menu-list-links/menu-list-links.component.html +++ b/apps/web-mzima-client/src/app/shared/components/fragments/menu-list-links/menu-list-links.component.html @@ -6,30 +6,15 @@ class="sidebar-menu-button" [data-qa]="'btn-' + item.icon" [attr.data-onboard-id]="'sidebar-btn-' + item.icon" - *ngIf="item.router; else menuAction" - [routerLink]="createRouterLink(item.router)" + [routerLink]="item.router && createRouterLink(item.router)" routerLinkActive="sidebar-menu-button--active" - (click)="registerPage($event, item.router, item.label)" + (click)="item.router && registerPage($event, item.router, item.label)" > {{ item.label | translate }} - - - diff --git a/apps/web-mzima-client/src/app/shared/components/fragments/menu-list-non-links/menu-list-non-links.component.html b/apps/web-mzima-client/src/app/shared/components/fragments/menu-list-non-links/menu-list-non-links.component.html index 8b1334a131..a0a470a66a 100644 --- a/apps/web-mzima-client/src/app/shared/components/fragments/menu-list-non-links/menu-list-non-links.component.html +++ b/apps/web-mzima-client/src/app/shared/components/fragments/menu-list-non-links/menu-list-non-links.component.html @@ -6,30 +6,16 @@ - - - - - {{ item.label | translate }} - - - From 4addb66f359a1a6e38dad983eba5069d4ae38637 Mon Sep 17 00:00:00 2001 From: Chiemezuo Date: Tue, 9 Apr 2024 17:28:23 +0100 Subject: [PATCH 07/13] fix "change_laguage" to "change_language" (#1004) --- e2e-testing/cypress/functions/LoginFunctions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e-testing/cypress/functions/LoginFunctions.js b/e2e-testing/cypress/functions/LoginFunctions.js index cee9837d80..6d7c3f1a47 100644 --- a/e2e-testing/cypress/functions/LoginFunctions.js +++ b/e2e-testing/cypress/functions/LoginFunctions.js @@ -4,7 +4,7 @@ class LoginFunctions { launch_login_modal(launchURL) { cy.visit(launchURL); this.click_through_onboarding(); - this.change_laguage(); + this.change_language(); cy.get(LoginLocators.loginModal).click(); } @@ -34,7 +34,7 @@ class LoginFunctions { } //quick-fix, change language to english after logging in - change_laguage() { + change_language() { cy.get('.language__selected').click(); cy.get('#mat-option-7 > .mat-option-text').click(); } From 2e92237645c41315a8323559cc530c0d4c2b98c0 Mon Sep 17 00:00:00 2001 From: Chiemezuo Date: Thu, 11 Apr 2024 16:18:00 +0100 Subject: [PATCH 08/13] make correction (#968) --- apps/mobile-mzima-client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/mobile-mzima-client/package.json b/apps/mobile-mzima-client/package.json index 54b7285b95..73a5d32bb9 100644 --- a/apps/mobile-mzima-client/package.json +++ b/apps/mobile-mzima-client/package.json @@ -7,7 +7,7 @@ "dependencies": { "@capacitor-community/intercom": "^5.0.0", "@capacitor/android": "^5.6.0", - "@capacitor/app": "^5.6.0", + "@capacitor/app": "^5.0.6", "@capacitor/camera": "^5.0.8", "@capacitor/core": "^5.0.0", "@capacitor/device": "^5.0.6", From 69ae71f6f0a00c2566eae44bbddb745055f72e9a Mon Sep 17 00:00:00 2001 From: ushahidlee <152860706+ushahidlee@users.noreply.github.com> Date: Thu, 11 Apr 2024 17:24:19 +0200 Subject: [PATCH 09/13] Ush 1193 - Filters should behave (#1007) * Bug 1 - Unknown Form on mapview * Bug 2 Fix : include_unstructured_posts in mapview * brackets, whats not to love? * Bug 3 - Filters.... looking good * Removed unstructured posts default filter value * added unstructured posts check just before endpoint * Tweaks for edge cases * more tweaks * Cleaned up more filter logic. Added more type safety. * a few tweaks * tweaks * Fixed edge case bug * Fixed bug around select all/clear --- .../src/app/feed/feed.component.ts | 23 ++- .../src/app/map/map.component.ts | 4 +- .../shared/components/main-view.component.ts | 21 +- .../search-form/search-form.component.html | 30 +-- .../search-form/search-form.component.scss | 4 + .../search-form/search-form.component.ts | 36 ++-- libs/sdk/src/lib/models/posts.interface.ts | 3 + libs/sdk/src/lib/services/posts.service.ts | 194 ++++++++++-------- 8 files changed, 172 insertions(+), 143 deletions(-) diff --git a/apps/web-mzima-client/src/app/feed/feed.component.ts b/apps/web-mzima-client/src/app/feed/feed.component.ts index c4f25ca034..dc954ee610 100644 --- a/apps/web-mzima-client/src/app/feed/feed.component.ts +++ b/apps/web-mzima-client/src/app/feed/feed.component.ts @@ -38,19 +38,13 @@ enum FeedMode { export class FeedComponent extends MainViewComponent implements OnInit { @ViewChild('feed') public feed: ElementRef; @ViewChild('masonry') public masonry: NgxMasonryComponent; - public override params: GeoJsonFilter = { - limit: 20, - page: 1, - include_unstructured_posts: true, - // created_before_by_id: '', - }; private readonly getPostsSubject = new Subject<{ params: GeoJsonFilter; add?: boolean; }>(); public pagination = { - page: 1, - size: this.params.limit, + page: 0, + limit: 20, }; public postsSkeleton = new Array(20).fill(''); // used for Post mode's skeleton loader public posts: PostResult[] = []; @@ -117,7 +111,9 @@ export class FeedComponent extends MainViewComponent implements OnInit { sessionService, breakpointService, ); + this.checkDesktop(); + this.setupFeedDefaultFilters(); this.initGetPostsListener(); if (!this.isDesktop) { @@ -221,6 +217,17 @@ export class FeedComponent extends MainViewComponent implements OnInit { this.eventBusListeners(); } + private setupFeedDefaultFilters() { + if (this.params.include_unstructured_posts) this.params['form[]']?.push('0'); + this.params.currentView = 'feed'; + (this.params.limit = 20), + (this.params.page = 1), + (this.pagination = { + limit: this.params.limit, + page: this.params.page, + }); + } + private eventBusListeners() { this.eventBusService .on(EventType.DeleteCollection) diff --git a/apps/web-mzima-client/src/app/map/map.component.ts b/apps/web-mzima-client/src/app/map/map.component.ts index 3ea2519fb6..bba84fb826 100644 --- a/apps/web-mzima-client/src/app/map/map.component.ts +++ b/apps/web-mzima-client/src/app/map/map.component.ts @@ -80,6 +80,7 @@ export class MapComponent extends MainViewComponent implements OnInit { } ngOnInit() { + this.reInitParams(); this.route.params.subscribe(() => { this.initCollection(); }); @@ -130,7 +131,6 @@ export class MapComponent extends MainViewComponent implements OnInit { } loadData(): void { - this.reInitParams(); this.getPostsGeoJson(); } @@ -140,7 +140,6 @@ export class MapComponent extends MainViewComponent implements OnInit { if (this.route.snapshot.data['view'] === 'search' && !this.searchId) return; if (this.route.snapshot.data['view'] === 'collection' && !this.collectionId) return; - this.reInitParams(); this.getPostsGeoJson(); }, }); @@ -148,6 +147,7 @@ export class MapComponent extends MainViewComponent implements OnInit { private reInitParams() { this.params.page = 1; + this.params.currentView = 'map'; this.mapLayers.map((layer) => { this.map.removeLayer(layer); this.markerClusterData.removeLayer(layer); diff --git a/apps/web-mzima-client/src/app/shared/components/main-view.component.ts b/apps/web-mzima-client/src/app/shared/components/main-view.component.ts index d884fc94ad..42ed33bbc3 100644 --- a/apps/web-mzima-client/src/app/shared/components/main-view.component.ts +++ b/apps/web-mzima-client/src/app/shared/components/main-view.component.ts @@ -21,7 +21,6 @@ export abstract class MainViewComponent { limit: 500, offset: 0, }; - filters; public user: UserInterface; public isDesktop: boolean = false; @@ -34,7 +33,7 @@ export abstract class MainViewComponent { protected sessionService: SessionService, protected breakpointService: BreakpointService, ) { - this.filters = JSON.parse( + this.params = JSON.parse( localStorage.getItem(this.sessionService.getLocalStorageNameMapper('filters'))!, ); } @@ -46,7 +45,7 @@ export abstract class MainViewComponent { this.collectionId = this.route.snapshot.paramMap.get('id')!; this.params.set = this.collectionId; this.postsService.applyFilters({ - ...this.normalizeFilter(this.filters), + ...this.normalizeFilter(this.params), set: this.collectionId, }); this.searchId = ''; @@ -65,28 +64,24 @@ export abstract class MainViewComponent { } else { this.searchId = ''; this.postsService.applyFilters({ - ...this.normalizeFilter(this.filters), + ...this.normalizeFilter(this.params), set: [], }); } } } - private normalizeFilter(values: any) { + private normalizeFilter(values: GeoJsonFilter): GeoJsonFilter { if (!values) return {}; const filters = { ...values, - 'form[]': values.form, - 'source[]': values.source, - 'status[]': values.status, - 'tags[]': values.tags, + 'form[]': values['form[]'], + 'source[]': values['source[]'], + 'status[]': values['status[]'], + 'tags[]': values['tags[]'], }; - delete filters.form; - delete filters.source; - delete filters.status; - delete filters.tags; return filters; } diff --git a/apps/web-mzima-client/src/app/shared/components/search-form/search-form.component.html b/apps/web-mzima-client/src/app/shared/components/search-form/search-form.component.html index e40f2a4ce5..4543f2987a 100644 --- a/apps/web-mzima-client/src/app/shared/components/search-form/search-form.component.html +++ b/apps/web-mzima-client/src/app/shared/components/search-form/search-form.component.html @@ -156,20 +156,22 @@ formControlName="form" [data-qa]="'survey-selection-list'" > - - - {{ survey.name }} - {{ survey.total }} - + + + + {{ survey.name }} + {{ survey.total }} + +
diff --git a/apps/web-mzima-client/src/app/shared/components/search-form/search-form.component.scss b/apps/web-mzima-client/src/app/shared/components/search-form/search-form.component.scss index e3058f178c..50005a7907 100644 --- a/apps/web-mzima-client/src/app/shared/components/search-form/search-form.component.scss +++ b/apps/web-mzima-client/src/app/shared/components/search-form/search-form.component.scss @@ -486,6 +486,10 @@ line-height: 14px; } + &[hidden] { + display: none; + } + &:not(:first-child) { margin-top: 8px; } diff --git a/apps/web-mzima-client/src/app/shared/components/search-form/search-form.component.ts b/apps/web-mzima-client/src/app/shared/components/search-form/search-form.component.ts index 4f5135e946..09fe1de921 100644 --- a/apps/web-mzima-client/src/app/shared/components/search-form/search-form.component.ts +++ b/apps/web-mzima-client/src/app/shared/components/search-form/search-form.component.ts @@ -35,6 +35,7 @@ import { Savedsearch, SurveyItem, AccountNotificationsInterface, + GeoJsonFilter, } from '@mzima-client/sdk'; import dayjs from 'dayjs'; import { MatSnackBar } from '@angular/material/snack-bar'; @@ -105,12 +106,13 @@ export class SearchFormComponent extends BaseComponent implements OnInit { } ngOnInit(): void { - this.getUserData(); + this.isMapView = this.router.url.includes('/map'); this.eventBusInit(); this.getSavedFilters(); + this.initFilters(); this.getSurveys(); this.getCategories(); - this.initFilters(); + this.getUserData(); if (this.activeSaved) { this.activeSavedSearch = JSON.parse(this.activeSaved!); @@ -119,11 +121,10 @@ export class SearchFormComponent extends BaseComponent implements OnInit { this.checkSavedSearchNotifications(); } - // TODO: There should be a better way to check if the context is a map view - this.isMapView = this.router.url.includes('/map'); this.router.events.pipe(filter((event) => event instanceof NavigationStart)).subscribe({ next: (params: any) => { this.isMapView = params.url.includes('/map'); + this.applyFilters(false); }, }); @@ -321,37 +322,39 @@ export class SearchFormComponent extends BaseComponent implements OnInit { private getActiveFilters(values: any): void { // Check if values.form contains an item with id 0 let fetchPostsWithoutFormId = false; - if (Array.isArray(values.form)) { + if (!this.isMapView && Array.isArray(values.form)) { const index = values.form.findIndex((id: any) => id === 0); fetchPostsWithoutFormId = index !== -1; } - const filters: any = { + const filters: GeoJsonFilter = { 'source[]': values.source, 'status[]': values.status, 'form[]': values.form, 'tags[]': values.tags, + currentView: this.isMapView ? 'map' : 'feed', include_unstructured_posts: fetchPostsWithoutFormId, set: values.set, - date_after: values.date.start ? dayjs(values.date.start).toISOString() : null, + date_after: values.date.start ? dayjs(values.date.start).toISOString() : undefined, date_before: values.date.end ? dayjs(values.date.end) .endOf('day') .add(dayjs(values.date.end).utcOffset(), 'minute') .toISOString() - : null, + : undefined, q: this.searchQuery, center_point: values.center_point?.location?.lat && values.center_point?.location?.lng ? [values.center_point.location.lat, values.center_point.location.lng].join(',') - : null, + : undefined, within_km: values.center_point.distance, }; this.activeFilters = {}; for (const key in filters) { - if (!filters[key] && !filters[key]?.length) continue; - this.activeFilters[key] = filters[key]; + const val = filters[key as keyof typeof filters]; + if (val === undefined) continue; + this.activeFilters[key] = val; } } @@ -425,7 +428,7 @@ export class SearchFormComponent extends BaseComponent implements OnInit { forkJoin([ this.surveysService.get('', { show_unknown_form: true }), - this.postsService.getPostStatistics(null, this.isMapView), + this.getPostsStatistic(), ]).subscribe({ next: (responses) => { const values = responses[1].result.group_by_total_posts; @@ -490,7 +493,7 @@ export class SearchFormComponent extends BaseComponent implements OnInit { } public getPostsStatistic(): Observable { - return this.postsService.getPostStatistics(null, this.isMapView).pipe( + return this.postsService.getPostStatistics(this.activeFilters).pipe( map((res) => { this.notMappedPostsCount = res.result.unmapped; const values = res.result.group_by_total_posts; @@ -670,9 +673,12 @@ export class SearchFormComponent extends BaseComponent implements OnInit { } public resetForm(filters: any = {}): void { + let fetchPostsWithoutFormId = false; // Check if this.surveyList contains an item with id 0 - const index = this.surveyList.findIndex((s) => s.id === 0); - const fetchPostsWithoutFormId = index !== -1; + if (!this.isMapView) { + const index = this.surveyList.findIndex((s) => s.id === 0); + fetchPostsWithoutFormId = index !== -1; + } this.form.patchValue({ query: '', diff --git a/libs/sdk/src/lib/models/posts.interface.ts b/libs/sdk/src/lib/models/posts.interface.ts index b11c46b283..9e001826a0 100644 --- a/libs/sdk/src/lib/models/posts.interface.ts +++ b/libs/sdk/src/lib/models/posts.interface.ts @@ -22,6 +22,7 @@ export interface GeoJsonItem { export interface GeoJsonFilter { has_location?: string; + currentView?: 'map' | 'feed'; limit?: number; offset?: number; order?: 'desc' | 'asc'; @@ -37,6 +38,8 @@ export interface GeoJsonFilter { 'status[]'?: string[]; 'form[]'?: string[]; created_before_by_id?: string; + center_point?: string; + within_km?: string; q?: string; page?: number; } diff --git a/libs/sdk/src/lib/services/posts.service.ts b/libs/sdk/src/lib/services/posts.service.ts index deb2bd88c9..5a5da6a3e3 100644 --- a/libs/sdk/src/lib/services/posts.service.ts +++ b/libs/sdk/src/lib/services/posts.service.ts @@ -25,13 +25,12 @@ export class PostsService extends ResourceService { set: '', order_unlocked_on_top: true, reactToFilters: true, - include_unstructured_posts: true, 'source[]': [], 'tags[]': [], 'form[]': [], 'status[]': [], }; - private postsFilters = new BehaviorSubject(this.defaultPostsFilters); + private postsFilters = new BehaviorSubject(this.defaultPostsFilters); public postsFilters$ = this.postsFilters.asObservable(); private totalPosts = new Subject(); public totalPosts$ = this.totalPosts.asObservable(); @@ -114,11 +113,16 @@ export class PostsService extends ResourceService { return super.delete(id); } + public searchPosts(url: string, query?: string, params?: any): Observable { + return super.get(url, { has_location: 'all', q: query, ...params }).pipe( + tap((response) => { + this.totalPosts.next(response.meta.total); + }), + ); + } + getGeojson(filter?: GeoJsonFilter): Observable { - const tmpParams = { ...this.postsFilters.value, has_location: 'mapped', ...filter }; - delete tmpParams.order; - delete tmpParams.orderby; - return super.get('geojson', this.postParamsMapper(tmpParams)).pipe( + return super.get('geojson', this.postParamsMapper(filter, { ...this.postsFilters.value })).pipe( tap((res) => { this.totalGeoPosts.next(res.meta.total); }), @@ -126,29 +130,29 @@ export class PostsService extends ResourceService { } public getPosts(url: string, filter?: GeoJsonFilter): Observable { - const tmpParams = { ...this.postsFilters.value, has_location: 'all', ...filter }; - return super.get(url, this.postParamsMapper(tmpParams)).pipe( - map((response) => { - response.results.map((post: PostResult) => { - post.source = - post.source === 'sms' - ? 'SMS' - : post.source - ? post.source.charAt(0).toUpperCase() + post.source.slice(1) - : 'Web'; - }); - - return response; - }), - tap((response) => { - this.totalPosts.next(response.meta.total); - }), - ); + return super + .get(url, this.postParamsMapper({ ...this.postsFilters.value, has_location: 'all' }, filter)) + .pipe( + map((response) => { + response.results.map((post: PostResult) => { + post.source = + post.source === 'sms' + ? 'SMS' + : post.source + ? post.source.charAt(0).toUpperCase() + post.source.slice(1) + : 'Web'; + }); + + return response; + }), + tap((response) => { + this.totalPosts.next(response.meta.total); + }), + ); } public getMyPosts(url: string, filter?: GeoJsonFilter): Observable { - const tmpParams = { has_location: 'all', user: 'me', ...filter }; - return super.get(url, this.postParamsMapper(tmpParams)).pipe( + return super.get(url, this.postParamsMapper({ has_location: 'all', user: 'me' }, filter)).pipe( map((response) => { response.results.map((post: PostResult) => { post.source = @@ -164,92 +168,99 @@ export class PostsService extends ResourceService { ); } - public searchPosts(url: string, query?: string, params?: any): Observable { - return super.get(url, { has_location: 'all', q: query, ...params }).pipe( - tap((response) => { - this.totalPosts.next(response.meta.total); - }), - ); + public getPostStatistics(queryParams?: any): Observable { + const params = { ...queryParams, group_by: 'form', enable_group_by_source: true }; + const filters = this.postParamsMapper(params, this.postsFilters.value); + + return super.get('stats', filters); } - private postParamsMapper(params: any) { - // TODO: REWORK THIS!! Created to make current API work as expected - if (params.date?.start) { - params.date_after = params.date.start; - if (params.date.end) { - params.date_before = params.date.end; + private postParamsMapper(params: any, filter?: GeoJsonFilter) { + // Combine new parameters with existing filter + const postParams: any = { ...filter, ...params }; + postParams.currentView = filter?.currentView; + + // Allocate start and end dates, and remove originals + if (postParams.date?.start) { + postParams.date_after = postParams.date.start; + if (postParams.date.end) { + postParams.date_before = postParams.date.end; } - delete params.date; + delete postParams.date; } else { - delete params.date; + delete postParams.date; } - if (params.center_point?.location?.lat) { - params.within_km = params.center_point.distance; - params.center_point = `${params.center_point.location.lat},${params.center_point.location.lng}`; - } else if (!params.center_point?.length) { - delete params.center_point; + // Re-allocate location information + if (postParams.center_point?.location?.lat) { + postParams.within_km = postParams.center_point.distance; + postParams.center_point = `${postParams.center_point.location.lat},${postParams.center_point.location.lng}`; + } else if (!postParams.center_point?.length) { + delete postParams.center_point; } - if (!params.set) { - delete params.set; + if (postParams.status?.length) { + postParams['status[]'] = postParams.status; + } + if (postParams.source?.length) { + postParams['source[]'] = postParams.source; + } + if (postParams.tags?.length) { + postParams['tags[]'] = postParams.tags; } - if (params.form && params.form.length === 0) { - params.form.push('none'); + // Clean up new params based on which view is currently active + if (postParams.currentView === 'map') { + postParams.has_location = 'mapped'; + postParams.include_unstructured_posts = false; + delete postParams.order; + delete postParams.orderby; + } else if (postParams.currentView === 'feed') { + postParams.include_unmapped = true; + if (postParams['form[]'].includes(0)) { + postParams.include_unstructured_posts = true; + } else { + postParams.include_unstructured_posts = false; + } + delete postParams.has_location; + delete postParams.within_km; + delete postParams.place; } - if (params.form?.length) { - params['form[]'] = params.form; - delete params.form; + // Clean up whatevers left, removing empty arrays and values + postParams['form[]'] = postParams['form[]'].filter((formId: any) => formId !== 0); + if (postParams['form[]']?.length === 0 || postParams['form[]'][0] === 'none') { + delete postParams['form[]']; } - if (params['form[]']?.length === 0) { - params['form[]'].push('none'); + if (postParams['source[]']?.length === 0) { + delete postParams['source[]']; } - if (params['source[]']?.length === 0) { - params['source[]'].push('none'); + if (postParams['status[]']?.length === 0) { + delete postParams['status[]']; } - if (params.status?.length) { - params['status[]'] = params.status; - delete params.status; + if (postParams['tags[]']?.length === 0) { + delete postParams['tags[]']; } - if (params.source?.length) { - params['source[]'] = params.source; - delete params.source; + if (postParams.set?.length === 0) { + delete postParams.set; } - - if (params.tags?.length) { - params['tags[]'] = params.tags; - delete params.tags; + if (postParams.query?.length === 0) { + delete postParams.query; + } + if (postParams.place?.length === 0) { + delete postParams.place; } - return params; - } - public getPostStatistics( - queryParams?: any, - isMapView: boolean = false, - ): Observable { - const filters = { ...this.postsFilters.value }; + delete postParams.currentView; + delete postParams.source; + delete postParams.tags; + delete postParams.status; + delete postParams.form; - delete filters.form; - delete filters['form[]']; - delete filters.source; - delete filters['source[]']; - - return super.get( - 'stats', - queryParams ?? { - ...this.postParamsMapper(filters), - group_by: 'form', - enable_group_by_source: true, - has_location: isMapView ? 'mapped' : 'all', - include_unmapped: true, - include_unstructured_posts: true, - }, - ); + return postParams; } public lockPost(id: string | number) { @@ -263,8 +274,9 @@ export class PostsService extends ResourceService { public applyFilters(filters: any, updated = true): void { const newFilters: any = {}; for (const key in filters) { - if (filters[key] || this.postsFilters.value[key]) { - newFilters[key] = filters[key] || this.postsFilters.value[key]; + const postsFilterValue = this.postsFilters.value[key as keyof typeof this.postsFilters.value]; + if (filters[key] !== undefined || postsFilterValue) { + newFilters[key] = filters[key] !== undefined ? filters[key] : postsFilterValue; } } if (updated) { From 5e4100c82218e8e63f5a5047d90093e5904d6919 Mon Sep 17 00:00:00 2001 From: ushahidlee <152860706+ushahidlee@users.noreply.github.com> Date: Mon, 15 Apr 2024 09:04:33 +0200 Subject: [PATCH 10/13] Ush 1220 - After initial load, the mobile app does not switch to a different tile layer on the map (#1009) * first cut * Removed all the logging and fixed the bug * added a sanity check --- .../components/map-view/map-view.component.ts | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/apps/mobile-mzima-client/src/app/map/components/map-view/map-view.component.ts b/apps/mobile-mzima-client/src/app/map/components/map-view/map-view.component.ts index 849d903989..d2116fc2e0 100644 --- a/apps/mobile-mzima-client/src/app/map/components/map-view/map-view.component.ts +++ b/apps/mobile-mzima-client/src/app/map/components/map-view/map-view.component.ts @@ -73,22 +73,37 @@ export class MapViewComponent implements AfterViewInit { this.sessionService.mapConfig$.subscribe({ next: (mapConfig) => { if (mapConfig) { - this.mapConfig = mapConfig; - - this.baseLayer = this.mapConfig.default_view!.baselayer; - const currentLayer = mapHelper.getMapLayer(this.baseLayer, this.isDarkMode); - this.offlineLayer = tileLayerOffline(currentLayer.url, currentLayer.layerOptions); - this.onlineLayer = tileLayer(currentLayer.url, currentLayer.layerOptions); - - this.leafletOptions = { - minZoom: 4, - maxZoom: 17, - scrollWheelZoom: true, - zoomControl: false, - layers: [this.offlineLayer, this.onlineLayer], - center: [this.mapConfig.default_view!.lat, this.mapConfig.default_view!.lon], - zoom: this.mapConfig.default_view!.zoom, - }; + // The map can only be configured using leafletOptions on creation... + if (!this.mapConfig && this.mapLayers.length === 0) { + this.mapConfig = mapConfig; + this.baseLayer = this.mapConfig.default_view!.baselayer; + const currentLayer = mapHelper.getMapLayer(this.baseLayer, this.isDarkMode); + this.offlineLayer = tileLayerOffline(currentLayer.url, currentLayer.layerOptions); + this.onlineLayer = tileLayer(currentLayer.url, currentLayer.layerOptions); + this.leafletOptions = { + minZoom: 4, + maxZoom: 17, + scrollWheelZoom: true, + zoomControl: false, + layers: [this.offlineLayer, this.onlineLayer], + center: [this.mapConfig.default_view!.lat, this.mapConfig.default_view!.lon], + zoom: this.mapConfig.default_view!.zoom, + }; + } else { + // So if the baseLayer has changed and its an already created map, we need to manipulate the layers manually + if (this.mapConfig.default_view!.baselayer !== mapConfig.default_view.baselayer) { + this.map.eachLayer((layer) => { + if (layer instanceof TileLayer) this.map.removeLayer(layer); + }); + this.mapConfig = mapConfig; + this.baseLayer = this.mapConfig.default_view!.baselayer; + const currentLayer = mapHelper.getMapLayer(this.baseLayer, this.isDarkMode); + this.offlineLayer = tileLayerOffline(currentLayer.url, currentLayer.layerOptions); + this.onlineLayer = tileLayer(currentLayer.url, currentLayer.layerOptions); + this.map.addLayer(this.offlineLayer); + this.map.addLayer(this.onlineLayer); + } + } this.markerClusterOptions.maxClusterRadius = this.mapConfig.cluster_radius; } }, From 1476dc6b6685053b573d911d8ebc0f82607b6a92 Mon Sep 17 00:00:00 2001 From: Shakira Ndagire Seruwagi Date: Mon, 15 Apr 2024 10:24:20 +0300 Subject: [PATCH 11/13] Remove the reloads from the settings function (#998) Co-authored-by: Shakira Co-authored-by: AmTryingMyBest <38259840+AmTryingMyBest@users.noreply.github.com> --- e2e-testing/cypress/functions/GeneralSettingsFunctions.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/e2e-testing/cypress/functions/GeneralSettingsFunctions.js b/e2e-testing/cypress/functions/GeneralSettingsFunctions.js index 4c46e5dcdb..15baded2d4 100644 --- a/e2e-testing/cypress/functions/GeneralSettingsFunctions.js +++ b/e2e-testing/cypress/functions/GeneralSettingsFunctions.js @@ -79,7 +79,6 @@ class GeneralSettingsFunctions { steps_to_generate_new_api_key() { this.generate_new_api_key(); - cy.reload(); this.verify_api_field_should_have_value(); this.click_save_button(); } @@ -89,7 +88,6 @@ class GeneralSettingsFunctions { this.type_deployment_name('-Automated'); this.type_site_description('Fixtures are a great way to mock data for responses to routes'); this.click_save_button(); - cy.reload(); this.verify_deployment_changes_reflect('-Automated'); } } From 35bc7619246b4e8a621af7c621d0268bf8b99504 Mon Sep 17 00:00:00 2001 From: Maxwell Mwandigha <47903907+MMwandigha@users.noreply.github.com> Date: Mon, 15 Apr 2024 15:48:03 +0300 Subject: [PATCH 12/13] [USH-1248] Improving the search for existing Collections (#994) * Functionality that allows collections to load before pressing return * Functionality that allows collections to load before pressing the return key * Reverting the env.json file --------- Co-authored-by: Mh-Asmi --- .../components/collections/collections.component.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/web-mzima-client/src/app/shared/components/collections/collections.component.ts b/apps/web-mzima-client/src/app/shared/components/collections/collections.component.ts index 2477e891ff..0ae9f63341 100644 --- a/apps/web-mzima-client/src/app/shared/components/collections/collections.component.ts +++ b/apps/web-mzima-client/src/app/shared/components/collections/collections.component.ts @@ -6,6 +6,7 @@ import { surveyHelper, formHelper } from '@helpers'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { TranslateService } from '@ngx-translate/core'; import { SessionService, BreakpointService, EventBusService, EventType } from '@services'; +import { debounceTime } from 'rxjs'; import { CollectionsService, NotificationsService, @@ -75,6 +76,7 @@ export class CollectionsComponent extends BaseComponent implements OnInit { this.initializeForm(); this.formSubscribe(); this.checkPermission(); + this.loadSearchCollections(); const permissions = localStorage.getItem('USH_permissions')!; this.featuredEnabled = permissions ? permissions.split(',').includes('Manage Posts') : false; @@ -90,6 +92,15 @@ export class CollectionsComponent extends BaseComponent implements OnInit { } } + private loadSearchCollections() { + this.searchForm + .get('query') + ?.valueChanges.pipe(debounceTime(500), untilDestroyed(this)) + .subscribe((query: string) => { + this.loadData(query); + }); + } + private initializeForm() { this.collectionForm = this.formBuilder.group({ name: ['', [Validators.required]], From 714109c9b8c5daa5917c8d29853987614736f450 Mon Sep 17 00:00:00 2001 From: AmTryingMyBest <38259840+AmTryingMyBest@users.noreply.github.com> Date: Tue, 16 Apr 2024 11:01:52 +0300 Subject: [PATCH 13/13] add instruction step for running tests locally (#1016) --- e2e-testing/README.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/e2e-testing/README.md b/e2e-testing/README.md index c98a8253a2..a4ecf852f0 100644 --- a/e2e-testing/README.md +++ b/e2e-testing/README.md @@ -23,37 +23,54 @@ Each file is put into its own respectively named folder, and each folder prefixe Each main test file has two associated files: ### Locator file + Named to match the associated test file. Most elements on the platform have custom attributes added to them in the format `data-qa="element_name"` This is then declared in the locators file and referred to from the test files. Locators folder has files that have custom element selectors. Locator files sit here. ### Functions file + Named to match the associated test file. Functions file has the core functions for the tests. The main test file calls from from the functions file located in the Functions folder. ## Getting Set Up ### Installation + Requires node v18+ ```bash npm install -``` -will install the current cypress version the tests are written and running in. +``` + +will install the current cypress version the tests are written and running in. Check out [Cypress.io](cypress.io) for more information ### Running the tests + Once cypress is set up and running, simply open the runner and click on individual tests to run. Tests can be run in headless mode in the terminal using the default command: +PS: In the CI environment when the platform is launched for the tests to run, it is launched in a non-english language and the tests run against a non-english platform. This doesn't happen locally. We needed the platform to run in English. To make this happen, we added a step right after login that changes the language to English. +For the tests to run locally correctly, remove the step that changes language to English. +Comment out this line to do this: LoginFunctions, line 7 + +```bash +this.change_language() +``` + +Be sure to change this back before creating a Pull Request, or else the tests will break. + ```bash npx cypress run ``` To launch the cypress test runner, use the command: + ```bash npm run cy:open ``` + This will launch the test runner laying out available tests, and individual tests can be run on a browser on a visual interface. The site under test runs on a remote staging server - https://mzima.staging.ush.zone/