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