diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98ff32775c62db929d9bcd1087e89d60eaee393c..f084ac2010fe12bb85d640c634fdda21594c2c04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,10 +37,10 @@ jobs: node-version: 16.x - run: npm i - run: | - if [[ "$GITHUB_REF" = *hotfix* ]] + if [[ "$GITHUB_REF" = *hotfix* ]] || [[ "$GITHUB_REF" = refs/heads/staging ]] then - export BS_REST_URL=https://siibra-api-rc.apps.hbp.eu/v2_0 - echo 'export const environment = { "BS_REST_URL": "https://siibra-api-rc.apps.hbp.eu/v2_0" }' > src/environments/environment.common.ts + export SIIBRA_API_ENDPOINTS=https://siibra-api-rc.apps.hbp.eu/v2_0 + node src/environments/parseEnv.js ./environment.ts fi npm run test-ci diff --git a/.github/workflows/docker_img.yml b/.github/workflows/docker_img.yml index 47734fc27ba6540ee111646142553af53a365798..a25d30c92fe0636dc593c859e224eead06025d52 100644 --- a/.github/workflows/docker_img.yml +++ b/.github/workflows/docker_img.yml @@ -18,7 +18,7 @@ jobs: PRODUCTION: 'true' DOCKER_REGISTRY: 'docker-registry.ebrains.eu/siibra/' - SIIBRA_API_STABLE: 'https://siibra-api-stable.apps.hbp.eu/v2_0' + SIIBRA_API_STABLE: 'https://siibra-api-stable.apps.hbp.eu/v2_0,https://siibra-api-stable-ns.apps.hbp.eu/v2_0,https://siibra-api-stable.apps.jsc.hbp.eu/v2_0' SIIBRA_API_RC: 'https://siibra-api-rc.apps.hbp.eu/v2_0' SIIBRA_API_LATEST: 'https://siibra-api-latest.apps-dev.hbp.eu/v2_0' @@ -37,19 +37,19 @@ jobs: if [[ "$GITHUB_REF" == 'refs/heads/master' ]] then echo "Either master, using prod env..." - echo "BS_REST_URL=${{ env.SIIBRA_API_STABLE }}" >> $GITHUB_ENV + echo "SIIBRA_API_ENDPOINTS=${{ env.SIIBRA_API_STABLE }}" >> $GITHUB_ENV elif [[ "$GITHUB_REF" == 'refs/heads/staging' ]] then echo "Either staging, using staging env..." - echo "BS_REST_URL=${{ env.SIIBRA_API_RC }}" >> $GITHUB_ENV + echo "SIIBRA_API_ENDPOINTS=${{ env.SIIBRA_API_RC }}" >> $GITHUB_ENV else if [[ "$GITHUB_REF" == *hotfix* ]] then echo "Hotfix branch, using prod env..." - echo "BS_REST_URL=${{ env.SIIBRA_API_RC }}" >> $GITHUB_ENV + echo "SIIBRA_API_ENDPOINTS=${{ env.SIIBRA_API_RC }}" >> $GITHUB_ENV else echo "Using dev env..." - echo "BS_REST_URL=${{ env.SIIBRA_API_LATEST }}" >> $GITHUB_ENV + echo "SIIBRA_API_ENDPOINTS=${{ env.SIIBRA_API_LATEST }}" >> $GITHUB_ENV fi fi @@ -78,7 +78,7 @@ jobs: --build-arg VERSION=$VERSION \ --build-arg MATOMO_URL=$MATOMO_URL \ --build-arg MATOMO_ID=$MATOMO_ID \ - --build-arg BS_REST_URL=$BS_REST_URL \ + --build-arg SIIBRA_API_ENDPOINTS=$SIIBRA_API_ENDPOINTS \ --build-arg EXPERIMENTAL_FEATURE_FLAG=$EXPERIMENTAL_FEATURE_FLAG \ -t $DOCKER_BUILT_TAG \ . diff --git a/Dockerfile b/Dockerfile index 17e735232f80890da6b5b2b65db1b33c152c9765..8ad7624403cf18aa021e133e2926d1fc1f616b04 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,8 @@ FROM node:14 as builder ARG BACKEND_URL ENV BACKEND_URL=${BACKEND_URL} -ARG BS_REST_URL -ENV BS_REST_URL=${BS_REST_URL:-https://siibra-api-stable.apps.hbp.eu/v1_0} +ARG SIIBRA_API_ENDPOINTS +ENV SIIBRA_API_ENDPOINTS=${SIIBRA_API_ENDPOINTS:-https://siibra-api-stable.apps.hbp.eu/v2_0,https://siibra-api-stable-ns.apps.hbp.eu/v2_0,https://siibra-api-stable.apps.jsc.hbp.eu/v2_0} ARG STRICT_LOCAL ENV STRICT_LOCAL=${STRICT_LOCAL:-false} @@ -39,6 +39,7 @@ RUN rm -rf ./node_modules RUN npm i RUN npm run build +RUN node third_party/matomo/processMatomo.js RUN npm run build-storybook # gzipping container diff --git a/build_env.md b/build_env.md index 1c29ea98614c3a40dbe91d7c620f9e6a5356b4e8..fa8ed9e3951cdabd2cfa2f26bd46c5904e74af14 100644 --- a/build_env.md +++ b/build_env.md @@ -7,7 +7,8 @@ As interactive atlas viewer uses [webpack define plugin](https://webpack.js.org/ | `VERSION` | printed in console on viewer startup | `GIT_HASH` \|\| unspecificed hash | v2.2.2 | | `PRODUCTION` | if the build is for production, toggles optimisations such as minification | `undefined` | true | | `BACKEND_URL` | backend that the viewer calls to fetch available template spaces, parcellations, plugins, datasets | `null` | https://interactive-viewer.apps.hbp.eu/ | -| `BS_REST_URL` | [siibra-api](https://github.com/FZJ-INM1-BDA/siibra-api) used to fetch different resources | https://siibra-api-stable.apps.hbp.eu/v1_0 | +| ~~`BS_REST_URL`~~ _deprecated. use `SIIBRA_API_ENDPOINTS` instead_ | [siibra-api](https://github.com/FZJ-INM1-BDA/siibra-api) used to fetch different resources | `https://siibra-api-stable.apps.hbp.eu/v1_0` | +| `SIIBRA_API_ENDPOINTS` | Comma separated endpoints of [siibra-api](https://github.com/FZJ-INM1-BDA/siibra-api) used to fetch different resources | `https://siibra-api-stable.apps.hbp.eu/v2_0,https://siibra-api-stable-ns.apps.hbp.eu/v2_0,https://siibra-api-stable.apps.jsc.hbp.eu/v2_0` | | `MATOMO_URL` | base url for matomo analytics | `null` | https://example.com/matomo/ | | `MATOMO_ID` | application id for matomo analytics | `null` | 6 | | `STRICT_LOCAL` | hides **Explore** and **Download** buttons. Useful for offline demo's | `false` | `true` | diff --git a/docs/releases/v2.7.3.md b/docs/releases/v2.7.3.md new file mode 100644 index 0000000000000000000000000000000000000000..ec24dc460b6f4bc81b0faab9a78fa5eaf29ff948 --- /dev/null +++ b/docs/releases/v2.7.3.md @@ -0,0 +1,13 @@ +# v2.7.3 + +## Bugfix + +- fixed matomo visitor counting (broke since 2.7.0 release) +- fixed sane url generation +- fixed reset navigation buttons in navigation card + +## Under the hood + +- minor refactor of unused code +- added mirrors to siibra-api +- experimental support for drag and drop swc diff --git a/e2e/checklist.md b/e2e/checklist.md index eef89d804006fd65b2cfadb59cc3d687cb78f714..d03d59b1f8424872c3c88efefa15b01a3b600ecd 100644 --- a/e2e/checklist.md +++ b/e2e/checklist.md @@ -65,6 +65,9 @@ - [ ] on hover, show correct region name(s) - [ ] whole mesh loads ## saneURL +- [ ] saneurl generation functions properly + - [ ] try existing key (human), and get unavailable error + - [ ] try non existing key, and get available - [ ] [saneUrl](https://atlases.ebrains.eu/viewer-staging/saneUrl/bigbrainGreyWhite) redirects to big brain - [ ] [saneUrl](https://atlases.ebrains.eu/viewer-staging/saneUrl/julichbrain) redirects to julich brain (colin 27) - [ ] [saneUrl](https://atlases.ebrains.eu/viewer-staging/saneUrl/whs4) redirects to waxholm v4 diff --git a/mkdocs.yml b/mkdocs.yml index 4d705ac0aabaec7503bcfab2794f46ea6a27d83b..645c2483972a50115c13db5bfeecb40e4489fc6d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -33,6 +33,7 @@ nav: - Fetching datasets: 'advanced/datasets.md' - Display non-atlas volumes: 'advanced/otherVolumes.md' - Release notes: + - v2.7.3: 'releases/v2.7.3.md' - v2.7.2: 'releases/v2.7.2.md' - v2.7.1: 'releases/v2.7.1.md' - v2.7.0: 'releases/v2.7.0.md' diff --git a/package.json b/package.json index 82db76567fac6c5f0b472486c523ce38fa0b3a82..dd637a495824c81008750313065fd1866e01dadf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interactive-viewer", - "version": "2.7.2", + "version": "2.7.3", "description": "siibra-explorer - explore brain atlases. Based on humanbrainproject/nehuba & google/neuroglancer. Built with angular", "scripts": { "lint": "eslint src --ext .ts", diff --git a/src/atlasComponents/sapi/features/sapiFeature.ts b/src/atlasComponents/sapi/features/sapiFeature.ts index 5abaa0040f6cb71046c70bf57e7a77a57f2794a8..8290da0cad9b4c8e057845258980ff5091579272 100644 --- a/src/atlasComponents/sapi/features/sapiFeature.ts +++ b/src/atlasComponents/sapi/features/sapiFeature.ts @@ -7,7 +7,7 @@ export class SAPIFeature { } public detail$ = this.sapi.httpGet<SapiFeatureModel>( - `${SAPI.bsEndpoint}/features/${this.id}`, + `${SAPI.BsEndpoint}/features/${this.id}`, this.opts ) } diff --git a/src/atlasComponents/sapi/module.ts b/src/atlasComponents/sapi/module.ts index a64cc8bc817f05c801cde40d58a95d93c1d198a1..6d0757bb5e771b5bb18e9009bc9032f399fa0c38 100644 --- a/src/atlasComponents/sapi/module.ts +++ b/src/atlasComponents/sapi/module.ts @@ -1,4 +1,4 @@ -import { NgModule } from "@angular/core"; +import { APP_INITIALIZER, NgModule } from "@angular/core"; import { SAPI } from "./sapi.service"; import { CommonModule } from "@angular/common"; import { HttpClientModule, HTTP_INTERCEPTORS } from "@angular/common/http"; @@ -21,6 +21,11 @@ import { MatSnackBarModule } from "@angular/material/snack-bar"; provide: HTTP_INTERCEPTORS, useClass: PriorityHttpInterceptor, multi: true + }, + { + provide: APP_INITIALIZER, + useValue: () => SAPI.SetBsEndPoint(), + multi: true } ] }) diff --git a/src/atlasComponents/sapi/sapi.service.spec.ts b/src/atlasComponents/sapi/sapi.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..0448de0ccf6c7c2fcb3d5cd587e996db861a7bf2 --- /dev/null +++ b/src/atlasComponents/sapi/sapi.service.spec.ts @@ -0,0 +1,97 @@ +import { NEVER } from "rxjs" +import * as env from "src/environments/environment" +import { SAPI } from "./sapi.service" + +describe("> sapi.service.ts", () => { + describe("> SAPI", () => { + describe("#SetBsEndPoint", () => { + let fetchSpy: jasmine.Spy + let environmentSpy: jasmine.Spy + + const endpt1 = 'http://foo-bar' + const endpt2 = 'http://buzz-bizz' + + const atlas1 = 'foo' + const atlas2 = 'bar' + + let originalBsEndpoint: string + beforeAll(() => { + originalBsEndpoint = SAPI.BsEndpoint + }) + + afterAll(() => { + SAPI.BsEndpoint = originalBsEndpoint + }) + + beforeEach(() => { + fetchSpy = spyOn(window, 'fetch') + fetchSpy.and.callThrough() + + environmentSpy = spyOnProperty(env, 'environment') + environmentSpy.and.returnValue({ + SIIBRA_API_ENDPOINTS: `${endpt1},${endpt2}` + }) + }) + + afterEach(() => { + fetchSpy.calls.reset() + environmentSpy.calls.reset() + }) + + describe("> first passes", () => { + beforeEach(() => { + const resp = new Response(JSON.stringify([atlas1]), { headers: { 'content-type': 'application/json' }, status: 200 }) + fetchSpy.and.resolveTo(resp) + }) + it("> should call fetch once", async () => { + await SAPI.SetBsEndPoint() + expect(fetchSpy).toHaveBeenCalledTimes(1) + expect(fetchSpy).toHaveBeenCalledOnceWith(`${endpt1}/atlases`) + }) + + it("> endpoint should be set", async () => { + await SAPI.SetBsEndPoint() + expect(SAPI.BsEndpoint).toBe(endpt1) + }) + }) + + describe("> first fails", () => { + beforeEach(() => { + let counter = 0 + fetchSpy.and.callFake(async () => { + if (counter === 0) { + counter ++ + throw new Error(`bla`) + } + const resp = new Response(JSON.stringify([atlas1]), { headers: { 'content-type': 'application/json' }, status: 200 }) + return resp + }) + }) + + it("> should call twice", async () => { + await SAPI.SetBsEndPoint() + expect(fetchSpy).toHaveBeenCalledTimes(2) + expect(fetchSpy.calls.allArgs()).toEqual([ + [`${endpt1}/atlases`], + [`${endpt2}/atlases`], + ]) + }) + + it('> should set endpt2', async () => { + await SAPI.SetBsEndPoint() + expect(SAPI.BsEndpoint).toBe(endpt2) + }) + + it("> instances bsendpoint should be the updated version", async () => { + await SAPI.SetBsEndPoint() + const mockHttpClient = { + get: jasmine.createSpy() + } + mockHttpClient.get.and.returnValue(NEVER) + const sapi = new SAPI(mockHttpClient as any, null, null) + expect(sapi.bsEndpoint).toBe(endpt2) + }) + }) + }) + }) +}) diff --git a/src/atlasComponents/sapi/sapi.service.ts b/src/atlasComponents/sapi/sapi.service.ts index 0600ef2978744509aacbd555e1ae9c5472df89ec..394acc033c64d0b3567024c4f65f1ddc53dfc3c1 100644 --- a/src/atlasComponents/sapi/sapi.service.ts +++ b/src/atlasComponents/sapi/sapi.service.ts @@ -3,7 +3,8 @@ import { HttpClient } from '@angular/common/http'; import { map, shareReplay } from "rxjs/operators"; import { SAPIAtlas, SAPISpace } from './core' import { - SapiAtlasModel, SapiModalityModel, + SapiAtlasModel, + SapiModalityModel, SapiParcellationModel, SapiQueryPriorityArg, SapiRegionalFeatureModel, @@ -24,15 +25,38 @@ 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.1' +export const SIIBRA_API_VERSION = '0.2.2' type RegistryType = SAPIAtlas | SAPISpace | SAPIParcellation @Injectable() export class SAPI{ - static bsEndpoint = environment.BS_REST_URL || `https://siibra-api-latest.apps-dev.hbp.eu/v2_0` - public bsEndpoint = SAPI.bsEndpoint + static async SetBsEndPoint() { + let idx = 0 + const siibraApiEndpts = environment.SIIBRA_API_ENDPOINTS.split(',') + while (idx < siibraApiEndpts.length) { + const url = siibraApiEndpts[idx] + try { + const resp = await fetch(`${url}/atlases`) + const atlases = await resp.json() + if (atlases.length > 0) { + SAPI.BsEndpoint = url + return + } + } catch (e) { + idx ++ + } + } + SAPI.ErrorMessage = `It appears all of our mirrors are not working. The viewer may not be working properly...` + } + + static ErrorMessage = null + static BsEndpoint = `https://siibra-api-rc.apps.hbp.eu/v2_0` + + get bsEndpoint() { + return SAPI.BsEndpoint + } registry = { _map: {} as Record<string, { @@ -92,7 +116,7 @@ export class SAPI{ } getModalities(): Observable<SapiModalityModel[]> { - return this.http.get<SapiModalityModel[]>(`${SAPI.bsEndpoint}/modalities`) + return this.http.get<SapiModalityModel[]>(`${SAPI.BsEndpoint}/modalities`) } httpGet<T>(url: string, params?: Record<string, string>, sapiParam?: SapiQueryPriorityArg){ @@ -133,6 +157,9 @@ export class SAPI{ private snackbar: MatSnackBar, private workerSvc: AtlasWorkerService, ){ + if (SAPI.ErrorMessage) { + this.snackbar.open(SAPI.ErrorMessage, 'Dismiss', { duration: 5000 }) + } this.atlases$.subscribe(atlases => { for (const atlas of atlases) { for (const space of atlas.spaces) { diff --git a/src/atlasComponents/sapi/stories.base.ts b/src/atlasComponents/sapi/stories.base.ts index 51fde4f0cd5695f6055398e8bd9ec39ed469c541..1fa58bef89b562d5be3ad8ef5383c302bb335649 100644 --- a/src/atlasComponents/sapi/stories.base.ts +++ b/src/atlasComponents/sapi/stories.base.ts @@ -67,22 +67,22 @@ export const parcId = { } export async function getAtlases(): Promise<SapiAtlasModel[]> { - return await (await fetch(`${SAPI.bsEndpoint}/atlases`)).json() as SapiAtlasModel[] + return await (await fetch(`${SAPI.BsEndpoint}/atlases`)).json() as SapiAtlasModel[] } export async function getAtlas(id: string): Promise<SapiAtlasModel>{ - return await (await fetch(`${SAPI.bsEndpoint}/atlases/${id}`)).json() + return await (await fetch(`${SAPI.BsEndpoint}/atlases/${id}`)).json() } export async function getParc(atlasId: string, id: string): Promise<SapiParcellationModel>{ - return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId}/parcellations/${id}`)).json() + return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId}/parcellations/${id}`)).json() } export async function getParcRegions(atlasId: string, id: string, spaceId: string): Promise<SapiRegionModel[]>{ - return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId}/parcellations/${id}/regions?space_id=${encodeURIComponent(spaceId)}`)).json() + return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId}/parcellations/${id}/regions?space_id=${encodeURIComponent(spaceId)}`)).json() } export async function getSpace(atlasId: string, id: string): Promise<SapiSpaceModel> { - return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId}/spaces/${id}`)).json() + return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId}/spaces/${id}`)).json() } export async function getHumanAtlas(): Promise<SapiAtlasModel> { @@ -90,7 +90,7 @@ export async function getHumanAtlas(): Promise<SapiAtlasModel> { } export async function getMni152(): Promise<SapiSpaceModel> { - return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}`)).json() + return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}`)).json() } export async function getJba29(): Promise<SapiParcellationModel> { @@ -103,33 +103,33 @@ export async function getJba29Regions(): Promise<SapiRegionModel[]> { export async function getHoc1Right(spaceId=null): Promise<SapiRegionModel> { if (!spaceId) { - return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right`)).json() + return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right`)).json() } - return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right?space_id=${encodeURIComponent(spaceId)}`)).json() + return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right?space_id=${encodeURIComponent(spaceId)}`)).json() } export async function get44Left(spaceId=null): Promise<SapiRegionModel> { if (!spaceId) { - return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/area%2044%20left`)).json() + return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/area%2044%20left`)).json() } - return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/area%2044%20left?space_id=${encodeURIComponent(spaceId)}`)).json() + return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/area%2044%20left?space_id=${encodeURIComponent(spaceId)}`)).json() } export async function getHoc1RightSpatialFeatures(): Promise<SxplrCleanedFeatureModel[]> { - const json: SapiSpatialFeatureModel[] = await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}/features?parcellation_id=2.9®ion=hoc1%20right`)).json() + const json: SapiSpatialFeatureModel[] = await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}/features?parcellation_id=2.9®ion=hoc1%20right`)).json() return cleanIeegSessionDatasets(json.filter(it => it['@type'] === "siibra/features/ieegSession")) } export async function getHoc1RightFeatures(): Promise<SapiRegionalFeatureModel[]> { - return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right/features`)).json() + return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right/features`)).json() } export async function getHoc1RightFeatureDetail(featId: string): Promise<SapiRegionalFeatureModel>{ - return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right/features/${encodeURIComponent(featId)}`)).json() + return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20right/features/${encodeURIComponent(featId)}`)).json() } export async function getJba29Features(): Promise<SapiParcellationFeatureModel[]> { - return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/features`)).json() + return await (await fetch(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/features`)).json() } export async function getBigbrainSpatialFeatures(): Promise<SapiSpatialFeatureModel[]>{ @@ -137,14 +137,14 @@ export async function getBigbrainSpatialFeatures(): Promise<SapiSpatialFeatureMo [-1000, -1000, -1000], [1000, 1000, 1000] ] - const url = new URL(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.bigbrain}/features`) + const url = new URL(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.bigbrain}/features`) url.searchParams.set(`bbox`, JSON.stringify(bbox)) return await (await fetch(url.toString())).json() } export async function getMni152SpatialFeatureHoc1Right(): Promise<SapiSpatialFeatureModel[]>{ - const url = new URL(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}/features`) + const url = new URL(`${SAPI.BsEndpoint}/atlases/${atlasId.human}/spaces/${spaceId.human.mni152}/features`) url.searchParams.set(`parcellation_id`, parcId.human.jba29) url.searchParams.set("region", 'hoc1 right') return await (await fetch(url.toString())).json() diff --git a/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.spec.ts b/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.spec.ts index 77df043bb37ed7ffb87cad72d422dda777db33d0..972e411795a3ac9e3a862a3cb61bc7d403f1a105 100644 --- a/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.spec.ts +++ b/src/atlasComponents/sapiViews/core/parcellation/parcellationVersion.pipe.spec.ts @@ -3,11 +3,11 @@ import { SAPI } from "src/atlasComponents/sapi/sapi.service" import { SapiParcellationModel } from "src/atlasComponents/sapi/type" import { getTraverseFunctions } from "./parcellationVersion.pipe" -describe(`parcellationVersion.pipe.ts (endpoint at ${SAPI.bsEndpoint})`, () => { +describe(`parcellationVersion.pipe.ts (endpoint at ${SAPI.BsEndpoint})`, () => { describe("getTraverseFunctions", () => { let julichBrainParcellations: SapiParcellationModel[] = [] beforeAll(async () => { - const res = await fetch(`${SAPI.bsEndpoint}/atlases/${encodeURIComponent(IDS.ATLAES.HUMAN)}/parcellations`) + const res = await fetch(`${SAPI.BsEndpoint}/atlases/${encodeURIComponent(IDS.ATLAES.HUMAN)}/parcellations`) const arr: SapiParcellationModel[] = await res.json() julichBrainParcellations = arr.filter(it => /Julich-Brain Cytoarchitectonic Maps/.test(it.name)) }) diff --git a/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts b/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts index 3889c4c3f20621280a66f92f3e37f2a4521414d0..b1efcd694501bd33796a961449fbb3fec244b28c 100644 --- a/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts +++ b/src/atlasComponents/sapiViews/features/connectivity/connectivityBrowser/connectivityBrowser.component.spec.ts @@ -6,7 +6,6 @@ import {CUSTOM_ELEMENTS_SCHEMA, Directive, Input} from "@angular/core"; import {provideMockActions} from "@ngrx/effects/testing"; import {MockStore, provideMockStore} from "@ngrx/store/testing"; import {Observable, of} from "rxjs"; -import {BS_ENDPOINT} from "src/util/constants"; import {SAPI} from "src/atlasComponents/sapi"; import {AngularMaterialModule} from "src/sharedModules"; @@ -66,10 +65,6 @@ describe('ConnectivityComponent', () => { providers: [ provideMockActions(() => actions$), provideMockStore(), - { - provide: BS_ENDPOINT, - useValue: MOCK_BS_ENDPOINT - }, { provide: SAPI, useValue: { diff --git a/src/atlasViewer/atlasViewer.workerService.service.ts b/src/atlasViewer/atlasViewer.workerService.service.ts index 9a17115534cae61a899a29c7d32bc4602599fd19..67a422714822d25e47fdb55b345116cda9db8eba 100644 --- a/src/atlasViewer/atlasViewer.workerService.service.ts +++ b/src/atlasViewer/atlasViewer.workerService.service.ts @@ -3,9 +3,6 @@ import { fromEvent } from "rxjs"; import { filter, take } from "rxjs/operators"; import { getUuid } from "src/util/fn"; -// worker is now exported in angular.json file -export const worker = new Worker('worker.js') - interface IWorkerMessage { method: string param: any @@ -17,7 +14,11 @@ interface IWorkerMessage { }) export class AtlasWorkerService { - public worker = worker + private worker: Worker + + constructor(){ + this.worker = new Worker('worker.js') + } async sendMessage(_data: IWorkerMessage){ diff --git a/src/environments/environment.common.ts b/src/environments/environment.common.ts index f4d5c96275db63b6e84e76a7223c597139fba508..4f410904c371a0239abecd0e602153b1680fdba3 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-latest.apps-dev.hbp.eu/v2_0', + SIIBRA_API_ENDPOINTS: 'https://siibra-api-stable.apps.hbp.eu/v2_0,https://siibra-api-stable.apps.jsc.hbp.eu/v2_0,https://siibra-api-stable-ns.apps.hbp.eu/v2_0', SPATIAL_TRANSFORM_BACKEND: 'https://hbp-spatial-backend.apps.hbp.eu', MATOMO_URL: null, MATOMO_ID: null, diff --git a/src/environments/parseEnv.js b/src/environments/parseEnv.js index b05909d596c85cc1875650602cafb8a5fa2fba3a..9533d341158797f5da8a604929d9327e8980545f 100644 --- a/src/environments/parseEnv.js +++ b/src/environments/parseEnv.js @@ -2,15 +2,17 @@ const fs = require('fs') const path = require('path') const { promisify } = require('util') const asyncWrite = promisify(fs.writeFile) +const process = require("process") const main = async () => { - const pathToEnvFile = path.join(__dirname, './environment.prod.ts') + const target = process.argv[2] || './environment.prod.ts' + const pathToEnvFile = path.join(__dirname, target) const { BACKEND_URL, STRICT_LOCAL, MATOMO_URL, MATOMO_ID, - BS_REST_URL, + SIIBRA_API_ENDPOINTS, VERSION, GIT_HASH = 'unknown hash', EXPERIMENTAL_FEATURE_FLAG @@ -21,7 +23,7 @@ const main = async () => { STRICT_LOCAL, MATOMO_URL, MATOMO_ID, - BS_REST_URL, + SIIBRA_API_ENDPOINTS, VERSION, GIT_HASH, EXPERIMENTAL_FEATURE_FLAG, @@ -39,7 +41,7 @@ export const environment = { ...commonEnv, GIT_HASH: ${gitHash}, VERSION: ${version}, - BS_REST_URL: ${JSON.stringify(BS_REST_URL)}, + SIIBRA_API_ENDPOINTS: ${JSON.stringify(SIIBRA_API_ENDPOINTS)}, BACKEND_URL: ${JSON.stringify(BACKEND_URL)}, STRICT_LOCAL: ${JSON.stringify(STRICT_LOCAL)}, MATOMO_URL: ${JSON.stringify(MATOMO_URL)}, diff --git a/src/main.module.ts b/src/main.module.ts index 7d26a8ee52400fa2acd8e9cbd0a670ddd06c5670..da570799012cb86e814b3760f110174c943f8f36 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -33,7 +33,6 @@ import { CookieModule } from './ui/cookieAgreement/module'; import { KgTosModule } from './ui/kgtos/module'; import { AtlasViewerRouterModule } from './routerModule'; import { MessagingGlue } from './messagingGlue'; -import { BS_ENDPOINT } from './util/constants'; import { QuickTourModule } from './ui/quickTour'; import { of } from 'rxjs'; import { CANCELLABLE_DIALOG, CANCELLABLE_DIALOG_OPTS } from './util/interfaces'; @@ -163,10 +162,6 @@ import { CONST } from "common/constants" provide: WINDOW_MESSAGING_HANDLER_TOKEN, useClass: MessagingGlue }, - { - provide: BS_ENDPOINT, - useValue: (environment.BS_REST_URL || `https://siibra-api-stable.apps.hbp.eu/v1_0`).replace(/\/$/, '') - }, { provide: DARKTHEME, useFactory: (store: Store) => store.pipe( diff --git a/src/share/saneUrl/saneUrl.component.spec.ts b/src/share/saneUrl/saneUrl.component.spec.ts index 5ab173950c92148144b7b3025f63cddf95205a30..982ecb76fb47ca954a5da6c9f5c792c69f3118ba 100644 --- a/src/share/saneUrl/saneUrl.component.spec.ts +++ b/src/share/saneUrl/saneUrl.component.spec.ts @@ -1,14 +1,13 @@ -import { TestBed, fakeAsync, tick, flush } from '@angular/core/testing' -import { ShareModule } from '../share.module' +import { TestBed, fakeAsync, tick, flush, ComponentFixture } from '@angular/core/testing' import { SaneUrl } from './saneUrl.component' -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing' import { By } from '@angular/platform-browser' import { BACKENDURL } from 'src/util/constants' import { NoopAnimationsModule } from '@angular/platform-browser/animations' import { SaneUrlSvc } from './saneUrl.service' import { AngularMaterialModule } from 'src/sharedModules' import { CUSTOM_ELEMENTS_SCHEMA, Directive } from '@angular/core' -import { of } from 'rxjs' +import { of, throwError } from 'rxjs' +import { NotFoundError } from '../type' const inputCss = `input[aria-label="Custom link"]` const submitCss = `button[aria-label="Create custom link"]` @@ -25,15 +24,22 @@ class AuthStateDummy { describe('> saneUrl.component.ts', () => { describe('> SaneUrl', () => { + const mockSaneUrlSvc = { + saneUrlroot: 'saneUrlroot', + getKeyVal: jasmine.createSpy('getKeyVal'), + setKeyVal: jasmine.createSpy('setKeyVal'), + } beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ - HttpClientTestingModule, NoopAnimationsModule, AngularMaterialModule, ], providers: [ - SaneUrlSvc, + { + provide: SaneUrlSvc, + useValue: mockSaneUrlSvc + } ], declarations: [ SaneUrl, @@ -43,11 +49,18 @@ describe('> saneUrl.component.ts', () => { CUSTOM_ELEMENTS_SCHEMA ] }).compileComponents() + + mockSaneUrlSvc.getKeyVal.and.returnValue( + of('foo-bar') + ) + mockSaneUrlSvc.setKeyVal.and.returnValue( + of('OK') + ) }) afterEach(() => { - const ctrl = TestBed.inject(HttpTestingController) - ctrl.verify() + mockSaneUrlSvc.getKeyVal.calls.reset() + mockSaneUrlSvc.setKeyVal.calls.reset() }) it('> can be created', () => { @@ -112,215 +125,188 @@ describe('> saneUrl.component.ts', () => { const input = fixture.debugElement.query( By.css( inputCss ) ) }) - it('> on entering string in input, makes debounced GET request', fakeAsync(() => { - - const value = 'test_1' - - const httpTestingController = TestBed.inject(HttpTestingController) - - // Necessary to detectChanges, or formControl will not initialise properly - // See https://stackoverflow.com/a/56600762/6059235 - const fixture = TestBed.createComponent(SaneUrl) - fixture.detectChanges() - - // Set value - fixture.componentInstance.customUrl.setValue(value) - - tick(500) - - const req = httpTestingController.expectOne(`${BACKENDURL}saneUrl/${value}`) - req.flush(200) - })) - - it('> on 200 response, show error', fakeAsync(() => { - - const value = 'test_1' - - const httpTestingController = TestBed.inject(HttpTestingController) - - // Necessary to detectChanges, or formControl will not initialise properly - // See https://stackoverflow.com/a/56600762/6059235 - const fixture = TestBed.createComponent(SaneUrl) - fixture.detectChanges() - - // Set value - fixture.componentInstance.customUrl.setValue(value) - - tick(500) - - const req = httpTestingController.expectOne(`${BACKENDURL}saneUrl/${value}`) - req.flush('OK') - - // Expect validator to fail catch it - expect(fixture.componentInstance.customUrl.invalid).toEqual(true) - - // on change detection, UI should catch it - fixture.detectChanges() - - const input = fixture.debugElement.query( By.css( inputCss ) ) - - const submit = fixture.debugElement.query( By.css( submitCss ) ) - const disabled = !!submit.attributes['disabled'] - expect(disabled.toString()).toEqual('true') - })) - - it('> on 404 response, show available', fakeAsync(() => { - - const value = 'test_1' - - const httpTestingController = TestBed.inject(HttpTestingController) - - // Necessary to detectChanges, or formControl will not initialise properly - // See https://stackoverflow.com/a/56600762/6059235 - const fixture = TestBed.createComponent(SaneUrl) - fixture.detectChanges() - - // Set value - fixture.componentInstance.customUrl.setValue(value) - - tick(500) - - const req = httpTestingController.expectOne(`${BACKENDURL}saneUrl/${value}`) - req.flush('some reason', { status: 404, statusText: 'Not Found.' }) - - // Expect validator to fail catch it - expect(fixture.componentInstance.customUrl.invalid).toEqual(false) - - // on change detection, UI should catch it - fixture.detectChanges() - - const input = fixture.debugElement.query( By.css( inputCss ) ) - - const submit = fixture.debugElement.query( By.css( submitCss ) ) - const disabled = !!submit.attributes['disabled'] - expect(disabled.toString()).toEqual('false') - })) - - it('> on other error codes, show invalid', fakeAsync(() => { - - const value = 'test_1' - - const httpTestingController = TestBed.inject(HttpTestingController) - - // Necessary to detectChanges, or formControl will not initialise properly - // See https://stackoverflow.com/a/56600762/6059235 - const fixture = TestBed.createComponent(SaneUrl) - fixture.detectChanges() - - // Set value - fixture.componentInstance.customUrl.setValue(value) - - tick(500) - - const req = httpTestingController.expectOne(`${BACKENDURL}saneUrl/${value}`) - req.flush('some reason', { status: 401, statusText: 'Unauthorised.' }) - - // Expect validator to fail catch it - expect(fixture.componentInstance.customUrl.invalid).toEqual(true) - - // on change detection, UI should catch it - fixture.detectChanges() - - const input = fixture.debugElement.query( By.css( inputCss ) ) - - const submit = fixture.debugElement.query( By.css( submitCss ) ) - const disabled = !!submit.attributes['disabled'] - expect(disabled.toString()).toEqual('true') - })) - - it('> on click create link btn calls correct API', fakeAsync(() => { - - const value = 'test_1' - - const httpTestingController = TestBed.inject(HttpTestingController) - - // Necessary to detectChanges, or formControl will not initialise properly - // See https://stackoverflow.com/a/56600762/6059235 - const fixture = TestBed.createComponent(SaneUrl) - fixture.detectChanges() - - // Set value - fixture.componentInstance.customUrl.setValue(value) - - tick(500) - - const req = httpTestingController.expectOne(`${BACKENDURL}saneUrl/${value}`) - req.flush('some reason', { status: 404, statusText: 'Not Found.' }) - - fixture.detectChanges() - flush() - - const submit = fixture.debugElement.query( By.css( submitCss ) ) - const disabled = !!submit.attributes['disabled'] - expect(disabled.toString()).toEqual('false') - - submit.triggerEventHandler('click', {}) - - fixture.detectChanges() - - const disabledInProgress = !!submit.attributes['disabled'] - expect(disabledInProgress.toString()).toEqual('true') - - const req2 = httpTestingController.expectOne({ - method: 'POST', - url: `${BACKENDURL}saneUrl/${value}` + describe("> on valid input", () => { + let saneUrlCmp: SaneUrl + let fixture: ComponentFixture<SaneUrl> + const stateTobeSaved = 'foo-bar' + beforeEach(() => { + // Necessary to detectChanges, or formControl will not initialise properly + // See https://stackoverflow.com/a/56600762/6059235 + fixture = TestBed.createComponent(SaneUrl) + saneUrlCmp = fixture.componentInstance + saneUrlCmp.stateTobeSaved = stateTobeSaved + fixture.detectChanges() }) - - req2.flush({}) - - fixture.detectChanges() - - const disabledAfterComplete = !!submit.attributes['disabled'] - expect(disabledAfterComplete.toString()).toEqual('true') - - const cpyBtn = fixture.debugElement.query( By.css( copyBtnCss ) ) - expect(cpyBtn).toBeTruthy() - })) - - it('> on click create link btn fails show result', fakeAsync(() => { - - const value = 'test_1' - - const httpTestingController = TestBed.inject(HttpTestingController) - - // Necessary to detectChanges, or formControl will not initialise properly - // See https://stackoverflow.com/a/56600762/6059235 - const fixture = TestBed.createComponent(SaneUrl) - fixture.detectChanges() - - // Set value - fixture.componentInstance.customUrl.setValue(value) - - tick(500) - - const req = httpTestingController.expectOne(`${BACKENDURL}saneUrl/${value}`) - req.flush('some reason', { status: 404, statusText: 'Not Found.' }) - - fixture.detectChanges() - flush() - - const submit = fixture.debugElement.query( By.css( submitCss ) ) - const disabled = !!submit.attributes['disabled'] - expect(disabled.toString()).toEqual('false') - - submit.triggerEventHandler('click', {}) - - fixture.detectChanges() - - const disabledInProgress = !!submit.attributes['disabled'] - expect(disabledInProgress.toString()).toEqual('true') - - const req2 = httpTestingController.expectOne({ - method: 'POST', - url: `${BACKENDURL}saneUrl/${value}` + it('> on entering string in input, makes debounced GET request', fakeAsync(() => { + + const value = 'test_1' + + // Set value + fixture.componentInstance.customUrl.setValue(value) + + tick(500) + + expect(mockSaneUrlSvc.getKeyVal).toHaveBeenCalledOnceWith(value) + })) + + describe("> on 200", () => { + it("> show error", fakeAsync(() => { + + const value = 'test_1' + + // Set value + fixture.componentInstance.customUrl.setValue(value) + + tick(500) + + // Expect validator to fail catch it + expect(fixture.componentInstance.customUrl.invalid).toEqual(true) + + // on change detection, UI should catch it + fixture.detectChanges() + + const input = fixture.debugElement.query( By.css( inputCss ) ) + + const submit = fixture.debugElement.query( By.css( submitCss ) ) + const disabled = !!submit.attributes['disabled'] + expect(disabled.toString()).toEqual('true') + })) + }) + + describe('> on 404', () => { + beforeEach(() => { + mockSaneUrlSvc.getKeyVal.and.returnValue( + throwError(new NotFoundError('not found')) + ) + }) + it("> should available", fakeAsync(() => { + + const value = 'test_1' + + // Set value + fixture.componentInstance.customUrl.setValue(value) + + tick(500) + + // Expect validator to fail catch it + expect(fixture.componentInstance.customUrl.invalid).toEqual(false) + + // on change detection, UI should catch it + fixture.detectChanges() + + const input = fixture.debugElement.query( By.css( inputCss ) ) + + const submit = fixture.debugElement.query( By.css( submitCss ) ) + const disabled = !!submit.attributes['disabled'] + expect(disabled.toString()).toEqual('false') + })) }) + + describe("> on other error", () => { + beforeEach(() => { + + mockSaneUrlSvc.getKeyVal.and.returnValue( + throwError(new Error('other errors')) + ) + }) + it("> show invalid", fakeAsync(() => { + const value = 'test_1' + + // Set value + fixture.componentInstance.customUrl.setValue(value) + + tick(500) + + // Expect validator to fail catch it + expect(fixture.componentInstance.customUrl.invalid).toEqual(true) + + // on change detection, UI should catch it + fixture.detectChanges() + + const input = fixture.debugElement.query( By.css( inputCss ) ) + + const submit = fixture.debugElement.query( By.css( submitCss ) ) + const disabled = !!submit.attributes['disabled'] + expect(disabled.toString()).toEqual('true') + })) + }) + + describe("> on click create link", () => { + beforeEach(() => { + mockSaneUrlSvc.getKeyVal.and.returnValue( + throwError(new NotFoundError('not found')) + ) + }) + it("> calls correct service function", fakeAsync(() => { + + const value = 'test_1' + + // Set value + fixture.componentInstance.customUrl.setValue(value) + + tick(500) + + fixture.detectChanges() + flush() + + const submit = fixture.debugElement.query( By.css( submitCss ) ) + const disabled = !!submit.attributes['disabled'] + expect(disabled.toString()).toEqual('false') + + submit.triggerEventHandler('click', {}) + + fixture.detectChanges() + + const disabledInProgress = !!submit.attributes['disabled'] + expect(disabledInProgress.toString()).toEqual('true') + + fixture.detectChanges() + + const disabledAfterComplete = !!submit.attributes['disabled'] + expect(disabledAfterComplete.toString()).toEqual('true') + + const cpyBtn = fixture.debugElement.query( By.css( copyBtnCss ) ) + expect(cpyBtn).toBeTruthy() + })) + + describe("> on fail", () => { + beforeEach(() => { + mockSaneUrlSvc.setKeyVal.and.returnValue( + throwError(new Error(`some error`)) + ) + }) + it("> show result", fakeAsync(() => { + + const value = 'test_1' - req2.flush('Something went wrong', { statusText: 'Wrong status text', status: 500 }) - - fixture.detectChanges() - - const input = fixture.debugElement.query( By.css( inputCss ) ) - - })) + // Set value + fixture.componentInstance.customUrl.setValue(value) + + tick(500) + + fixture.detectChanges() + + const submit = fixture.debugElement.query( By.css( submitCss ) ) + const disabled = !!submit.attributes['disabled'] + expect(disabled.toString()).toEqual('false') + + submit.triggerEventHandler('click', {}) + + fixture.detectChanges() + + const disabledInProgress = !!submit.attributes['disabled'] + expect(disabledInProgress.toString()).toEqual('true') + + expect(mockSaneUrlSvc.setKeyVal).toHaveBeenCalledOnceWith(value, stateTobeSaved) + + fixture.detectChanges() + + const input = fixture.debugElement.query( By.css( inputCss ) ) + + })) + }) + }) + + }) }) }) diff --git a/src/share/saneUrl/saneUrl.service.ts b/src/share/saneUrl/saneUrl.service.ts index 8f3af1f3d55fcb9baec57d44ddbbda76ac24c2df..c5d9849f760ed90b7197ef8206b53db55ab1025d 100644 --- a/src/share/saneUrl/saneUrl.service.ts +++ b/src/share/saneUrl/saneUrl.service.ts @@ -4,23 +4,27 @@ import { throwError } from "rxjs"; import { catchError, mapTo } from "rxjs/operators"; import { BACKENDURL } from 'src/util/constants' import { IKeyValStore, NotFoundError } from '../type' +import { DISABLE_PRIORITY_HEADER } from "src/util/priority" @Injectable({ providedIn: 'root' }) export class SaneUrlSvc implements IKeyValStore{ - public saneUrlRoot = `${BACKENDURL}saneUrl/` + public saneUrlRoot = `${BACKENDURL}go/` constructor( private http: HttpClient ){ - + if (!BACKENDURL) { + const loc = window.location + this.saneUrlRoot = `${loc.protocol}//${loc.hostname}${!!loc.port ? (':' + loc.port) : ''}${loc.pathname}go/` + } } getKeyVal(key: string) { return this.http.get<Record<string, any>>( `${this.saneUrlRoot}${key}`, - { responseType: 'json' } + { responseType: 'json', headers: { [DISABLE_PRIORITY_HEADER]: '1' } } ).pipe( catchError((err, obs) => { const { status } = err @@ -35,7 +39,8 @@ export class SaneUrlSvc implements IKeyValStore{ setKeyVal(key: string, value: any) { return this.http.post( `${this.saneUrlRoot}${key}`, - value + value, + { headers: { [DISABLE_PRIORITY_HEADER]: '1' } } ).pipe( mapTo(`${this.saneUrlRoot}${key}`) ) diff --git a/src/util/constants.ts b/src/util/constants.ts index 5c4e7185743a1dca35e0523f076385b797fc21b5..9b1700442a02984d4b5a15c73b122d5bcc0cf8ed 100644 --- a/src/util/constants.ts +++ b/src/util/constants.ts @@ -115,7 +115,6 @@ export const compareLandmarksChanged: (prevLandmarks: any[], newLandmarks: any[] } export const CYCLE_PANEL_MESSAGE = `[spacebar] to cycle through views` -export const BS_ENDPOINT = new InjectionToken<string>('BS_ENDPOINT') export const UNSUPPORTED_PREVIEW = [{ text: 'Preview of Colin 27 and JuBrain Cytoarchitectonic', diff --git a/src/util/priority.ts b/src/util/priority.ts index ab703f76b14e1a299fa2ae017d81038a43c1c858..79ef1b043e4d5bb13a99e157995315e05e94b8d5 100644 --- a/src/util/priority.ts +++ b/src/util/priority.ts @@ -24,6 +24,8 @@ type Queue = { next: HttpHandler } +export const DISABLE_PRIORITY_HEADER = 'x-sxplr-disable-priority' + @Injectable({ providedIn: 'root' }) @@ -137,8 +139,11 @@ export class PriorityHttpInterceptor implements HttpInterceptor{ * Since the way in which serialization occurs is via path and query param... * body is not used. */ - if (this.disablePriority || req.method !== 'GET') { - return next.handle(req) + if (this.disablePriority || req.method !== 'GET' || !!req.headers.get(DISABLE_PRIORITY_HEADER)) { + const newReq = req.clone({ + headers: req.headers.delete(DISABLE_PRIORITY_HEADER) + }) + return next.handle(newReq) } const { urlWithParams } = req diff --git a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts index c6437fc7c93844af930c70ce2c5ae019227f76cf..6c5bb5aeb0c02634389135fe0056c5b88644d89b 100644 --- a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts +++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.spec.ts @@ -1,7 +1,6 @@ import { TestBed, fakeAsync, tick, ComponentFixture } from "@angular/core/testing" import { CommonModule } from "@angular/common" import { NehubaViewerUnit, IMPORT_NEHUBA_INJECT_TOKEN, scanFn } from "./nehubaViewer.component" -import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service" import { LoggingModule, LoggingService } from "src/logging" import { IMeshesToLoad, SET_MESHES_TO_LOAD } from "../constants" import { Subject } from "rxjs" @@ -106,7 +105,6 @@ describe('> nehubaViewer.component.ts', () => { provide: SET_COLORMAP_OBS, useValue: setcolorMap$ }, - AtlasWorkerService, LoggingService, ] }).compileComponents() diff --git a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts index b1f70202ac24bfbe8375be62bcee549eb5132694..4d38f0487b48b24a678e8a34b454d521d102e94e 100644 --- a/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts +++ b/src/viewerModule/nehuba/nehubaViewer/nehubaViewer.component.ts @@ -1,7 +1,6 @@ import { Component, ElementRef, EventEmitter, OnDestroy, Output, Inject, Optional } from "@angular/core"; -import { fromEvent, Subscription, BehaviorSubject, Observable, Subject, of, interval } from 'rxjs' -import { debounceTime, filter, map, scan, switchMap, take, distinctUntilChanged, debounce } from "rxjs/operators"; -import { AtlasWorkerService } from "src/atlasViewer/atlasViewer.workerService.service"; +import { Subscription, BehaviorSubject, Observable, Subject, of, interval } from 'rxjs' +import { debounceTime, filter, scan, switchMap, take, distinctUntilChanged, debounce } from "rxjs/operators"; import { LoggingService } from "src/logging"; import { bufferUntil, getExportNehuba, getViewer, setNehubaViewer, switchMapWaitFor } from "src/util/fn"; import { deserializeSegment, NEHUBA_INSTANCE_INJTKN } from "../util"; @@ -135,7 +134,6 @@ export class NehubaViewerUnit implements OnDestroy { constructor( public elementRef: ElementRef, - private workerService: AtlasWorkerService, private log: LoggingService, @Inject(IMPORT_NEHUBA_INJECT_TOKEN) getImportNehubaPr: () => Promise<any>, @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) private nehubaViewer$: Subject<NehubaViewerUnit>, @@ -179,67 +177,6 @@ export class NehubaViewerUnit implements OnDestroy { }) .catch(e => this.errorEmitter.emit(e)) - - /** - * TODO move to layerCtrl.service - */ - this.ondestroySubscriptions.push( - fromEvent(this.workerService.worker, 'message').pipe( - filter((message: any) => { - - if (!message) { - // this.log.error('worker response message is undefined', message) - return false - } - if (!message.data) { - // this.log.error('worker response message.data is undefined', message.data) - return false - } - if (message.data.type !== 'ASSEMBLED_USERLANDMARKS_VTK') { - /* worker responded with not assembled landmark, no need to act */ - return false - } - /** - * nb url may be undefined - * if undefined, user have removed all user landmarks, and all that needs to be done - * is remove the user landmark layer - * - * message.data.url - */ - - return true - }), - debounceTime(100), - map(e => e.data.url), - ).subscribe(url => { - this.landmarksLoaded = !!url - this.removeuserLandmarks() - - /** - * url may be null if user removes all landmarks - */ - if (!url) { - /** - * remove transparency from meshes in current layer(s) - */ - this.setMeshTransparency(false) - return - } - const _ = {} - _[NG_USER_LANDMARK_LAYER_NAME] = { - type: 'mesh', - source: `vtk://${url}`, - shader: this.userLandmarkShader, - } - this.loadLayer(_) - - /** - * adding transparency to meshes in current layer(s) - */ - this.setMeshTransparency(true) - }), - ) - if (this.setColormap$) { this.ondestroySubscriptions.push( this.setColormap$.pipe( @@ -546,37 +483,6 @@ export class NehubaViewerUnit implements OnDestroy { } private userLandmarkShader: string = FRAGMENT_MAIN_WHITE - - // TODO single landmark for user landmark - public updateUserLandmarks(landmarks: any[]) { - if (!this.nehubaViewer) { - return - } - - this.workerService.worker.postMessage({ - type : 'GET_USERLANDMARKS_VTK', - scale: Math.min(...this.dim.map(v => v * NG_LANDMARK_CONSTANT)), - landmarks : landmarks.map(lm => lm.position.map(coord => coord * 1e6)), - }) - - const parseLmColor = lm => { - if (!lm) return null - const { color } = lm - if (!color) return null - if (!Array.isArray(color)) return null - if (color.length !== 3) return null - const parseNum = num => (num >= 0 && num <= 255 ? num / 255 : 1).toFixed(3) - return `emitRGB(vec3(${color.map(parseNum).join(',')}));` - } - - const appendConditional = (frag, idx) => frag && `if (label > ${idx - 0.01} && label < ${idx + 0.01}) { ${frag} }` - - if (landmarks.some(parseLmColor)) { - this.userLandmarkShader = `void main(){ ${landmarks.map(parseLmColor).map(appendConditional).filter(v => !!v).join('else ')} else {${FRAGMENT_EMIT_WHITE}} }` - } else { - this.userLandmarkShader = FRAGMENT_MAIN_WHITE - } - } public removeSpatialSearch3DLandmarks() { this.removeLayer({ diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts index 0c7b084eb8dab41764d88d913578e8eeb843478b..fea1911266af6a410bdd339c805c4aeb3129f20c 100644 --- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts +++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts @@ -284,8 +284,33 @@ export class NehubaGlueCmp implements IViewer<'nehuba'>, OnDestroy, AfterViewIni /** * TODO check extension? */ - this.dismissAllAddedLayers() + + if (/\.swc$/i.test(file.name)) { + const url = URL.createObjectURL(file) + this.droppedLayerNames.push({ + layerName: randomUuid, + resourceUrl: url + }) + this.store$.dispatch( + atlasAppearance.actions.addCustomLayer({ + customLayer: { + id: randomUuid, + source: `swc://${url}`, + segments: ["1"], + transform: [ + [1e3, 0, 0, 0], + [0, 1e3, 0, 0], + [0, 0, 1e3, 0], + [0, 0, 0, 1], + ], + clType: 'customlayer/nglayer' as const + } + }) + ) + return + } + // Get file, try to inflate, if files, use original array buffer const buf = await file.arrayBuffer() diff --git a/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts b/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts index ce0afa8fa8632b433b5998eed1107654fdc4dfa2..ab6ac511153553a1bc50365bd2b85c4a239c94f4 100644 --- a/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts +++ b/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts @@ -146,8 +146,8 @@ describe('> statusCard.component.ts', () => { initialNgState: { navigation: { pose: { - orientation: [0,0,0,1], - position: [10, 20, 30] + orientation: [0, 0, 0, 1], + position: [0, 0, 0] }, zoomFactor: 1e6 } diff --git a/src/viewerModule/nehuba/statusCard/statusCard.component.ts b/src/viewerModule/nehuba/statusCard/statusCard.component.ts index 26d6786e8b7f7ea3723eb47d7a32bc299c46b720..58bbdf911ed843cd5e68123f65869fe5843b3ace 100644 --- a/src/viewerModule/nehuba/statusCard/statusCard.component.ts +++ b/src/viewerModule/nehuba/statusCard/statusCard.component.ts @@ -177,10 +177,7 @@ export class StatusCardComponent implements OnInit, OnChanges{ */ public resetNavigation({rotation: rotationFlag = false, position: positionFlag = false, zoom : zoomFlag = false}: {rotation?: boolean, position?: boolean, zoom?: boolean}): void { const config = getNehubaConfig(this.selectedTemplate) - const { - orientation, - position - } = config.dataset.initialNgState.navigation.pose + const { zoomFactor: zoom } = config.dataset.initialNgState.navigation @@ -189,8 +186,8 @@ export class StatusCardComponent implements OnInit, OnChanges{ actions.navigateTo({ navigation: { ...this.currentNavigation, - ...(rotationFlag ? { orientation: orientation } : {}), - ...(positionFlag ? { position: position } : {}), + ...(rotationFlag ? { orientation: [0, 0, 0, 1] } : {}), + ...(positionFlag ? { position: [0, 0, 0] } : {}), ...(zoomFlag ? { zoom: zoom } : {}), }, physical: true, diff --git a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts index c5042ecfc22e3134ab6af2c967bdad86bf5d4933..0ec1d5125ba83f89dc91ea8e20e33954673030a7 100644 --- a/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts +++ b/src/viewerModule/nehuba/viewerCtrl/viewerCtrlCmp/viewerCtrlCmp.component.spec.ts @@ -22,7 +22,6 @@ describe('> viewerCtrlCmp.component.ts', () => { let mockStore: MockStore let mockNehubaViewer = { - updateUserLandmarks: jasmine.createSpy(), nehubaViewer: { ngviewer: { layerManager: { @@ -42,7 +41,6 @@ describe('> viewerCtrlCmp.component.ts', () => { } afterEach(() => { - mockNehubaViewer.updateUserLandmarks.calls.reset() mockNehubaViewer.nehubaViewer.ngviewer.layerManager.getLayerByName.calls.reset() mockNehubaViewer.nehubaViewer.ngviewer.display.scheduleRedraw.calls.reset() })