From 91f97ecc4317dc8b4ccb47cac898d608a11cfd00 Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Tue, 10 Nov 2020 08:57:19 +0100
Subject: [PATCH] bugfix: remove additional layers

---
 deploy/atlas/sanity.spec.js                   | 106 ++++++++++++++++++
 .../browsingForDatasets.prod.e2e-spec.js      |   2 +-
 e2e/src/selecting/atlas.prod.e2e-spec.js      |  56 ++++++++-
 e2e/src/selecting/region.prod.e2e-spec.js     |   4 +-
 e2e/util/selenium/iav.js                      |   2 +-
 e2e/util/selenium/layout.js                   |  40 ++++---
 src/res/ext/waxholmRatV2_0.json               |   2 +
 .../state/viewerState.store.helper.ts         |  29 +++--
 .../atlasLayerSelector.template.html          |  14 +--
 9 files changed, 217 insertions(+), 38 deletions(-)
 create mode 100644 deploy/atlas/sanity.spec.js

diff --git a/deploy/atlas/sanity.spec.js b/deploy/atlas/sanity.spec.js
new file mode 100644
index 000000000..21d4be22a
--- /dev/null
+++ b/deploy/atlas/sanity.spec.js
@@ -0,0 +1,106 @@
+const fs = require('fs')
+const path = require('path')
+const { promisify } = require('util')
+const asyncReadfile = promisify(fs.readFile)
+const { expect } = require('chai')
+const { pid } = require('process')
+const { assert } = require('console')
+
+const templateFiles = [
+  'bigbrain.json',
+  'colin.json',
+  'MNI152.json',
+  'waxholmRatV2_0.json',
+  'allenMouse.json'
+]
+
+const atlasFiles = [
+  'atlas_multiLevelHuman.json',
+  'atlas_waxholmRat.json',
+  'atlas_allenMouse.json'
+]
+
+const templateIdToParcsMap = new Map()
+const templateIdToTemplateMap = new Map()
+const parcIdToTemplateId = new Map()
+const parcIdToParcMap = new Map()
+
+const rootDir = path.join(__dirname, '../../src/res/ext')
+
+describe('> atlas sanity check', () => {
+  before(async () => {
+    for (const templateFile of templateFiles){
+      const txt = await asyncReadfile(
+        path.join(rootDir, templateFile),
+        'utf-8'
+      )
+      const template = JSON.parse(txt)
+      const { ['@id']: templateId, name: templateName, parcellations } = template
+      if (!templateId) throw new Error(`${templateFile} / ${templateName} / @id not defined`)
+      templateIdToTemplateMap.set(templateId, template)
+
+      for (const parc of parcellations) {
+        const { ['@id']: parcId, name: parcName } = parc
+        if (!parcId) throw new Error(`${templateFile} / ${parcName} /@id not defined`)
+        parcIdToParcMap.set(parcId, parc)
+
+        const arr = templateIdToParcsMap.get(templateId) || []
+        templateIdToParcsMap.set(templateId, arr.concat(parcId) )
+
+        const arr2 = parcIdToTemplateId.get(parcId) || []
+        parcIdToTemplateId.set(parcId, arr2.concat(templateId))
+      }
+    }
+  })
+
+  for (const atlas of atlasFiles) {
+    describe(`> checking ${atlas}`, () => {
+      
+      beforeEach(async () => {
+        const txt = await asyncReadfile(
+          path.join(rootDir, 'atlas', atlas)
+        )
+        const { ['@id']: atlasId, name: atlasName, templateSpaces, parcellations } = JSON.parse(txt)
+
+        describe(`> checking ${atlasName}`, () => {
+          describe('> checking template spaces', () => {
+            for (const { name: tName, ['@id']: tId, availableIn } of templateSpaces) {
+              describe(`> checking ${tName} with id ${tId}`, () => {
+                for (const { ['@id']: pId, name: pName } of availableIn) {
+                  describe(`> checking ${pName}, with id ${pId}`, () => {
+                    it('> template maps to parc', () => {
+                      const arr = templateIdToParcsMap.get(tId)
+                      const idx = arr.findIndex(id => id === pId)
+                      assert(
+                        idx >= 0,
+                        'entry can be found'
+                      )
+                      arr.splice(idx, 1)
+                    })
+
+                    it('> parc maps to tmpl', () => {
+                      
+                      const arr = parcIdToTemplateId.get(pId)
+                      if (!arr) throw new Error(`${pName} cannot be found`)
+                      const idx = arr.findIndex(id => id === tId)
+                      assert(
+                        idx >= 0,
+                        'entry can be found'
+                      )
+                      arr.splice(idx, 1)
+                    })
+                  })
+                }
+              })
+            }
+          })
+        })
+        
+      })
+
+      it('> dummy test', () => {
+        expect(true).to.equal(true)
+      })
+    })
+  }
+})
diff --git a/e2e/src/advanced/browsingForDatasets.prod.e2e-spec.js b/e2e/src/advanced/browsingForDatasets.prod.e2e-spec.js
index fba414f29..28900df32 100644
--- a/e2e/src/advanced/browsingForDatasets.prod.e2e-spec.js
+++ b/e2e/src/advanced/browsingForDatasets.prod.e2e-spec.js
@@ -75,7 +75,7 @@ describe('> dataset browser', () => {
     describe(`> in template: ${template}`, () => {
       beforeAll(async () => {
         await iavPage.goto()
-        await iavPage.selectAtlasTemplateParcellation(atlasName, template)
+        await iavPage.setAtlasSpecifications(atlasName, [ template ])
 
         // account for linear template translation backend
         await iavPage.wait(5000)
diff --git a/e2e/src/selecting/atlas.prod.e2e-spec.js b/e2e/src/selecting/atlas.prod.e2e-spec.js
index 75dac89ac..e5b703809 100644
--- a/e2e/src/selecting/atlas.prod.e2e-spec.js
+++ b/e2e/src/selecting/atlas.prod.e2e-spec.js
@@ -19,7 +19,7 @@ describe('> atlases are generally available', () => {
   })
 })
 
-describe('> generic atlaes behaviours', () => {
+describe('> generic atlas behaviours', () => {
   let atlasPage = new AtlasPage()
   beforeEach(async () => {
     atlasPage = new AtlasPage()
@@ -30,7 +30,8 @@ describe('> generic atlaes behaviours', () => {
       it('> on launch, shows atlas name as a pill', async () => {
         await atlasPage.goto()
         await atlasPage.clearAlerts()
-        await atlasPage.selectAtlasTemplateParcellation(atlas)
+        await atlasPage.setAtlasSpecifications(atlas)
+        await atlasPage.wait(500)
         await atlasPage.waitUntilAllChunksLoaded()
         const txtArr = await atlasPage.getAllChipsText()
         expect(
@@ -40,4 +41,53 @@ describe('> generic atlaes behaviours', () => {
       })
     })
   }
-})
\ No newline at end of file
+})
+
+describe('> in human multi level', () => {
+  let atlasPage = new AtlasPage()
+  beforeAll(async () => {
+    atlasPage = new AtlasPage()
+    await atlasPage.init()
+    await atlasPage.goto()
+    await atlasPage.clearAlerts()
+  })
+  describe('> removal of additional layers should restore base layer', () => {
+    it('> in mni152', async () => {
+      await atlasPage.setAtlasSpecifications(atlases[1], [`Fibre tracts`, `Short Bundle`])
+      await atlasPage.wait(500)
+      await atlasPage.waitForAsync()
+
+      const txtArr = await atlasPage.getAllChipsText()
+      expect(
+        txtArr.find(txt => /short bundle/i.test(txt))
+      ).toBeTruthy()
+
+      await atlasPage.clickChip(/short bundle/i, '.fa-times')
+
+      const txtArr2 = await atlasPage.getAllChipsText()
+      expect(
+        txtArr2.find(txt => /short bundle/i.test(txt))
+      ).toBeFalsy()
+      
+    })
+
+    it('> in bigbrain', async () => {
+      await atlasPage.setAtlasSpecifications(atlases[1], [/isocortex/i])
+      await atlasPage.wait(500)
+      await atlasPage.waitForAsync()
+
+      const txtArr = await atlasPage.getAllChipsText()
+      expect(
+        txtArr.find(txt => /isocortex/i.test(txt))
+      ).toBeTruthy()
+
+      await atlasPage.clickChip(/isocortex/i, '.fa-times')
+
+      const txtArr2 = await atlasPage.getAllChipsText()
+      expect(
+        txtArr2.find(txt => /isocortex/i.test(txt))
+      ).toBeFalsy()
+      
+    })
+  })
+})
diff --git a/e2e/src/selecting/region.prod.e2e-spec.js b/e2e/src/selecting/region.prod.e2e-spec.js
index d2f63699d..01b30f83e 100644
--- a/e2e/src/selecting/region.prod.e2e-spec.js
+++ b/e2e/src/selecting/region.prod.e2e-spec.js
@@ -14,7 +14,7 @@ describe('> selecting regions', () => {
       const newPage = new AtlasPage()
       await newPage.init()
       await newPage.goto()
-      await newPage.selectAtlasTemplateParcellation(duplicatedRegion.atlas, duplicatedRegion.template)
+      await newPage.setAtlasSpecifications(duplicatedRegion.atlas, [ duplicatedRegion.template ])
       await newPage.wait(500)
       await newPage.waitForAsync()
       await newPage.execScript(`interactiveViewer.viewerHandle.setNavigationLoc(${JSON.stringify(duplicatedRegion.position.map(v => v*1e6))}, true)`)
@@ -64,7 +64,7 @@ describe('> selecting regions', () => {
     })
 
     it('> on change atlas, multi region panel are dismissed', async () => {
-      await newPage.selectAtlasTemplateParcellation(humanAtlasName)
+      await newPage.setAtlasSpecifications(humanAtlasName)
       await newPage.wait(500)
       await newPage.waitForAsync()
 
diff --git a/e2e/util/selenium/iav.js b/e2e/util/selenium/iav.js
index 75f011dee..f93946027 100644
--- a/e2e/util/selenium/iav.js
+++ b/e2e/util/selenium/iav.js
@@ -39,7 +39,7 @@ class WdIavPage extends WdLayoutPage{
   }
 
   async selectDropdownTemplate(title) {
-    throw new Error(`selectDropdownTemplate has been deprecated. use changeTemplate instead`)
+    throw new Error(`selectDropdownTemplate has been deprecated. use setAtlasSpecifications instead`)
   }
 
   async _getSearchRegionInput(){
diff --git a/e2e/util/selenium/layout.js b/e2e/util/selenium/layout.js
index 99b7e97c6..e4a01a627 100644
--- a/e2e/util/selenium/layout.js
+++ b/e2e/util/selenium/layout.js
@@ -258,6 +258,20 @@ class WdLayoutPage extends WdBase{
     return visibility
   }
 
+  async clickChip(search, cssSelector) {
+    const allChips = await this._getChips()
+    const idx = await _getIndexFromArrayOfWebElements(search, allChips)
+    if (idx < 0) throw new Error(`clickChip ${search.toString()} not found`)
+    if (!cssSelector) {
+      return await allChips[idx].click()
+    }
+    const el = await allChips[idx].findElement(
+      By.css(cssSelector)
+    )
+    if (!el) throw new Error(`clickChip css selector ${cssSelector} not found`)
+    return await el.click()
+  }
+
   /**
    * Cards
    */
@@ -297,7 +311,7 @@ class WdLayoutPage extends WdBase{
   }
 
   async selectTitleTemplateParcellation(templateName, parcellationName){
-    throw new Error(`selectTitleTemplateParcellation has been deprecated. use selectAtlasTemplateParcellation`)
+    throw new Error(`selectTitleTemplateParcellation has been deprecated. use setAtlasSpecifications`)
   }
 
   /**
@@ -320,17 +334,15 @@ class WdLayoutPage extends WdBase{
     }
   }
 
-  async changeTemplate(templateName){
-    if (!templateName) throw new Error(`templateName needs to be provided`)
+  async selectTile(tileName){
+    if (!tileName) throw new Error(`tileName needs to be provided`)
     await this._setAtlasSelectorExpanded(true)
     await this.wait(1000)
-    const allTiles = await this._browser
-      .findElement( By.css('atlas-layer-selector') )
-      .findElements( By.css(`mat-grid-tile`) )
+    const allTiles = await this._browser.findElements( By.css(`mat-grid-tile`) )
 
-    const idx = await _getIndexFromArrayOfWebElements(templateName, allTiles)
+    const idx = await _getIndexFromArrayOfWebElements(tileName, allTiles)
     if (idx >= 0) await allTiles[idx].click()
-    else throw new Error(`#changeTemplate: templateName ${templateName} cannot be found.`)
+    else throw new Error(`#selectTile: tileName ${tileName} cannot be found.`)
   }
 
   async changeParc(parcName) {
@@ -341,7 +353,7 @@ class WdLayoutPage extends WdBase{
     throw new Error(`changeParcVersion NYI`)
   }
 
-  async selectAtlasTemplateParcellation(atlasName, templateName, parcellationName, parcVersion) {
+  async setAtlasSpecifications(atlasName, atlasSpecifications = [], parcVersion = null) {
     if (!atlasName) throw new Error(`atlasName needs to be provided`)
     try {
       /**
@@ -357,16 +369,10 @@ class WdLayoutPage extends WdBase{
       await this.selectDropdownOption(`[aria-label="${ARIA_LABELS.SELECT_ATLAS}"]`, atlasName)
     }
 
-    if (templateName) {
-      await this.wait(1000)
-      await this.waitUntilAllChunksLoaded()
-      await this.changeTemplate(templateName)
-    }
-    
-    if (parcellationName) {
+    for (const spec of atlasSpecifications) {
       await this.wait(1000)
       await this.waitUntilAllChunksLoaded()
-      await this.changeParc(parcellationName)
+      await this.selectTile(spec)
     }
 
     if (parcVersion) {
diff --git a/src/res/ext/waxholmRatV2_0.json b/src/res/ext/waxholmRatV2_0.json
index a6cd785fe..8c07984a4 100644
--- a/src/res/ext/waxholmRatV2_0.json
+++ b/src/res/ext/waxholmRatV2_0.json
@@ -13,6 +13,8 @@
   "parcellations": [
     {
       "ngId": "WHS_SD_rat_atlas_v4_beta",
+      "@id": "juelich/iav/atlas/v1.0.0/9",
+      "fullId": "juelich/iav/atlas/v1.0.0/9",
       "type": "parcellation",
       "name": "Waxholm Space rat brain atlas v4 beta",
       "regions": [
diff --git a/src/services/state/viewerState.store.helper.ts b/src/services/state/viewerState.store.helper.ts
index 16ee0b763..307ea047f 100644
--- a/src/services/state/viewerState.store.helper.ts
+++ b/src/services/state/viewerState.store.helper.ts
@@ -1,6 +1,6 @@
 // TODO merge with viewerstate.store.ts when refactor is done
-import { createReducer, on, ActionReducer, createSelector, Store, select } from "@ngrx/store";
-import { generalApplyState } from "../stateStore.helper";
+import { createReducer, on, ActionReducer, Store, select } from "@ngrx/store";
+import { generalActionError, generalApplyState } from "../stateStore.helper";
 import { Effect, Actions, ofType } from "@ngrx/effects";
 import { Observable } from "rxjs";
 import { withLatestFrom, map } from "rxjs/operators";
@@ -112,11 +112,26 @@ export class ViewerStateHelperEffect{
   @Effect()
   selectParcellationWithId$: Observable<any> = this.actions$.pipe(
     ofType(viewerStateRemoveAdditionalLayer.type),
-    withLatestFrom(this.store$.pipe(
-      select(viewerStateGetSelectedAtlas)
-    )),
-    map(([ { payload }, selectedAtlas ]) => {
-      const baseLayer = selectedAtlas['parcellations'].find(p => p['baseLayer'])
+    withLatestFrom(
+      this.store$.pipe(
+        select(viewerStateGetSelectedAtlas)
+      ),
+      this.store$.pipe(
+        select(viewerStateSelectedTemplateSelector)
+      )
+    ),
+    map(([ { payload }, selectedAtlas, selectedTemplate ]) => {
+      const tmpl = selectedAtlas['templateSpaces'].find(t => t['@id'] === selectedTemplate['@id'])
+      if (!tmpl) {
+        return generalActionError({
+          message: `templateSpace with id ${selectedTemplate['@id']} cannot be found in atlas with id ${selectedAtlas['@id']}`
+        })
+      }
+
+      const eligibleParcIdSet = new Set(
+        tmpl.availableIn.map(p => p['@id'])
+      )
+      const baseLayer = selectedAtlas['parcellations'].find(fullP => fullP['baseLayer'] && eligibleParcIdSet.has(fullP['@id']))
       return viewerStateHelperSelectParcellationWithId({ payload: baseLayer })
     })
   )
diff --git a/src/ui/atlasLayerSelector/atlasLayerSelector.template.html b/src/ui/atlasLayerSelector/atlasLayerSelector.template.html
index c3e55596b..8e1293981 100644
--- a/src/ui/atlasLayerSelector/atlasLayerSelector.template.html
+++ b/src/ui/atlasLayerSelector/atlasLayerSelector.template.html
@@ -171,12 +171,11 @@
     hasBackdrop="false">
 
     <ng-template matMenuContent let-layerGroupItems="layerGroupItems">
-
-        <div iav-stop="click"
-            class="d-flex flex-column align-items-center"
+        <mat-grid-list cols="1"
+            rowHeight="1:1"
+            iav-stop="click"
             (iav-outsideClick)="collapseExpandedGroup()">
-
-            <div class="single-column-tile" *ngFor="let layer of layerGroupItems">
+            <mat-grid-tile *ngFor="let layer of layerGroupItems">
 
                 <ng-container *ngTemplateOutlet="tileTmpl; context: {
                     tileSrc: layer,
@@ -186,7 +185,8 @@
                 } ">
 
                 </ng-container>
-            </div>
-        </div>
+            </mat-grid-tile>
+        </mat-grid-list>
+
     </ng-template>
 </mat-menu>
-- 
GitLab