From 363dc496367f94c685e21a4c2ba6d5bb546cac4a Mon Sep 17 00:00:00 2001 From: Jason Kulatunga Date: Wed, 10 May 2023 19:34:57 -0700 Subject: [PATCH] working callbacks, working search filtering/facets --- Makefile | 14 +++ .../medical-sources.component.ts | 56 ++++----- .../medical-sources-filter.service.ts | 107 +++++++++++++++--- 3 files changed, 134 insertions(+), 43 deletions(-) diff --git a/Makefile b/Makefile index 3d9ebfe9..3930b03c 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,20 @@ .PHONY: test test: backend-test frontend-test +.PHONY: serve-frontend +serve-web: + cd frontend + yarn dist -- -c sandbox + +.PHONY: serve-frontend-prod +serve-web: + cd frontend + yarn dist -- -c prod + +.PHONY: serve-backend +serve-web: + go run backend/cmd/fasten/fasten.go start --config ./config.dev.yaml --debug + ######################################################################################################################## # Backend diff --git a/frontend/src/app/pages/medical-sources/medical-sources.component.ts b/frontend/src/app/pages/medical-sources/medical-sources.component.ts index 253148b6..7da8d926 100644 --- a/frontend/src/app/pages/medical-sources/medical-sources.component.ts +++ b/frontend/src/app/pages/medical-sources/medical-sources.component.ts @@ -7,15 +7,16 @@ import {MetadataSource} from '../../models/fasten/metadata-source'; import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; import {ActivatedRoute} from '@angular/router'; import {environment} from '../../../environments/environment'; -import {BehaviorSubject, forkJoin, Observable, Subject} from 'rxjs'; +import {BehaviorSubject, forkJoin, Observable, of, Subject} from 'rxjs'; import { LighthouseSourceSearch, LighthouseSourceSearchAggregation, LighthouseSourceSearchResult } from '../../models/lighthouse/lighthouse-source-search'; -import {debounceTime, distinctUntilChanged} from 'rxjs/operators'; +import {debounceTime, distinctUntilChanged, pairwise, startWith} from 'rxjs/operators'; import {MedicalSourcesFilter, MedicalSourcesFilterService} from '../../services/medical-sources-filter.service'; import {FormControl, FormGroup} from '@angular/forms'; +import * as _ from 'lodash'; // If you dont import this angular will import the wrong "Location" export const sourceConnectWindowTimeout = 24*5000 //wait 2 minutes (5 * 24 = 120) @@ -78,7 +79,22 @@ export class MedicalSourcesComponent implements OnInit { private activatedRoute: ActivatedRoute, private filterService: MedicalSourcesFilterService, private modalService: NgbModal, - ) { } + ) { + this.filterService.filterChanges.subscribe((filterInfo) => { + + console.log("medical-sources - filterChanges", filterInfo) + + //this function should only trigger when there's a change to the filter values -- which requires a new query + this.availableSourceList = [] + this.resultLimits.totalItems = 0 + this.resultLimits.scrollComplete = false + + //update the form with data from route (don't emit a new patch event), then submit query + this.querySources(filterInfo?.filter).subscribe(null, null, () => { + console.log("querySources() complete") + }) + }) + } ngOnInit(): void { @@ -92,7 +108,6 @@ export class MedicalSourcesComponent implements OnInit { if(inProgressAvailableIndex > -1){ let sourcesInProgress = this.availableSourceList.splice(inProgressAvailableIndex, 1); } - } //we're not in a callback redirect, lets load the sources if(this.activatedRoute.snapshot.queryParams['query']){ @@ -100,26 +115,6 @@ export class MedicalSourcesComponent implements OnInit { } - //changing the route should trigger a query - this.activatedRoute.queryParams - .subscribe(params => { - console.log("QUERY PARAMS CHANGED ON ROUTE", params); // {order: "popular"} - var updatedForm = this.filterService.parseQueryParams(params); - - //this is a "breaking change" to the filter values, causing a reset and a new query - this.availableSourceList = [] - this.resultLimits.totalItems = 0 - this.resultLimits.scrollComplete = false - this.filterService.resetControl("categories") - // this.filterService.filterForm.setControl("categories", this.{: {}}, { emitEvent: false}) - - //update the form with data from route (don't emit a new patch event), then submit query - var searchObservable = this.querySources(this.filterService.toMedicalSourcesFilter(updatedForm)); - searchObservable.subscribe(null, null, () => { - this.filterForm.patchValue(updatedForm, { emitEvent: false}); - }) - }); - //register a callback for when the search box content changes this.searchTermUpdate .pipe( @@ -137,8 +132,10 @@ export class MedicalSourcesComponent implements OnInit { } private querySources(filter?: MedicalSourcesFilter): Observable { + console.log("querySources()", filter) if(this.loading){ - return + console.log("already loading, ignoring querySources()") + return of(null) } //TODO: pass filter to function. // this.location.replaceState('/dashboard','', this.filter) @@ -161,11 +158,12 @@ export class MedicalSourcesComponent implements OnInit { return {metadata: result._source} })) - //change the current Page (but don't cause a new query) - if(!wrapper?.hits || !wrapper?.hits || wrapper?.hits?.hits?.length == 0){ + //check if scroll is complete. + if(!wrapper?.hits || !wrapper?.hits?.hits || wrapper?.hits?.hits?.length == 0 || wrapper?.hits?.total?.value == wrapper?.hits?.hits?.length){ console.log("SCROLL_COMPLETE!@@@@@@@@") this.resultLimits.scrollComplete = true; } else { + //change the current Page (but don't cause a new query) console.log("SETTING NEXT SORT KEY:", wrapper.hits.hits[wrapper.hits.hits.length - 1].sort.join(',')) this.filterService.filterForm.patchValue({searchAfter: wrapper.hits.hits[wrapper.hits.hits.length - 1].sort.join(",")}, {emitEvent: false}) } @@ -218,7 +216,9 @@ export class MedicalSourcesComponent implements OnInit { public onScroll(): void { - this.querySources() + if(!this.resultLimits.scrollComplete) { + this.querySources() + } } //OLD FUNCTIONS diff --git a/frontend/src/app/services/medical-sources-filter.service.ts b/frontend/src/app/services/medical-sources-filter.service.ts index 125f7d5e..58527ebd 100644 --- a/frontend/src/app/services/medical-sources-filter.service.ts +++ b/frontend/src/app/services/medical-sources-filter.service.ts @@ -1,16 +1,23 @@ import { Injectable } from '@angular/core'; import {FormBuilder, FormControl, FormGroup} from '@angular/forms'; -import {debounceTime} from 'rxjs/operators'; +import {debounceTime, pairwise, startWith} from 'rxjs/operators'; import {Router} from '@angular/router'; +import * as _ from 'lodash'; +import {BehaviorSubject} from 'rxjs'; export class MedicalSourcesFilter { - searchAfter: string | string[] = ''; + //primary search terms (changes here should restart the search completely) query: string; + + //secondary search terms/facets (changes here should restart pagination) platformTypes: string[] = []; categories: string[] = []; showHidden: boolean = false; + //pagination - this is the current page (changes here should be ignored) + searchAfter: string | string[] = ''; + fields: string[] = []; //specify the fields to return. if null or empty list, return all. } @@ -27,24 +34,90 @@ export class MedicalSourcesFilterService { showHidden: [false], }) + filterChanges = new BehaviorSubject<{filter: MedicalSourcesFilter, changed:string[] }>(null); + constructor( private formBuilder: FormBuilder, - private router: Router, + // private router: Router, ) { - //changing the form, should change the URL, BUT NOT do a query - this.filterForm.valueChanges.pipe(debounceTime(100)).subscribe(val => { - console.log("FILTER FORM CHANGED:", val, this.toQueryParams()) + // //changing the form, should change the URL, BUT NOT do a query + // this.filterForm.valueChanges.pipe(debounceTime(100)).subscribe(val => { + // console.log("FILTER FORM CHANGED:", val, this.toQueryParams()) + // + // // change the browser url whenever the filter is updated. + // this.updateBrowserUrl(this.toQueryParams()) + // }) - // change the browser url whenever the filter is updated. - this.updateBrowserUrl(this.toQueryParams()) - }) + + this.filterForm.valueChanges + //see https://stackoverflow.com/questions/44898010/form-control-valuechanges-gives-the-previous-value + .pipe(startWith(null), pairwise()) + .subscribe(pairParams => { + let prevParams = this.toMedicalSourcesFilter(pairParams[0]) + let nextParams = this.toMedicalSourcesFilter(pairParams[1]) + let changed = []; + + //check if primary has changed + if(!_.isEqual(prevParams.query, nextParams.query)){ + changed.push('query') + this.resetSecondary(); + nextParams.platformTypes = []; + nextParams.categories = []; + nextParams.showHidden = false; + + this.resetPagination(); + nextParams.searchAfter = '' + + //check if secondary/facets have changed + } else if (!_.isEqual(prevParams.platformTypes, nextParams.platformTypes) + || !_.isEqual(prevParams.categories, nextParams.categories) + || !_.isEqual(prevParams.showHidden, nextParams.showHidden) + ){ + if(!_.isEqual(prevParams.platformTypes, nextParams.platformTypes)){ + changed.push('platformTypes') + } + if(!_.isEqual(prevParams.categories, nextParams.categories)){ + changed.push('categories') + } + if(!_.isEqual(prevParams.showHidden, nextParams.showHidden)){ + changed.push('showHidden') + } + this.resetPagination(); + nextParams.searchAfter = '' + } + + //emit the changes + if (changed.length > 0){ + console.log("FILTER FORM - CHANGED:", nextParams, changed) + + this.filterChanges.next({ + filter: nextParams, + changed: changed + }) + } else { + console.log("FILTER FORM - NO CHANGES:", nextParams) + } + }) } - updateBrowserUrl(queryParams: {[name: string]: string}){ - console.log("update the browser url with query params data", queryParams) - this.router.navigate(['/sources'], { queryParams: queryParams }) + resetPrimary(emit: boolean = false){ + this.filterForm.get('query').reset(undefined, {emitEvent: emit}); } + resetSecondary(emit: boolean = false){ + this.filterForm.get('platformTypes').reset(undefined, {emitEvent: emit}); + this.filterForm.get('categories').reset(undefined, {emitEvent: emit}); + this.filterForm.get('showHidden').reset(undefined, {emitEvent: emit}); + } + resetPagination(emit: boolean = false){ + this.filterForm.get('searchAfter').reset(undefined, {emitEvent: emit}); + } + + // + // updateBrowserUrl(queryParams: {[name: string]: string}){ + // console.log("update the browser url with query params data", queryParams) + // this.router.navigate(['/sources'], { queryParams: queryParams }) + // } resetControl(controlName: string){ this.filterForm.get(controlName).reset(); @@ -105,8 +178,10 @@ export class MedicalSourcesFilterService { return updateData; } - toQueryParams() : {[name:string]:string} { - var form = this.filterForm.value; + toQueryParams(form) : {[name:string]:string} { + if(!form){ + form = this.filterForm.value; + } var queryParams = {}; @@ -156,7 +231,9 @@ export class MedicalSourcesFilterService { toMedicalSourcesFilter(form): MedicalSourcesFilter { var medicalSourcesFilter = new MedicalSourcesFilter(); - + if(!form){ + return medicalSourcesFilter + } if(form.searchAfter){ medicalSourcesFilter.searchAfter = form.searchAfter }