From 4c11f518533ad4c5a5ffb1f1686206f3657b326f Mon Sep 17 00:00:00 2001
From: jdestin <jeremy.destin@inra.fr>
Date: Thu, 25 Jul 2019 12:26:39 +0200
Subject: [PATCH] feat: Add plantMaterial export button on germplasm card.
 GNP-5540.

---
 .../gnpis/v1/GnpISGermplasmController.java    | 12 ++-
 frontend/package-lock.json                    | 88 +++++++++++++++----
 frontend/package.json                         |  2 +
 .../germplasm-card.component.html             |  2 +
 .../germplasm-card.component.ts               | 22 ++++-
 frontend/src/app/gnpis.service.spec.ts        | 42 ++++++++-
 frontend/src/app/gnpis.service.ts             | 22 ++++-
 frontend/src/app/models/gnpis.model.ts        |  9 ++
 8 files changed, 169 insertions(+), 30 deletions(-)

diff --git a/backend/src/main/java/fr/inra/urgi/faidare/api/gnpis/v1/GnpISGermplasmController.java b/backend/src/main/java/fr/inra/urgi/faidare/api/gnpis/v1/GnpISGermplasmController.java
index 5b32adbb..210b50fe 100644
--- a/backend/src/main/java/fr/inra/urgi/faidare/api/gnpis/v1/GnpISGermplasmController.java
+++ b/backend/src/main/java/fr/inra/urgi/faidare/api/gnpis/v1/GnpISGermplasmController.java
@@ -12,16 +12,14 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.core.io.FileSystemResource;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
 import java.io.File;
 import java.util.Collections;
 
-import static org.springframework.web.bind.annotation.RequestMethod.GET;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
 
 @Api(tags = {"GnpIS API"}, description = "Extended GnpIS API")
 @RestController
@@ -89,8 +87,8 @@ public class GnpISGermplasmController {
      * resp.setContentType("application/zip");
      * </pre>
      */
-    @RequestMapping(value = "/csv", method = GET, produces = "text/csv")
-    public FileSystemResource export(GermplasmPOSTSearchCriteria criteria, HttpServletResponse response) {
+    @PostMapping(value = "/csv", produces = "text/csv", consumes = APPLICATION_JSON_VALUE)
+    public FileSystemResource export(@RequestBody @Valid GermplasmPOSTSearchCriteria criteria, HttpServletResponse response) {
         try {
             File exportFile = germplasmService.exportCSV(criteria);
             response.setHeader("Content-Disposition", "attachment; filename=germplasm.gnpis.csv");
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index aa2aa4ef..f3d2d502 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -821,6 +821,12 @@
         }
       }
     },
+    "@types/file-saver": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.1.tgz",
+      "integrity": "sha512-g1QUuhYVVAamfCifK7oB7G3aIl4BbOyzDOqVyUfEr4tfBKrXfeH+M+Tg7HKCXSrbzxYdhyCP7z9WbKo0R2hBCw==",
+      "dev": true
+    },
     "@types/geojson": {
       "version": "7946.0.6",
       "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.6.tgz",
@@ -1273,6 +1279,7 @@
       "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
       "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
       "dev": true,
+      "optional": true,
       "requires": {
         "delegates": "^1.0.0",
         "readable-stream": "^2.0.6"
@@ -2484,7 +2491,8 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
       "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
-      "dev": true
+      "dev": true,
+      "optional": true
     },
     "constants-browserify": {
       "version": "1.0.0",
@@ -2886,7 +2894,8 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
       "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
-      "dev": true
+      "dev": true,
+      "optional": true
     },
     "depd": {
       "version": "1.1.2",
@@ -3608,6 +3617,11 @@
         "schema-utils": "^1.0.0"
       }
     },
+    "file-saver": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz",
+      "integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw=="
+    },
     "fileset": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz",
@@ -3853,7 +3867,8 @@
         "ansi-regex": {
           "version": "2.1.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "aproba": {
           "version": "1.2.0",
@@ -3874,12 +3889,14 @@
         "balanced-match": {
           "version": "1.0.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -3894,17 +3911,20 @@
         "code-point-at": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "concat-map": {
           "version": "0.0.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "console-control-strings": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -4021,7 +4041,8 @@
         "inherits": {
           "version": "2.0.3",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "ini": {
           "version": "1.3.5",
@@ -4033,6 +4054,7 @@
           "version": "1.0.0",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -4047,6 +4069,7 @@
           "version": "3.0.4",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
@@ -4054,12 +4077,14 @@
         "minimist": {
           "version": "0.0.8",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "minipass": {
           "version": "2.3.5",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.2",
             "yallist": "^3.0.0"
@@ -4078,6 +4103,7 @@
           "version": "0.5.1",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -4158,7 +4184,8 @@
         "number-is-nan": {
           "version": "1.0.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -4170,6 +4197,7 @@
           "version": "1.4.0",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -4255,7 +4283,8 @@
         "safe-buffer": {
           "version": "5.1.2",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -4291,6 +4320,7 @@
           "version": "1.0.2",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -4310,6 +4340,7 @@
           "version": "3.0.1",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -4353,12 +4384,14 @@
         "wrappy": {
           "version": "1.0.2",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "yallist": {
           "version": "3.0.3",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         }
       }
     },
@@ -4367,6 +4400,7 @@
       "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
       "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
       "dev": true,
+      "optional": true,
       "requires": {
         "graceful-fs": "^4.1.2",
         "inherits": "~2.0.0",
@@ -4379,6 +4413,7 @@
       "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
       "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
       "dev": true,
+      "optional": true,
       "requires": {
         "aproba": "^1.0.3",
         "console-control-strings": "^1.0.0",
@@ -4416,7 +4451,8 @@
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
       "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
-      "dev": true
+      "dev": true,
+      "optional": true
     },
     "get-stream": {
       "version": "3.0.0",
@@ -4605,7 +4641,8 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
       "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
-      "dev": true
+      "dev": true,
+      "optional": true
     },
     "has-value": {
       "version": "1.0.0",
@@ -5325,7 +5362,8 @@
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
       "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
-      "dev": true
+      "dev": true,
+      "optional": true
     },
     "is-windows": {
       "version": "1.0.2",
@@ -5968,6 +6006,7 @@
       "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
       "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
       "dev": true,
+      "optional": true,
       "requires": {
         "graceful-fs": "^4.1.2",
         "parse-json": "^2.2.0",
@@ -5980,7 +6019,8 @@
           "version": "2.3.0",
           "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
           "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
-          "dev": true
+          "dev": true,
+          "optional": true
         }
       }
     },
@@ -6255,7 +6295,8 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
       "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
-      "dev": true
+      "dev": true,
+      "optional": true
     },
     "map-visit": {
       "version": "1.0.0",
@@ -6919,6 +6960,7 @@
       "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
       "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
       "dev": true,
+      "optional": true,
       "requires": {
         "are-we-there-yet": "~1.1.2",
         "console-control-strings": "~1.1.0",
@@ -7933,6 +7975,7 @@
       "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
       "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
       "dev": true,
+      "optional": true,
       "requires": {
         "load-json-file": "^1.0.0",
         "normalize-package-data": "^2.3.2",
@@ -7944,6 +7987,7 @@
           "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
           "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
           "dev": true,
+          "optional": true,
           "requires": {
             "graceful-fs": "^4.1.2",
             "pify": "^2.0.0",
@@ -7954,7 +7998,8 @@
           "version": "2.3.0",
           "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
           "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
-          "dev": true
+          "dev": true,
+          "optional": true
         }
       }
     },
@@ -7963,6 +8008,7 @@
       "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
       "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
       "dev": true,
+      "optional": true,
       "requires": {
         "find-up": "^1.0.0",
         "read-pkg": "^1.0.0"
@@ -7973,6 +8019,7 @@
           "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
           "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
           "dev": true,
+          "optional": true,
           "requires": {
             "path-exists": "^2.0.0",
             "pinkie-promise": "^2.0.0"
@@ -7983,6 +8030,7 @@
           "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
           "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
           "dev": true,
+          "optional": true,
           "requires": {
             "pinkie-promise": "^2.0.0"
           }
@@ -9263,6 +9311,7 @@
       "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
       "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
       "dev": true,
+      "optional": true,
       "requires": {
         "is-utf8": "^0.2.0"
       }
@@ -10625,6 +10674,7 @@
       "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
       "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
       "dev": true,
+      "optional": true,
       "requires": {
         "string-width": "^1.0.2 || 2"
       }
diff --git a/frontend/package.json b/frontend/package.json
index e4dc6e1e..d89b3596 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -28,6 +28,7 @@
     "angular-mocks": "1.7.8",
     "bootstrap": "4.1.3",
     "core-js": "2.5.7",
+    "file-saver": "2.0.2",
     "font-awesome": "4.7.0",
     "leaflet": "1.3.4",
     "leaflet.markercluster": "1.4.1",
@@ -46,6 +47,7 @@
     "@angular/cli": "7.3.4",
     "@angular/compiler-cli": "7.2.7",
     "@angular/language-service": "7.2.7",
+    "@types/file-saver": "2.0.1",
     "@types/jasmine": "2.8.12",
     "@types/jasminewd2": "2.0.6",
     "@types/node": "8.9.5",
diff --git a/frontend/src/app/germplasm-card/germplasm-card.component.html b/frontend/src/app/germplasm-card/germplasm-card.component.html
index 7d8b923b..4381eb02 100644
--- a/frontend/src/app/germplasm-card/germplasm-card.component.html
+++ b/frontend/src/app/germplasm-card/germplasm-card.component.html
@@ -13,6 +13,8 @@
 
   <div class="row align-items-center justify-content-center">
 
+    <button (click)="exportPlantMaterial(germplasmGnpis)">Export</button>
+
     <!--Templates for gerplasm card-->
 
     <ng-template #taxonTemplate>
diff --git a/frontend/src/app/germplasm-card/germplasm-card.component.ts b/frontend/src/app/germplasm-card/germplasm-card.component.ts
index 73616b70..842cf858 100644
--- a/frontend/src/app/germplasm-card/germplasm-card.component.ts
+++ b/frontend/src/app/germplasm-card/germplasm-card.component.ts
@@ -3,8 +3,9 @@ import { ActivatedRoute } from '@angular/router';
 import { BrapiService } from '../brapi.service';
 import { GnpisService } from '../gnpis.service';
 import { BrapiAttributeData, BrapiGermplasmPedigree, BrapiLocation, BrapiTaxonIds } from '../models/brapi.model';
-import { Children, Germplasm, Site } from '../models/gnpis.model';
+import { Children, Germplasm, GermplasmExportCriteria, Site } from '../models/gnpis.model';
 import { environment } from '../../environments/environment';
+import { saveAs } from 'file-saver';
 
 @Component({
     selector: 'faidare-germplasm-card',
@@ -231,4 +232,23 @@ export class GermplasmCardComponent implements OnInit {
         }
         return 0;
     }
+
+    exportPlantMaterial(germplasm: Germplasm) {
+        const germplasmExportCriteria: GermplasmExportCriteria = {
+            accessionNumbers: [germplasm.accessionNumber],
+            germplasmDbIds: [],
+            germplasmGenus: [],
+            germplasmNames: [],
+            germplasmPUIs: [germplasm.germplasmPUI],
+            germplasmSpecies: []
+        };
+        this.gnpisService.plantMaterialExport(germplasmExportCriteria).subscribe(
+            result => {
+                const blob = new Blob([result], { type: 'text/plain;charset=utf-8' });
+                saveAs(blob, 'germplasm.gnpis.csv');
+            },
+            error => {
+                console.log(error);
+            });
+    }
 }
diff --git a/frontend/src/app/gnpis.service.spec.ts b/frontend/src/app/gnpis.service.spec.ts
index 60327439..1213783d 100644
--- a/frontend/src/app/gnpis.service.spec.ts
+++ b/frontend/src/app/gnpis.service.spec.ts
@@ -3,7 +3,15 @@ import { BrapiMetaData, BrapiResults } from './models/brapi.model';
 import { DataDiscoveryCriteria, DataDiscoverySource } from './models/data-discovery.model';
 import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
 import { TestBed } from '@angular/core/testing';
-import { Donor, Germplasm, GermplasmInstitute, GermplasmSet, Institute, Site } from './models/gnpis.model';
+import {
+    Donor,
+    Germplasm,
+    GermplasmExportCriteria,
+    GermplasmInstitute,
+    GermplasmSet,
+    Institute,
+    Site
+} from './models/gnpis.model';
 
 describe('GnpisService', () => {
 
@@ -115,6 +123,25 @@ describe('GnpisService', () => {
         population: [germplasmSet]
     };
 
+    const germplasmExportCriteria: GermplasmExportCriteria = {
+        accessionNumbers: ['VCR010'],
+        germplasmDbIds: [],
+        germplasmGenus: [],
+        germplasmNames: [],
+        germplasmPUIs: [],
+        germplasmSpecies: []
+    };
+
+    const exportFile: string = '"DOI";"AccessionNumber";' +
+    '"AccessionName";"TaxonGroup";' +
+    '"HoldingInstitution";"LotName";' +
+    '"LotSynonym";' +
+    '"CollectionName";' +
+    '"CollectionType";' +
+    '"PanelName";' +
+    '"PanelSize"\n' +
+    '"https://germplasmdoi";"germplasm01";GermplasmTest;Pea;INRA-URGI;;;;;;';
+
     let gnpisService: GnpisService;
     let http: HttpTestingController;
     beforeEach(() => {
@@ -228,5 +255,18 @@ describe('GnpisService', () => {
             });
         });
     });
+
+    it('should export germplasm as PlantMaterial', () => {
+        gnpisService.plantMaterialExport(germplasmExportCriteria).subscribe(
+            plantMaterialExport => {
+                expect(plantMaterialExport).toEqual(exportFile);
+            }
+        );
+        const req = http.expectOne({
+            url: `${BASE_URL}/germplasm/csv`,
+            method: 'POST'
+        });
+        req.flush(exportFile);
+    });
 })
 ;
diff --git a/frontend/src/app/gnpis.service.ts b/frontend/src/app/gnpis.service.ts
index 0e91070d..4a3f30a2 100644
--- a/frontend/src/app/gnpis.service.ts
+++ b/frontend/src/app/gnpis.service.ts
@@ -1,10 +1,15 @@
 import { Injectable } from '@angular/core';
 import { Observable, ReplaySubject, zip } from 'rxjs';
 import { HttpClient } from '@angular/common/http';
-import { DataDiscoveryCriteria, DataDiscoveryFacet, DataDiscoveryResults, DataDiscoverySource } from './models/data-discovery.model';
+import {
+    DataDiscoveryCriteria,
+    DataDiscoveryFacet,
+    DataDiscoveryResults,
+    DataDiscoverySource
+} from './models/data-discovery.model';
 import { BrapiResults } from './models/brapi.model';
 import { map } from 'rxjs/operators';
-import { Germplasm } from './models/gnpis.model';
+import { Germplasm, GermplasmExportCriteria } from './models/gnpis.model';
 import { XrefResponse } from './models/xref.model';
 import { removeNullUndefined } from './utils';
 
@@ -118,4 +123,17 @@ export class GnpisService {
         return this.http.get<XrefResponse>(`${BASE_URL}/xref/documentbyfulltextid?linkedRessourcesID=${xrefId}`);
     }
 
+    // TODO Change return type
+    plantMaterialExport(criteria: GermplasmExportCriteria): Observable<any> {
+        const requestOptions: Object = {
+            /* other options here */
+            responseType: 'text'
+        };
+        return this.http.post<any>(
+            `${BASE_URL}/germplasm/csv`,
+            criteria,
+            requestOptions
+        );
+    }
+
 }
diff --git a/frontend/src/app/models/gnpis.model.ts b/frontend/src/app/models/gnpis.model.ts
index edee6ddf..5a8e4a6d 100644
--- a/frontend/src/app/models/gnpis.model.ts
+++ b/frontend/src/app/models/gnpis.model.ts
@@ -1,5 +1,14 @@
 import { BrapiDonor, BrapiGermplasm } from './brapi.model';
 
+export interface GermplasmExportCriteria {
+    accessionNumbers: string[];
+    germplasmDbIds: string[];
+    germplasmGenus: string[];
+    germplasmNames: string[];
+    germplasmPUIs: string[];
+    germplasmSpecies: string[];
+}
+
 export interface Germplasm extends BrapiGermplasm {
     genusSpecies: string;
     genusSpeciesSubtaxa: string;
-- 
GitLab