From 1031a3ae4bd1f0bd260a63d464135b1125c44f80 Mon Sep 17 00:00:00 2001
From: gcornut <guillaume.cornut@inra.fr>
Date: Thu, 21 Feb 2019 16:56:25 +0100
Subject: [PATCH 1/6] fix: Use font awesome spinner everywhere. #5

---
 ...STS-HeadlessChrome_0.0.0_(Linux_0.0.0).xml |  0
 .../germplasm-card.component.html             |  2 +-
 .../loading-spinner.component.html            | 15 ---
 .../loading-spinner.component.scss            | 96 -------------------
 .../loading-spinner.component.spec.ts         | 25 -----
 .../loading-spinner.component.ts              | 20 ++--
 .../result-page/result-page.component.html    | 17 +---
 .../app/site-card/site-card.component.html    |  2 +-
 .../app/study-card/study-card.component.html  |  2 +-
 frontend/src/assets/gpds/theme.scss           | 12 ---
 frontend/src/styles.scss                      |  7 --
 11 files changed, 16 insertions(+), 182 deletions(-)
 delete mode 100644 frontend/karma-junit-tests-report/TESTS-HeadlessChrome_0.0.0_(Linux_0.0.0).xml
 delete mode 100644 frontend/src/app/loading-spinner/loading-spinner.component.html
 delete mode 100644 frontend/src/app/loading-spinner/loading-spinner.component.scss
 delete mode 100644 frontend/src/app/loading-spinner/loading-spinner.component.spec.ts

diff --git a/frontend/karma-junit-tests-report/TESTS-HeadlessChrome_0.0.0_(Linux_0.0.0).xml b/frontend/karma-junit-tests-report/TESTS-HeadlessChrome_0.0.0_(Linux_0.0.0).xml
deleted file mode 100644
index e69de29b..00000000
diff --git a/frontend/src/app/germplasm-card/germplasm-card.component.html b/frontend/src/app/germplasm-card/germplasm-card.component.html
index 5fa9f5eb..80c26df8 100644
--- a/frontend/src/app/germplasm-card/germplasm-card.component.html
+++ b/frontend/src/app/germplasm-card/germplasm-card.component.html
@@ -1,4 +1,4 @@
-<gpds-loading-spinner [loading]="loading" class="display-spinner-front rounded"></gpds-loading-spinner>
+<gpds-loading-spinner [loading]="loading" class="float-right"></gpds-loading-spinner>
 
 <ng-container *ngIf="germplasmGnpis">
   <h3>
diff --git a/frontend/src/app/loading-spinner/loading-spinner.component.html b/frontend/src/app/loading-spinner/loading-spinner.component.html
deleted file mode 100644
index 01060a7d..00000000
--- a/frontend/src/app/loading-spinner/loading-spinner.component.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!-- Loading (see CSS for animation)-->
-<div *ngIf="loading" class="lds-spinner mx-auto">
-  <div></div>
-  <div></div>
-  <div></div>
-  <div></div>
-  <div></div>
-  <div></div>
-  <div></div>
-  <div></div>
-  <div></div>
-  <div></div>
-  <div></div>
-  <div></div>
-</div>
diff --git a/frontend/src/app/loading-spinner/loading-spinner.component.scss b/frontend/src/app/loading-spinner/loading-spinner.component.scss
deleted file mode 100644
index 3c571d09..00000000
--- a/frontend/src/app/loading-spinner/loading-spinner.component.scss
+++ /dev/null
@@ -1,96 +0,0 @@
-// Loading spinner
-.lds-spinner {
-  $scale: 1.2;
-
-  color: black;
-  display: block;
-  position: relative;
-  width: 64px * $scale;
-  height: 64px * $scale;
-
-  div {
-    transform-origin: 32px * $scale 32px * $scale;
-    animation: lds-spinner 1.2s linear infinite;
-  }
-
-  div:after {
-    content: " ";
-    display: block;
-    position: absolute;
-    top: 3px;
-    left: 29px * $scale;
-    width: 5px * $scale;
-    height: 14px * $scale;
-    border-radius: 20%;
-    background: black;
-  }
-
-  div:nth-child(1) {
-    transform: rotate(0deg);
-    animation-delay: -1.1s;
-  }
-
-  div:nth-child(2) {
-    transform: rotate(30deg);
-    animation-delay: -1s;
-  }
-
-  div:nth-child(3) {
-    transform: rotate(60deg);
-    animation-delay: -0.9s;
-  }
-
-  div:nth-child(4) {
-    transform: rotate(90deg);
-    animation-delay: -0.8s;
-  }
-
-  div:nth-child(5) {
-    transform: rotate(120deg);
-    animation-delay: -0.7s;
-  }
-
-  div:nth-child(6) {
-    transform: rotate(150deg);
-    animation-delay: -0.6s;
-  }
-
-  div:nth-child(7) {
-    transform: rotate(180deg);
-    animation-delay: -0.5s;
-  }
-
-  div:nth-child(8) {
-    transform: rotate(210deg);
-    animation-delay: -0.4s;
-  }
-
-  div:nth-child(9) {
-    transform: rotate(240deg);
-    animation-delay: -0.3s;
-  }
-
-  div:nth-child(10) {
-    transform: rotate(270deg);
-    animation-delay: -0.2s;
-  }
-
-  div:nth-child(11) {
-    transform: rotate(300deg);
-    animation-delay: -0.1s;
-  }
-
-  div:nth-child(12) {
-    transform: rotate(330deg);
-    animation-delay: 0s;
-  }
-}
-
-@keyframes lds-spinner {
-  0% {
-    opacity: 1;
-  }
-  100% {
-    opacity: 0;
-  }
-}
diff --git a/frontend/src/app/loading-spinner/loading-spinner.component.spec.ts b/frontend/src/app/loading-spinner/loading-spinner.component.spec.ts
deleted file mode 100644
index 88109e14..00000000
--- a/frontend/src/app/loading-spinner/loading-spinner.component.spec.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { LoadingSpinnerComponent } from './loading-spinner.component';
-
-describe('LoadingSpinnerComponent', () => {
-    let component: LoadingSpinnerComponent;
-    let fixture: ComponentFixture<LoadingSpinnerComponent>;
-
-    beforeEach(async(() => {
-        TestBed.configureTestingModule({
-            declarations: [LoadingSpinnerComponent]
-        })
-            .compileComponents();
-    }));
-
-    beforeEach(() => {
-        fixture = TestBed.createComponent(LoadingSpinnerComponent);
-        component = fixture.componentInstance;
-        fixture.detectChanges();
-    });
-
-    it('should create', () => {
-        expect(component).toBeTruthy();
-    });
-});
diff --git a/frontend/src/app/loading-spinner/loading-spinner.component.ts b/frontend/src/app/loading-spinner/loading-spinner.component.ts
index 24cb5902..62e34fb2 100644
--- a/frontend/src/app/loading-spinner/loading-spinner.component.ts
+++ b/frontend/src/app/loading-spinner/loading-spinner.component.ts
@@ -1,18 +1,18 @@
-import { Component, Input, OnInit } from '@angular/core';
+import { Component, Input } from '@angular/core';
 
 @Component({
     selector: 'gpds-loading-spinner',
-    templateUrl: './loading-spinner.component.html',
-    styleUrls: ['./loading-spinner.component.scss']
+    template: `
+        <i *ngIf="loading" class="fa fa-spin fa-spinner" title="Loading..."></i>
+    `,
+    styles: [`
+        i {
+            font-size: 2.2em;
+        }
+    `]
 })
-export class LoadingSpinnerComponent implements OnInit {
+export class LoadingSpinnerComponent {
 
     @Input() loading: boolean;
 
-    constructor() {
-    }
-
-    ngOnInit() {
-    }
-
 }
diff --git a/frontend/src/app/result-page/result-page.component.html b/frontend/src/app/result-page/result-page.component.html
index e846e026..b8bd5aad 100644
--- a/frontend/src/app/result-page/result-page.component.html
+++ b/frontend/src/app/result-page/result-page.component.html
@@ -31,20 +31,9 @@
 
   <!-- Column for result -->
   <div class="col-lg-9">
-    <!-- Loading (see CSS for animation)-->
-    <div *ngIf="loading" class="lds-spinner mx-auto" >
-      <div></div>
-      <div></div>
-      <div></div>
-      <div></div>
-      <div></div>
-      <div></div>
-      <div></div>
-      <div></div>
-      <div></div>
-      <div></div>
-      <div></div>
-      <div></div>
+    <!-- Loading spinner-->
+    <div class="text-center">
+      <gpds-loading-spinner [loading]="loading"></gpds-loading-spinner>
     </div>
 
     <!-- No criteria selected -->
diff --git a/frontend/src/app/site-card/site-card.component.html b/frontend/src/app/site-card/site-card.component.html
index f30867c6..2bfeb13c 100644
--- a/frontend/src/app/site-card/site-card.component.html
+++ b/frontend/src/app/site-card/site-card.component.html
@@ -1,4 +1,4 @@
-<gpds-loading-spinner [loading]="loading"></gpds-loading-spinner>
+<gpds-loading-spinner [loading]="loading" class="float-right"></gpds-loading-spinner>
 
 <ng-container *ngIf="location">
   <h3>
diff --git a/frontend/src/app/study-card/study-card.component.html b/frontend/src/app/study-card/study-card.component.html
index c010cf49..f12d1173 100644
--- a/frontend/src/app/study-card/study-card.component.html
+++ b/frontend/src/app/study-card/study-card.component.html
@@ -1,4 +1,4 @@
-<gpds-loading-spinner class="display-spinner-front rounded" [loading]="loading"></gpds-loading-spinner>
+<gpds-loading-spinner [loading]="loading" class="float-right"></gpds-loading-spinner>
 
 <ng-container *ngIf="study">
   <h3>
diff --git a/frontend/src/assets/gpds/theme.scss b/frontend/src/assets/gpds/theme.scss
index ad6939cc..286d878c 100644
--- a/frontend/src/assets/gpds/theme.scss
+++ b/frontend/src/assets/gpds/theme.scss
@@ -71,18 +71,6 @@ h4 {
   text-overflow: ellipsis;
 }
 
-.field {
-
-}
-
-.fieldName {
-
-}
-
-.headerTitle {
-
-}
-
 // custom button
 $theme-btn-color: $white;
 $theme-btn-bg-color: $_theme-black;
diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss
index 2af3b3a2..b7abb8b9 100644
--- a/frontend/src/styles.scss
+++ b/frontend/src/styles.scss
@@ -13,10 +13,3 @@ $fa-font-path: '~font-awesome/fonts';
 a {
   text-decoration: underline;
 }
-
-.display-spinner-front {
-  position: absolute;
-  top: 70px;
-  left: 720px;
-  background-color: #F9F9F9;
-}
-- 
GitLab


From 12abccb99e7c24c56c7853e821ec7f74cbd65b71 Mon Sep 17 00:00:00 2001
From: gcornut <guillaume.cornut@inra.fr>
Date: Thu, 21 Feb 2019 16:56:55 +0100
Subject: [PATCH 2/6] fix: Minor map component fixes. #5

---
 frontend/src/app/map/map.component.ts | 36 +++++++++++++++------------
 1 file changed, 20 insertions(+), 16 deletions(-)

diff --git a/frontend/src/app/map/map.component.ts b/frontend/src/app/map/map.component.ts
index 23d72dcb..df746dd8 100644
--- a/frontend/src/app/map/map.component.ts
+++ b/frontend/src/app/map/map.component.ts
@@ -16,33 +16,37 @@ export class MapComponent implements OnInit {
     }
 
     ngOnInit() {
-        // initialize map centered on the first site
-        const firstLocation: BrapiLocation = this.locations[0];
         const container = L.DomUtil.get('map');
         if (container) {
-            const map = L.map('map').setView([firstLocation.latitude, firstLocation.longitude], 5);
+            const map = L.map('map');
+
+            // initialize map centered on the first site
+            const firstLocation: BrapiLocation = this.locations[0];
+            if (firstLocation) {
+                map.setView([firstLocation.latitude, firstLocation.longitude], 5);
+            }
+
             L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
                 attribution: 'Tiles &copy; Esri &mdash; Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, ' +
                     'Esri China (Hong Kong), Esri (Thailand), TomTom, 2012'
             }).addTo(map);
             // add markers for all locations using markercluster plugin
             const markers = new MarkerClusterGroup();
-            for (const site of this.locations) {
+            for (const location of this.locations) {
                 const icon = L.icon({
-                    iconUrl: this.getMarkerIconUrl(site)
+                    iconUrl: this.getMarkerIconUrl(location)
                 });
-                let iconText: string = '<b>' + site.locationName + '</b><br/>';
-                iconText += site.locationType + '<br/>';
-                iconText += `<a href="sites/${site.locationDbId}">Details</a>`;
-                markers.addLayer(L.marker(
-                    [site.latitude, site.longitude],
-                    { icon: icon }
-                    ).bindPopup(iconText)
+                const popupText = `
+                  <b>${location.locationName}</b><br/>
+                  ${location.locationType}<br/>
+                  <a href="sites/${location.locationDbId}">Details</a>
+                `;
+                markers.addLayer(
+                    L.marker(
+                        [location.latitude, location.longitude],
+                        { icon: icon }
+                    ).bindPopup(popupText)
                 );
-                /* L.marker(
-                    [site.result.latitude, site.result.longitude],
-                    { icon: icon }
-                ).bindPopup(iconText).addTo(map); */
             }
             map.addLayer(markers);
         }
-- 
GitLab


From a0c736619936cd98d71a88a724e8e1ba2e1b3d47 Mon Sep 17 00:00:00 2001
From: gcornut <guillaume.cornut@inra.fr>
Date: Thu, 21 Feb 2019 18:25:11 +0100
Subject: [PATCH 3/6] fix: Fix loading spinner still displaying after an error
 occurred. Add back to previous page button in error message.

---
 frontend/src/app/error/error.component.html   | 27 +++++++++++--------
 .../src/app/error/error.component.spec.ts     |  4 ++-
 frontend/src/app/error/error.component.ts     | 16 ++++++++---
 .../loading-spinner.component.ts              | 14 ++++++++--
 4 files changed, 44 insertions(+), 17 deletions(-)

diff --git a/frontend/src/app/error/error.component.html b/frontend/src/app/error/error.component.html
index a52b913f..f5ffeccf 100644
--- a/frontend/src/app/error/error.component.html
+++ b/frontend/src/app/error/error.component.html
@@ -6,19 +6,24 @@
     <div>
       <p>
         An unexpected error occurred.<br/>
-        <a href="javascript:window.location.reload(true)">
-          Refresh the page
-        </a>
-        or try later.
-        <br/>
+        <a href="javascript:window.location.reload(true)">Refresh the page</a>
+        <ng-container *ngIf="canGoBack()">
+          , <a (click)="goBack();$event.preventDefault()" href="#">go back to the last page</a>
+        </ng-container>
+        or retry later.
+      </p>
+      <p>
         Please contact <a href="mailto:urgi-support@inra.fr">urgi-support@inra.fr</a> if the problem persists.
       </p>
-      <small *ngIf="error.status" id="error-status">
-        Status: {{ error.status }}
-      </small>
-      <small *ngIf="error.message" id="error-message">
-        Message: {{ error.message }}
-      </small>
+      <code *ngIf="error.status || error.message">
+        <span *ngIf="error.status" id="error-status">
+          Status: {{ error.status }}
+        </span>
+        <span *ngIf="error.message" id="error-message">
+          <br/>
+          Message: {{ error.message }}
+        </span>
+      </code>
     </div>
   </div>
 </div>
diff --git a/frontend/src/app/error/error.component.spec.ts b/frontend/src/app/error/error.component.spec.ts
index 07d9e977..d6debb1a 100644
--- a/frontend/src/app/error/error.component.spec.ts
+++ b/frontend/src/app/error/error.component.spec.ts
@@ -4,6 +4,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { Subject } from 'rxjs';
 import { ComponentTester, speculoosMatchers } from 'ngx-speculoos';
 
+import { Location } from '@angular/common';
 import { ErrorComponent } from './error.component';
 import { ErrorInterceptorService, HttpError } from '../error-interceptor.service';
 
@@ -38,7 +39,8 @@ describe('ErrorComponent', () => {
         TestBed.configureTestingModule({
             declarations: [ ErrorComponent ],
             providers: [
-                { provide: Router, useValue: { events: routerEvents } }
+                { provide: Router, useValue: { events: routerEvents } },
+                { provide: Location, useValue: { back: () => {} } }
             ],
             imports: [
                 HttpClientTestingModule
diff --git a/frontend/src/app/error/error.component.ts b/frontend/src/app/error/error.component.ts
index b19ff3b9..c93c9790 100644
--- a/frontend/src/app/error/error.component.ts
+++ b/frontend/src/app/error/error.component.ts
@@ -3,7 +3,7 @@ import { merge, Observable } from 'rxjs';
 import { ErrorInterceptorService, HttpError } from '../error-interceptor.service';
 import { NavigationEnd, Router } from '@angular/router';
 import { filter, map } from 'rxjs/operators';
-
+import { Location } from '@angular/common';
 
 @Component({
     selector: 'gpds-error',
@@ -14,8 +14,11 @@ export class ErrorComponent {
 
     error$: Observable<HttpError | null>;
 
-    constructor(private router: Router,
-                private errorInterceptor: ErrorInterceptorService) {
+    constructor(
+        private router: Router,
+        private errorInterceptor: ErrorInterceptorService,
+        private location: Location
+    ) {
         this.error$ = merge(
             this.errorInterceptor.getErrors(),
             this.router.events.pipe(
@@ -25,4 +28,11 @@ export class ErrorComponent {
         );
     }
 
+    canGoBack(): boolean {
+        return window.history.length > 1;
+    }
+
+    goBack(): void {
+        this.location.back();
+    }
 }
diff --git a/frontend/src/app/loading-spinner/loading-spinner.component.ts b/frontend/src/app/loading-spinner/loading-spinner.component.ts
index 62e34fb2..3f8f6df4 100644
--- a/frontend/src/app/loading-spinner/loading-spinner.component.ts
+++ b/frontend/src/app/loading-spinner/loading-spinner.component.ts
@@ -1,4 +1,5 @@
-import { Component, Input } from '@angular/core';
+import { Component, Input, OnInit } from '@angular/core';
+import { ErrorInterceptorService } from '../error-interceptor.service';
 
 @Component({
     selector: 'gpds-loading-spinner',
@@ -11,8 +12,17 @@ import { Component, Input } from '@angular/core';
         }
     `]
 })
-export class LoadingSpinnerComponent {
+export class LoadingSpinnerComponent implements OnInit {
 
     @Input() loading: boolean;
 
+    constructor(private errorService: ErrorInterceptorService) {
+    }
+
+    ngOnInit(): void {
+        // Force loading stop when an error is intercepted
+        this.errorService.getErrors().subscribe(() => {
+            this.loading = false;
+        });
+    }
 }
-- 
GitLab


From 26ec118010b3fa2ba18a6d07639a641cf38e50d7 Mon Sep 17 00:00:00 2001
From: gcornut <guillaume.cornut@inra.fr>
Date: Thu, 21 Feb 2019 19:03:16 +0100
Subject: [PATCH 4/6] fix: Fix backend code analysis warning. #5

---
 .../gpds/elasticsearch/document/DocumentAnnotationUtil.java | 2 +-
 .../repository/es/GermplasmAttributeRepositoryImpl.java     | 5 +----
 .../main/java/fr/inra/urgi/gpds/utils/StringFunctions.java  | 6 +++---
 .../fr/inra/urgi/gpds/domain/brapi/v1/BrapiMappingTest.java | 2 ++
 .../elasticsearch/query/impl/ESGenericQueryFactoryTest.java | 1 +
 .../urgi/gpds/repository/es/LocationRepositoryTest.java     | 2 ++
 .../java/fr/inra/urgi/gpds/repository/es/setup/ESSetUp.java | 1 +
 7 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/backend/src/main/java/fr/inra/urgi/gpds/elasticsearch/document/DocumentAnnotationUtil.java b/backend/src/main/java/fr/inra/urgi/gpds/elasticsearch/document/DocumentAnnotationUtil.java
index 0ee1ad94..21878643 100644
--- a/backend/src/main/java/fr/inra/urgi/gpds/elasticsearch/document/DocumentAnnotationUtil.java
+++ b/backend/src/main/java/fr/inra/urgi/gpds/elasticsearch/document/DocumentAnnotationUtil.java
@@ -26,7 +26,7 @@ public class DocumentAnnotationUtil {
      * and {@link Nested} annotations
      */
     public static <VO> DocumentMetadata<VO> getDocumentObjectMetadata(Class<VO> valueObjectClass) {
-        DocumentMetadata<VO> metadata = metadataCache.get(valueObjectClass);
+        DocumentMetadata metadata = metadataCache.get(valueObjectClass);
         if (metadata == null) {
             Document document = valueObjectClass.getAnnotation(Document.class);
             String valueObjectName = valueObjectClass.getSimpleName();
diff --git a/backend/src/main/java/fr/inra/urgi/gpds/repository/es/GermplasmAttributeRepositoryImpl.java b/backend/src/main/java/fr/inra/urgi/gpds/repository/es/GermplasmAttributeRepositoryImpl.java
index e67b06fd..af034811 100644
--- a/backend/src/main/java/fr/inra/urgi/gpds/repository/es/GermplasmAttributeRepositoryImpl.java
+++ b/backend/src/main/java/fr/inra/urgi/gpds/repository/es/GermplasmAttributeRepositoryImpl.java
@@ -18,8 +18,6 @@ import org.springframework.stereotype.Repository;
 public class GermplasmAttributeRepositoryImpl
     implements GermplasmAttributeRepository {
 
-    private final ESResponseParser parser;
-
     private ESFindRepository<GermplasmAttributeCriteria, GermplasmAttributeValueListVO> findAttributeRepository;
 
     @Autowired
@@ -28,9 +26,8 @@ public class GermplasmAttributeRepositoryImpl
         RestHighLevelClient client,
         ESRequestFactory requestFactory
     ) {
-        this.parser = parser;
         Class<GermplasmAttributeValueListVO> voClass = GermplasmAttributeValueListVO.class;
-        findAttributeRepository = new ESGenericFindRepository<>(client, requestFactory, voClass, this.parser);
+        findAttributeRepository = new ESGenericFindRepository<>(client, requestFactory, voClass, parser);
     }
 
     @Override
diff --git a/backend/src/main/java/fr/inra/urgi/gpds/utils/StringFunctions.java b/backend/src/main/java/fr/inra/urgi/gpds/utils/StringFunctions.java
index b7852246..6ade272f 100644
--- a/backend/src/main/java/fr/inra/urgi/gpds/utils/StringFunctions.java
+++ b/backend/src/main/java/fr/inra/urgi/gpds/utils/StringFunctions.java
@@ -1,6 +1,6 @@
 package fr.inra.urgi.gpds.utils;
 
-import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
 
 /**
  * @author gcornut
@@ -12,11 +12,11 @@ public final class StringFunctions {
     /**
      * Fixes identifier encoding (in case we have accents in it)
      */
-    public static String asUTF8(String string) throws UnsupportedEncodingException {
+    public static String asUTF8(String string) {
         if (string == null) {
             return null;
         }
-        return new String(string.getBytes("ISO-8859-1"), "UTF-8");
+        return new String(string.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
     }
 }
 
diff --git a/backend/src/test/java/fr/inra/urgi/gpds/domain/brapi/v1/BrapiMappingTest.java b/backend/src/test/java/fr/inra/urgi/gpds/domain/brapi/v1/BrapiMappingTest.java
index 744c9ab5..2b2424ce 100644
--- a/backend/src/test/java/fr/inra/urgi/gpds/domain/brapi/v1/BrapiMappingTest.java
+++ b/backend/src/test/java/fr/inra/urgi/gpds/domain/brapi/v1/BrapiMappingTest.java
@@ -85,6 +85,7 @@ class BrapiMappingTest {
         ESGenericQueryFactoryTest.assertJsonEquals(actualJson, expectedJson);
     }
 
+    @SuppressWarnings("unchecked")
     @Test
     void should_Deserialize_Additional_Info() throws IOException {
         String json = additionalInfoExample;
@@ -138,6 +139,7 @@ class BrapiMappingTest {
         ESGenericQueryFactoryTest.assertJsonEquals(actualJson, expectedJson);
     }
 
+    @SuppressWarnings("unchecked")
     @Test
     void should_Deserialize_Complex_Brapi_Object() throws IOException {
         BrapiTrial trial = mapper.readValue(trialExample, TrialVO.class);
diff --git a/backend/src/test/java/fr/inra/urgi/gpds/elasticsearch/query/impl/ESGenericQueryFactoryTest.java b/backend/src/test/java/fr/inra/urgi/gpds/elasticsearch/query/impl/ESGenericQueryFactoryTest.java
index c0bc3f5c..1f5df4ed 100644
--- a/backend/src/test/java/fr/inra/urgi/gpds/elasticsearch/query/impl/ESGenericQueryFactoryTest.java
+++ b/backend/src/test/java/fr/inra/urgi/gpds/elasticsearch/query/impl/ESGenericQueryFactoryTest.java
@@ -331,6 +331,7 @@ public class ESGenericQueryFactoryTest {
     /**
      * Read package resource in a String
      */
+    @SuppressWarnings("UnstableApiUsage")
     private String readResource(String path) {
         try {
             return CharStreams.toString(new InputStreamReader(getClass().getResourceAsStream(path)));
diff --git a/backend/src/test/java/fr/inra/urgi/gpds/repository/es/LocationRepositoryTest.java b/backend/src/test/java/fr/inra/urgi/gpds/repository/es/LocationRepositoryTest.java
index 58ed8232..b45c4e96 100644
--- a/backend/src/test/java/fr/inra/urgi/gpds/repository/es/LocationRepositoryTest.java
+++ b/backend/src/test/java/fr/inra/urgi/gpds/repository/es/LocationRepositoryTest.java
@@ -59,6 +59,7 @@ class LocationRepositoryTest {
         assertThat(result.getLocationName()).isNotBlank().isEqualTo(result.getName());
     }
 
+    @SuppressWarnings("deprecation")
     @Test
     void should_Have_Same_Abbreviation_And_Abreviation() {
         // 805
@@ -72,6 +73,7 @@ class LocationRepositoryTest {
         assertThat(result.getAbbreviation()).isNotBlank().isEqualTo(result.getAbreviation());
     }
 
+    @SuppressWarnings("deprecation")
     @Test
     void should_Have_Same_InstitutionAddress_And_InstitutionAdress() {
         // 805
diff --git a/backend/src/test/java/fr/inra/urgi/gpds/repository/es/setup/ESSetUp.java b/backend/src/test/java/fr/inra/urgi/gpds/repository/es/setup/ESSetUp.java
index abe5e757..7f38d301 100644
--- a/backend/src/test/java/fr/inra/urgi/gpds/repository/es/setup/ESSetUp.java
+++ b/backend/src/test/java/fr/inra/urgi/gpds/repository/es/setup/ESSetUp.java
@@ -167,6 +167,7 @@ public class ESSetUp {
     /**
      * Read package resource in a String
      */
+    @SuppressWarnings("UnstableApiUsage")
     private String readResource(String path) {
         try {
             return CharStreams.toString(new InputStreamReader(getClass().getResourceAsStream(path)));
-- 
GitLab


From 4a2e0782863c7e1f27a54cd5448e6e93784c5af8 Mon Sep 17 00:00:00 2001
From: gcornut <guillaume.cornut@inra.fr>
Date: Thu, 21 Feb 2019 19:05:07 +0100
Subject: [PATCH 5/6] feat: Animate an opacity pulse on the loading spinner. #5

---
 .../loading-spinner.component.scss             | 18 ++++++++++++++++++
 .../loading-spinner.component.ts               | 10 ++++------
 2 files changed, 22 insertions(+), 6 deletions(-)
 create mode 100644 frontend/src/app/loading-spinner/loading-spinner.component.scss

diff --git a/frontend/src/app/loading-spinner/loading-spinner.component.scss b/frontend/src/app/loading-spinner/loading-spinner.component.scss
new file mode 100644
index 00000000..6a74cd33
--- /dev/null
+++ b/frontend/src/app/loading-spinner/loading-spinner.component.scss
@@ -0,0 +1,18 @@
+
+.loading {
+  display: inline-block;
+  animation: opacityPulse 1.5s infinite ease-in-out;
+
+  i {
+    color: black;
+    font-size: 2.2em;
+  }
+}
+
+$from: 0.3;
+$to: 1;
+@keyframes opacityPulse {
+  0% { opacity: $from; }
+  50% { opacity: $to; }
+  100% { opacity: $from; }
+}
diff --git a/frontend/src/app/loading-spinner/loading-spinner.component.ts b/frontend/src/app/loading-spinner/loading-spinner.component.ts
index 3f8f6df4..b0c1cef4 100644
--- a/frontend/src/app/loading-spinner/loading-spinner.component.ts
+++ b/frontend/src/app/loading-spinner/loading-spinner.component.ts
@@ -4,13 +4,11 @@ import { ErrorInterceptorService } from '../error-interceptor.service';
 @Component({
     selector: 'gpds-loading-spinner',
     template: `
-        <i *ngIf="loading" class="fa fa-spin fa-spinner" title="Loading..."></i>
+        <div *ngIf="loading" class="loading">
+          <i class="fa fa-spin fa-spinner" title="Loading..."></i>
+        </div>
     `,
-    styles: [`
-        i {
-            font-size: 2.2em;
-        }
-    `]
+    styleUrls: ['./loading-spinner.component.scss']
 })
 export class LoadingSpinnerComponent implements OnInit {
 
-- 
GitLab


From 69be1f2ea0c7231999e9577d308ea34ce74b1d8d Mon Sep 17 00:00:00 2001
From: gcornut <guillaume.cornut@inra.fr>
Date: Thu, 21 Feb 2019 19:05:45 +0100
Subject: [PATCH 6/6] fix: Add KeyValueObject test. Minor fixes. #5

---
 .idea/modules/backend/gpds.backend.test.iml |  2 +-
 .idea/modules/frontend/gpds.frontend.iml    |  4 +++-
 frontend/src/app/utils.spec.ts              | 25 +++++++++++++++++++++
 frontend/src/app/utils.ts                   |  4 ++--
 4 files changed, 31 insertions(+), 4 deletions(-)
 create mode 100644 frontend/src/app/utils.spec.ts

diff --git a/.idea/modules/backend/gpds.backend.test.iml b/.idea/modules/backend/gpds.backend.test.iml
index fa6eb121..5f1aabfe 100644
--- a/.idea/modules/backend/gpds.backend.test.iml
+++ b/.idea/modules/backend/gpds.backend.test.iml
@@ -133,8 +133,8 @@
     <orderEntry type="library" name="Gradle: org.junit.platform:junit-platform-commons:1.3.2" level="project" />
     <orderEntry type="library" name="Gradle: org.apiguardian:apiguardian-api:1.0.0" level="project" />
     <orderEntry type="library" name="Gradle: org.opentest4j:opentest4j:1.1.1" level="project" />
-    <orderEntry type="library" scope="RUNTIME" name="Gradle: org.junit.platform:junit-platform-engine:1.3.2" level="project" />
     <orderEntry type="library" name="Gradle: javax.annotation:javax.annotation-api:1.3.2" level="project" />
+    <orderEntry type="library" scope="RUNTIME" name="Gradle: org.junit.platform:junit-platform-engine:1.3.2" level="project" />
     <orderEntry type="library" name="Gradle: org.apache.tomcat.embed:tomcat-embed-websocket:9.0.14" level="project" />
     <orderEntry type="library" name="Gradle: org.apache.tomcat.embed:tomcat-embed-core:9.0.14" level="project" />
     <orderEntry type="library" name="Gradle: org.apache.tomcat.embed:tomcat-embed-el:9.0.14" level="project" />
diff --git a/.idea/modules/frontend/gpds.frontend.iml b/.idea/modules/frontend/gpds.frontend.iml
index 58c4911e..95dbbe14 100644
--- a/.idea/modules/frontend/gpds.frontend.iml
+++ b/.idea/modules/frontend/gpds.frontend.iml
@@ -5,10 +5,12 @@
     <content url="file://$MODULE_DIR$/../../../frontend">
       <excludeFolder url="file://$MODULE_DIR$/../../../frontend/.gradle" />
       <excludeFolder url="file://$MODULE_DIR$/../../../frontend/build" />
+      <excludeFolder url="file://$MODULE_DIR$/../../../frontend/coverage" />
       <excludeFolder url="file://$MODULE_DIR$/../../../frontend/dist" />
+      <excludeFolder url="file://$MODULE_DIR$/../../../frontend/karma-junit-tests-report" />
       <excludeFolder url="file://$MODULE_DIR$/../../../frontend/tmp" />
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
   </component>
-</module>
+</module>
\ No newline at end of file
diff --git a/frontend/src/app/utils.spec.ts b/frontend/src/app/utils.spec.ts
new file mode 100644
index 00000000..104068ba
--- /dev/null
+++ b/frontend/src/app/utils.spec.ts
@@ -0,0 +1,25 @@
+import { KeyValueObject } from './utils';
+
+
+describe('KeyValueObject', () => {
+
+    it('should convert JS object to array of KeyValueObject', () => {
+        const actual = KeyValueObject.fromObject({
+            'a': '1',
+            'b': '2',
+            'c': null,
+            'd': '',
+            '': 'e',
+            'f': '3'
+        });
+
+        const expected = [
+            new KeyValueObject('a', '1'),
+            new KeyValueObject('b', '2'),
+            new KeyValueObject('f', '3'),
+        ];
+
+        expect(actual).toEqual(expected);
+    });
+
+});
diff --git a/frontend/src/app/utils.ts b/frontend/src/app/utils.ts
index 4ff7410e..939ddd37 100644
--- a/frontend/src/app/utils.ts
+++ b/frontend/src/app/utils.ts
@@ -1,4 +1,4 @@
-export function asArray(obj) {
+export function asArray<T>(obj: T | T[]): T[] {
     if (!obj) {
         return null;
     }
@@ -17,7 +17,7 @@ export class KeyValueObject {
         this.value = value;
     }
 
-    static fromObject(o: object): KeyValueObject[] {
+    static fromObject(o: { [key: string]: string }): KeyValueObject[] {
         return Object.entries(o)
             .filter(([key, value]) => !!key && !!value)
             .map(([key, value]) => new KeyValueObject(key, value));
-- 
GitLab