From 399a6ffcaf2f1f665408cb1beeb26edc79ff8759 Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Tue, 24 May 2022 12:06:50 +0200 Subject: [PATCH] feat: progressive loading of regional features feat: add lodaing spinner for rich region panel feat: added error handling in priority http interceptors chore: added plugin breaking changes chore: added docker build latest api url --- .github/workflows/docker_img.yml | 2 +- docs/releases/v2.7.0.md | 6 ++- src/atlasComponents/sapi/core/sapiRegion.ts | 21 +++++---- src/atlasComponents/sapi/sapi.service.ts | 3 +- .../region/region.features.directive.ts | 20 ++++----- .../region/rich/region.rich.component.ts | 3 +- src/environments/environment.common.ts | 2 +- src/util/priority.ts | 43 ++++++++++++++++--- .../viewerCmp/viewerCmp.template.html | 5 +++ 9 files changed, 72 insertions(+), 33 deletions(-) diff --git a/.github/workflows/docker_img.yml b/.github/workflows/docker_img.yml index 21cdd1dd5..d7c2b266a 100644 --- a/.github/workflows/docker_img.yml +++ b/.github/workflows/docker_img.yml @@ -20,7 +20,7 @@ jobs: SIIBRA_API_STABLE: 'https://siibra-api-stable.apps.hbp.eu/v1_0' SIIBRA_API_RC: 'https://siibra-api-rc.apps.hbp.eu/v1_0' - SIIBRA_API_LATEST: 'https://siibra-api-latest.apps-dev.hbp.eu/v1_0' + SIIBRA_API_LATEST: 'https://siibra-api-latest.apps-dev.hbp.eu/v2_0' steps: diff --git a/docs/releases/v2.7.0.md b/docs/releases/v2.7.0.md index 2367751d9..1ce6c8632 100644 --- a/docs/releases/v2.7.0.md +++ b/docs/releases/v2.7.0.md @@ -1,9 +1,13 @@ # v2.7.0 +## Breaking changes + +- plugin in interactive atlas viewer has been completely redesigned + ## New feature - added storybook for component development -- (experimental) Add first implementation of fetching VOI from siibra-api +- Add first implementation of fetching VOI from siibra-api ## Under the hood diff --git a/src/atlasComponents/sapi/core/sapiRegion.ts b/src/atlasComponents/sapi/core/sapiRegion.ts index 1787145e8..8248992c1 100644 --- a/src/atlasComponents/sapi/core/sapiRegion.ts +++ b/src/atlasComponents/sapi/core/sapiRegion.ts @@ -1,8 +1,8 @@ import { SAPI } from ".."; import { SapiRegionalFeatureModel, SapiRegionMapInfoModel, SapiRegionModel, cleanIeegSessionDatasets, SapiIeegSessionModel, CleanedIeegDataset, SapiVolumeModel, PaginatedResponse } from "../type"; import { strToRgb, hexToRgb } from 'common/util' -import { forkJoin, Observable, of } from "rxjs"; -import { catchError, map } from "rxjs/operators"; +import { merge, Observable, of } from "rxjs"; +import { catchError, map, scan } from "rxjs/operators"; export class SAPIRegion{ @@ -28,19 +28,20 @@ export class SAPIRegion{ } getFeatures(spaceId: string): Observable<(SapiRegionalFeatureModel | CleanedIeegDataset)[]> { - return forkJoin({ - regionalFeatures: this.sapi.httpGet<SapiRegionalFeatureModel[]>( + return merge( + this.sapi.httpGet<SapiRegionalFeatureModel[]>( `${this.prefix}/features`, { space_id: spaceId } ).pipe( - catchError((err, obs) => of([])) + catchError((err, obs) => { + return of([]) + }) ), - spatialFeatures: spaceId + spaceId ? this.sapi.getSpace(this.atlasId, spaceId).getFeatures({ parcellationId: this.parcId, region: this.id }).pipe( catchError((err, obs) => { - console.log('error caught') return of([]) }), map(feats => { @@ -49,10 +50,8 @@ export class SAPIRegion{ }), ) : of([] as CleanedIeegDataset[]) - }).pipe( - map(({ regionalFeatures, spatialFeatures }) => { - return [...spatialFeatures, ...regionalFeatures] - }) + ).pipe( + scan((acc, curr) => [...acc, ...curr], []) ) } diff --git a/src/atlasComponents/sapi/sapi.service.ts b/src/atlasComponents/sapi/sapi.service.ts index 198b9e304..ae43869a3 100644 --- a/src/atlasComponents/sapi/sapi.service.ts +++ b/src/atlasComponents/sapi/sapi.service.ts @@ -21,6 +21,7 @@ import { EnumColorMapName } from "src/util/colorMaps"; import { PRIORITY_HEADER } from "src/util/priority"; import { Observable } from "rxjs"; import { SAPIFeature } from "./features"; +import { environment } from "src/environments/environment" export const SIIBRA_API_VERSION_HEADER_KEY='x-siibra-api-version' export const SIIBRA_API_VERSION = '0.2.0' @@ -29,7 +30,7 @@ type RegistryType = SAPIAtlas | SAPISpace | SAPIParcellation @Injectable() export class SAPI{ - static bsEndpoint = `https://siibra-api-dev.apps-dev.hbp.eu/v2_0` + static bsEndpoint = `https://siibra-api-latest.apps-dev.hbp.eu/v2_0` || environment.BS_REST_URL public bsEndpoint = SAPI.bsEndpoint diff --git a/src/atlasComponents/sapiViews/core/region/region/region.features.directive.ts b/src/atlasComponents/sapiViews/core/region/region/region.features.directive.ts index 9c3cad17f..47a4b6912 100644 --- a/src/atlasComponents/sapiViews/core/region/region/region.features.directive.ts +++ b/src/atlasComponents/sapiViews/core/region/region/region.features.directive.ts @@ -1,6 +1,6 @@ import { Directive, OnChanges, SimpleChanges } from "@angular/core"; -import { BehaviorSubject, merge, Observable } from "rxjs"; -import { switchMap, filter, startWith, shareReplay, mapTo, delay, tap } from "rxjs/operators"; +import { BehaviorSubject, Observable } from "rxjs"; +import { switchMap, filter, startWith, shareReplay, finalize } from "rxjs/operators"; import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionalFeatureModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi"; import { SxplrCleanedFeatureModel } from "src/atlasComponents/sapi/type"; import { SapiViewsCoreRegionRegionBase } from "./region.base.directive"; @@ -34,7 +34,12 @@ export class SapiViewsCoreRegionRegionalFeatureDirective extends SapiViewsCoreRe const { atlas, parcellation, region, template } = arg return !!atlas && !!parcellation && !!region && !!template }), - switchMap(({ atlas, parcellation, region, template }) => this.sapi.getRegionFeatures(atlas["@id"], parcellation["@id"], template["@id"], region.name)), + switchMap(({ atlas, parcellation, region, template }) => { + this.busy$.next(true) + return this.sapi.getRegionFeatures(atlas["@id"], parcellation["@id"], template["@id"], region.name).pipe( + finalize(() => this.busy$.next(false)) + ) + }), ) public listOfFeatures$: Observable<(SapiRegionalFeatureModel|SxplrCleanedFeatureModel)[]> = this.features$.pipe( @@ -42,12 +47,5 @@ export class SapiViewsCoreRegionRegionalFeatureDirective extends SapiViewsCoreRe shareReplay(1), ) - public busy$: Observable<boolean> = merge( - this.ATPR$.pipe( - mapTo(true) - ), - this.features$.pipe( - mapTo(false) - ) - ) + public busy$ = new BehaviorSubject<boolean>(false) } diff --git a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts index f728d0ee8..79e4d693d 100644 --- a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts +++ b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.component.ts @@ -11,7 +11,8 @@ import { SAPI } from "src/atlasComponents/sapi/sapi.service"; templateUrl: './region.rich.template.html', styleUrls: [ `./region.rich.style.css` - ] + ], + exportAs: "sapiViewsCoreRegionRich" }) export class SapiViewsCoreRegionRegionRich extends SapiViewsCoreRegionRegionBase { diff --git a/src/environments/environment.common.ts b/src/environments/environment.common.ts index 3caf94fcb..f4d5c9627 100644 --- a/src/environments/environment.common.ts +++ b/src/environments/environment.common.ts @@ -4,7 +4,7 @@ export const environment = { VERSION: 'unknown version', PRODUCTION: true, BACKEND_URL: null, - BS_REST_URL: 'https://siibra-api-rc.apps.hbp.eu/v2_0', + BS_REST_URL: 'https://siibra-api-latest.apps-dev.hbp.eu/v2_0', SPATIAL_TRANSFORM_BACKEND: 'https://hbp-spatial-backend.apps.hbp.eu', MATOMO_URL: null, MATOMO_ID: null, diff --git a/src/util/priority.ts b/src/util/priority.ts index dd93c8f2b..93329679e 100644 --- a/src/util/priority.ts +++ b/src/util/priority.ts @@ -1,15 +1,22 @@ import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from "@angular/common/http" import { Injectable } from "@angular/core" import { interval, merge, Observable, of, Subject, timer } from "rxjs" -import { filter, finalize, map, switchMapTo, take, takeWhile } from "rxjs/operators" +import { catchError, filter, finalize, map, switchMapTo, take, takeWhile } from "rxjs/operators" export const PRIORITY_HEADER = 'x-sxplr-http-priority' -type Result<T> = { +type ResultBase = { urlWithParams: string - result: HttpResponse<T> } +type Result<T> = { + result: HttpResponse<T> +} & ResultBase + +type ErrorResult = { + error: Error +} & ResultBase + type Queue = { urlWithParams: string priority: number @@ -22,6 +29,7 @@ type Queue = { }) export class PriorityHttpInterceptor implements HttpInterceptor{ + private retry = 5 private disablePriority = false private priorityQueue: Queue[] = [] @@ -30,6 +38,7 @@ export class PriorityHttpInterceptor implements HttpInterceptor{ private archive: Map<string, HttpResponse<unknown>> = new Map() private queue$: Subject<Queue> = new Subject() private result$: Subject<Result<unknown>> = new Subject() + private error$: Subject<ErrorResult> = new Subject() private forceCheck$ = new Subject() @@ -56,11 +65,25 @@ export class PriorityHttpInterceptor implements HttpInterceptor{ this.queue$.subscribe(({ next, req, urlWithParams }) => { this.counter ++ + let retry = this.retry next.handle(req).pipe( finalize(() => { this.counter -- - }) + }), + catchError((err, obs) => { + if (retry >= 0) { + retry -- + return obs + } + return of(new Error(err)) + }), ).subscribe(val => { + if (val instanceof Error) { + this.error$.next({ + urlWithParams, + error: val + }) + } if (val instanceof HttpResponse) { this.archive.set(urlWithParams, val) this.result$.next({ @@ -132,10 +155,18 @@ export class PriorityHttpInterceptor implements HttpInterceptor{ this.insert(objToInsert) this.forceCheck$.next(true) - return this.result$.pipe( + return merge( + this.result$, + this.error$, + ).pipe( filter(v => v.urlWithParams === urlWithParams), take(1), - map(v => v.result) + map(v => { + if (v instanceof Error) { + throw v + } + return (v as Result<unknown>).result + }) ) } } diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html index 37afe5b4c..e81c9936c 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.template.html +++ b/src/viewerModule/viewerCmp/viewerCmp.template.html @@ -675,6 +675,10 @@ <!-- a series of bugs result in requiring this hacky --> <!-- see https://github.com/HumanBrainProject/interactive-viewer/issues/698 --> + + <ng-template [ngIf]="regionDirective.fetchInProgress"> + <spinner-cmp class="sxplr-mt-10 fs-200"></spinner-cmp> + </ng-template> <sxplr-sapiviews-core-region-region-rich [sxplr-sapiviews-core-region-atlas]="selectedAtlas$ | async" [sxplr-sapiviews-core-region-template]="templateSelected$ | async" @@ -682,6 +686,7 @@ [sxplr-sapiviews-core-region-region]="selectedRegions[0]" (sxplr-sapiviews-core-region-region-rich-feature-clicked)="showDataset($event)" (sxplr-sapiviews-core-region-navigate-to)="navigateTo($event)" + #regionDirective="sapiViewsCoreRegionRich" > <div class="sapi-container" header></div> </sxplr-sapiviews-core-region-region-rich> -- GitLab