Commit 78ff876d authored by Maxim's avatar Maxim
Browse files

Explorer: Three "view modes"

parent 3e0e2c5f
......@@ -16,22 +16,8 @@
package org.genesys2.server.servlet.controller;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import javax.imageio.ImageIO;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jhlabs.image.MapColorsFilter;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.utils.URIBuilder;
......@@ -57,21 +43,26 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.facet.result.Term;
import org.springframework.data.elasticsearch.core.facet.result.TermResult;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jhlabs.image.MapColorsFilter;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.RequestContextUtils;
import javax.imageio.ImageIO;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.List;
@Controller
public class ExplorerController extends BaseController implements InitializingBean {
......@@ -160,11 +151,16 @@ public class ExplorerController extends BaseController implements InitializingBe
* @throws IOException
*/
@RequestMapping("/explore")
public String viewFiltered(HttpServletResponse response, ModelMap model, @RequestParam(value = "page", required = false, defaultValue = "1") int page,
public String viewFiltered(
HttpServletResponse response,
ModelMap model,
@RequestParam(value = "page", required = false, defaultValue = "1") int page,
@RequestParam(value = "filter", required = true, defaultValue = "{}") String jsonFilter,
@RequestParam(value = "results", required = true, defaultValue = "50") int results,
@RequestParam(value = "columns", required = true, defaultValue = "") String[] columns,
@CookieValue(value = "columns", required=false) String[] cookieColumns) throws IOException, SearchException {
@CookieValue(value = "columns", required = false) String[] cookieColumns
)
throws IOException, SearchException {
String[] selectedFilters = null;
......@@ -252,6 +248,104 @@ public class ExplorerController extends BaseController implements InitializingBe
return "/accession/explore2";
}
@RequestMapping(value = "/explore/overview")
public String overview(
ModelMap model,
@RequestParam(value = "filter", required = false, defaultValue = "{}") String jsonFilter
)
throws IOException, SearchException {
AppliedFilters appliedFilters = mapper.readValue(jsonFilter, AppliedFilters.class);
String[] selectedFilters = appliedFilters.getFilterNames();
final List<GenesysFilter> currentFilters = filterHandler.selectFilters(selectedFilters);
model.addAttribute("appliedFilters", appliedFilters);
model.addAttribute("currentFilters", currentFilters);
// JSP works with JsonObject
final Map<?, ?> filters = mapper.readValue(appliedFilters.toString(), Map.class);
model.addAttribute("filters", filters);
model.addAttribute("jsonFilter", jsonFilter);
final Map<String, GenesysFilter> availableFilters = filterHandler.mapAvailableFilters();
model.addAttribute("availableFilters", availableFilters);
AppliedFilters tempFilters = mapper.readValue(jsonFilter, AppliedFilters.class);
model.addAttribute("crops", getCrops(tempFilters));
// Composition overview
model.addAttribute("accessionCount", elasticService.termStatisticsAuto(appliedFilters, FilterConstants.INSTCODE, 10).getTotalCount());
overviewInstitutes(model, appliedFilters);
overviewComposition(model, appliedFilters);
overviewAvailability(model, appliedFilters);
overviewHistoric(model, appliedFilters);
overviewManagement(model, appliedFilters);
overviewSources(model, appliedFilters);
return "/accession/overview";
}
@RequestMapping(value = "/explore/map", method = RequestMethod.GET)
public String map(
ModelMap model,
@RequestParam(value = "crop", required = false, defaultValue = "") String cropName,
@RequestParam(value = "filter", required = false, defaultValue = "{}") String jsonFilter)
throws IOException, SearchException {
Crop crop = null;
if (StringUtils.isNotBlank(cropName)) {
crop = cropService.getCrop(cropName);
if (crop == null) {
throw new ResourceNotFoundException("No crop " + cropName);
}
model.addAttribute("crop", crop);
}
final AppliedFilters appliedFilters = updateFilterWithCrop(cropName, jsonFilter);
AppliedFilters appliedFilters2 = mapper.readValue(jsonFilter, AppliedFilters.class);
String[] selectedFilters = appliedFilters.getFilterNames();
final List<GenesysFilter> currentFilters = filterHandler.selectFilters(selectedFilters);
model.addAttribute("appliedFilters", appliedFilters2);
model.addAttribute("currentFilters", currentFilters);
final Map<?, ?> filters = mapper.readValue(appliedFilters.toString(), Map.class);
model.addAttribute("filters", filters);
final Map<String, GenesysFilter> availableFilters = filterHandler.mapAvailableFilters();
model.addAttribute("availableFilters", availableFilters);
AppliedFilters tempFilters = mapper.readValue(jsonFilter, AppliedFilters.class);
model.addAttribute("crops", getCrops(tempFilters));
model.addAttribute("jsonFilter", appliedFilters.toString());
return "/accession/map";
}
@RequestMapping(value = "/explore/overview/json", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Map<String, TermResult> getOverviewData(
@RequestParam(value = "filter", required = true, defaultValue = "{}") String jsonFilter)
throws IOException, SearchException {
AppliedFilters appliedFilters = mapper.readValue(jsonFilter, AppliedFilters.class);
Map<String, TermResult> pageData = new HashMap<>();
pageData.put("statsInstCode", elasticService.termStatisticsAuto(appliedFilters, FilterConstants.INSTCODE, 20));
pageData.put("statsInstCountry", elasticService.termStatisticsAuto(appliedFilters, FilterConstants.INSTITUTE_COUNTRY_ISO3, 20));
pageData.put("statsOrgCty", elasticService.termStatisticsAuto(appliedFilters, FilterConstants.ORGCTY_ISO3, 20));
pageData.put("statsDonorCode", elasticService.termStatisticsAuto(appliedFilters, FilterConstants.DONORCODE, 20));
pageData.put("statsMLS", elasticService.termStatisticsAuto(appliedFilters, FilterConstants.MLSSTATUS, 2));
pageData.put("statsAvailable", elasticService.termStatisticsAuto(appliedFilters, FilterConstants.AVAILABLE, 2));
pageData.put("statsHistoric", elasticService.termStatisticsAuto(appliedFilters, FilterConstants.HISTORIC, 2));
pageData.put("statsStorage", elasticService.termStatisticsAuto(appliedFilters, FilterConstants.STORAGE, 30));
pageData.put("statsDuplSite", elasticService.termStatisticsAuto(appliedFilters, FilterConstants.DUPLSITE, 20));
pageData.put("statsSGSV", elasticService.termStatistics(appliedFilters, FilterConstants.SGSV, 2));
pageData.put("statsGenus", elasticService.termStatisticsAuto(appliedFilters, FilterConstants.TAXONOMY_GENUS, 20));
pageData.put("statsSpecies", elasticService.termStatisticsAuto(appliedFilters, FilterConstants.TAXONOMY_GENUSSPECIES, 20));
pageData.put("statsCrops", elasticService.termStatisticsAuto(appliedFilters, FilterConstants.CROPS, 30));
pageData.put("statsSampStat", elasticService.termStatisticsAuto(appliedFilters, FilterConstants.SAMPSTAT, 30));
return pageData;
}
@RequestMapping(value = "/explore/json", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Page<?> getFilteredJsonData(@RequestParam(value = "page", required = false, defaultValue = "1") int page,
......@@ -353,6 +447,13 @@ public class ExplorerController extends BaseController implements InitializingBe
model.addAttribute("countries", countries);
model.addAttribute("locale", locale);
Map<String, String> crops = new HashMap<>();
List<Crop> cropList = cropService.list(getLocale());
for (Crop crop: cropList) {
crops.put(crop.getShortName(), crop.getName(getLocale()));
}
model.addAttribute("cropsNames", crops);
response.setHeader("Cache-control", "max-age: 3600, private");
return "/accession/i18n";
......@@ -543,31 +644,15 @@ public class ExplorerController extends BaseController implements InitializingBe
@RequestMapping(value = "/explore/ac/{field:.+}", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public List<LabelValue<String>> autocomplete(@PathVariable("field") String filter, @RequestParam(value = "term", required = true) String ac,
public List<LabelValue<String>> autocomplete(
@PathVariable("field") String filter,
@RequestParam(value = "term", required = true) String ac,
@RequestParam(value = "jsonFilter", required = false, defaultValue = "{}") String jsonFilter) throws IOException {
AppliedFilters appliedFilters = mapper.readValue(jsonFilter, AppliedFilters.class);
return filterService.autocomplete(filter, ac, appliedFilters);
}
@RequestMapping(value = "/explore/map", method = RequestMethod.GET)
public String map(ModelMap model, @RequestParam(value = "crop", required = false, defaultValue = "") String cropName,
@RequestParam(value = "filter", required = false, defaultValue = "{}") String jsonFilter) throws IOException {
Crop crop = null;
if (StringUtils.isNotBlank(cropName)) {
crop = cropService.getCrop(cropName);
if (crop == null) {
throw new ResourceNotFoundException("No crop " + cropName);
}
model.addAttribute("crop", crop);
}
final AppliedFilters appliedFilters = updateFilterWithCrop(cropName, jsonFilter);
model.addAttribute("jsonFilter", appliedFilters.toString());
return "/accession/map";
}
@RequestMapping(value = "/explore/dwca", method = RequestMethod.POST)
public void dwca(ModelMap model, @RequestParam(value = "crop", required = false, defaultValue = "") String cropName,
@RequestParam(value = "filter", required = false, defaultValue = "{}") String jsonFilter, HttpServletResponse response) throws IOException {
......@@ -672,8 +757,12 @@ public class ExplorerController extends BaseController implements InitializingBe
}
@RequestMapping(value = "/explore/tile/{zoom}/{x}/{y}", produces = MediaType.IMAGE_PNG_VALUE)
public void tile(@PathVariable("zoom") int zoom, @PathVariable("x") int x, @PathVariable("y") int y,
@RequestParam(value = "filter", required = true) String jsonFilter, @RequestParam(value = "color", required = false) String color,
public void tile(
@PathVariable("zoom") int zoom,
@PathVariable("x") int x,
@PathVariable("y") int y,
@RequestParam(value = "filter", required = true) String jsonFilter,
@RequestParam(value = "color", required = false) String color,
HttpServletResponse response) {
try {
......@@ -729,32 +818,6 @@ public class ExplorerController extends BaseController implements InitializingBe
}
}
@RequestMapping(value = "/explore/overview")
public String overview(ModelMap model, @RequestParam(value = "filter", required = false, defaultValue = "{}") String jsonFilter)
throws IOException, SearchException {
AppliedFilters appliedFilters = mapper.readValue(jsonFilter, AppliedFilters.class);
String[] selectedFilters = appliedFilters.getFilterNames();
final List<GenesysFilter> currentFilters = filterHandler.selectFilters(selectedFilters);
model.addAttribute("appliedFilters", appliedFilters);
model.addAttribute("currentFilters", currentFilters);
// JSP works with JsonObject
final Map<?, ?> filters = mapper.readValue(appliedFilters.toString(), Map.class);
model.addAttribute("filters", filters);
model.addAttribute("jsonFilter", appliedFilters.toString());
// Composition overview
model.addAttribute("accessionCount", elasticService.termStatisticsAuto(appliedFilters, FilterConstants.INSTCODE, 10).getTotalCount());
overviewInstitutes(model, appliedFilters);
overviewComposition(model, appliedFilters);
overviewAvailability(model, appliedFilters);
overviewHistoric(model, appliedFilters);
overviewManagement(model, appliedFilters);
overviewSources(model, appliedFilters);
return "/accession/overview";
}
private void overviewInstitutes(ModelMap model, AppliedFilters appliedFilters) throws SearchException {
model.addAttribute("statsInstCode", elasticService.termStatisticsAuto(appliedFilters, FilterConstants.INSTCODE, 20));
......@@ -790,17 +853,34 @@ public class ExplorerController extends BaseController implements InitializingBe
@RequestMapping(value = "/explore/shorten-url", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Object getBriefURL(@RequestParam(value = "filter", required = false, defaultValue = "") String jsonFilter,
@RequestParam(value = "page", required = false, defaultValue = "1") int page, @CookieValue(required = false, value = "columns") String columns)
throws IOException, URISyntaxException {
public Object getBriefURL(
@RequestParam(value = "browser", required = false, defaultValue = "explorer") String browserPage,
@RequestParam(value = "filter", required = false, defaultValue = "") String jsonFilter,
@RequestParam(value = "page", required = false, defaultValue = "1") int page,
@CookieValue(required = false, value = "columns") String columns)
throws IOException, URISyntaxException {
AppliedFilters appliedFilters = mapper.readValue(jsonFilter, AppliedFilters.class);
URIBuilder uriBuilder = null;
URIBuilder uriBuilder = new URIBuilder(baseUrl).setPath("/explore").addParameter("filter", appliedFilters.toString()).addParameter("page",
Integer.toString(page));
if (StringUtils.isNotBlank(columns)) {
uriBuilder.addParameter("columns", columns);
switch (browserPage) {
case "map": {
uriBuilder = new URIBuilder(baseUrl).setPath("/explore/map")
.addParameter("filter", appliedFilters.toString());
break;
}
case "overview": {
uriBuilder = new URIBuilder(baseUrl).setPath("/explore/overview")
.addParameter("filter", appliedFilters.toString());
break;
}
default: {
uriBuilder = new URIBuilder(baseUrl).setPath("/explore")
.addParameter("filter", appliedFilters.toString()).addParameter("page", Integer.toString(page));
if (StringUtils.isNotBlank(columns)) {
uriBuilder.addParameter("columns", columns);
}
}
}
URI longUrl = uriBuilder.build();
......
'use strict';
function renderCropSuggestions(filters) {
$(".radio-wrapper").remove();
$.each(filters, function (name, value) {
var input = $("<input/>", {
type: 'radio',
name: 'crops',
id: 'crops_' + name,
'i-key': 'crops',
'class': 'filter-crop',
value: name
});
if ($("div.filtval[x-key='crops" + name + "']")[0] !== undefined) {
input.prop('checked', true);
}
var key = Object.keys(this)[0];
var label = $("<label/>", {
'for': 'crops_' + name,
text: key + " (" + value[key] + ")"
});
var filtval = $("div.filtval[i-key^='crops']");
var attach = filtval[0] !== undefined ? filtval[0] : (".crops button.applyBtn");
var wrapper = $("<div/>", {'class': "radio-wrapper"});
wrapper.append(input);
wrapper.append(label);
$(attach).before(wrapper);
});
}
function renderListFilterSuggestions(filters, messages) {
$.each(filters, function (option) {
$("div." + option).find("div[class!='panel-body'][class!='filtval complex']").remove();
if (filters[option].options[0] === undefined) {
$("div." + option).find("button.applyBtn").before("<div>No suggestions</div>");
} else {
$.each(filters[option].options, function (index) {
var div = '<div class="checkbox-wrapper">' +
'<label>' +
'<input class="filter-list"' +
'id="' + option + this.value + '_input"' +
($("div.filtval[x-key='" + option + this.value + "']")[0] !== undefined ? 'checked ' : '') +
'norm-key="' + option + '"' +
'i-key="' + option + '" type="checkbox"' +
'value="' + this.value + '"/>' +
messages[this.name] +
' (' + filters[option].counts[index] + ')' +
'</label>' +
'</div>';
var filtval = $("div.filtval[x-key^='" + option + "']");
var attach = filtval[0] !== undefined ? filtval[0] : $("div." + option).find("button.applyBtn");
$(attach).before(div);
});
}
});
}
function renderBooleanSuggestions(filters) {
$.each(filters, function (filter) {
$.each($("input[type='checkbox'][i-key='" + filter + "']"), function (index) {
var val = filters[filter][index === 0 ? "T" : "F"];
if (val === undefined) {
val = 0;
}
var text = $(this).parent().html();
var idx = text.indexOf("(");
if (idx !== -1) {
text = text.substring(0, idx);
}
$(this).parent().html(text + " (" + val + ")");
if ($("div[x-key='" + filter + $(this).val() + "']")[0] !== undefined) {
$($("input[type='checkbox'][i-key='" + filter + "']")[index]).prop("checked", true);
}
});
});
}
function applySuggestions(jsonData, messages) {
$.ajax({
url: "/explore/listFilterSuggestions",
method: 'get',
data: {
filter: JSON.stringify(jsonData)
},
success: function (response) {
renderListFilterSuggestions(response, messages);
}
});
$.ajax({
url: "/explore/booleanSuggestions",
method: 'get',
data: {
filter: JSON.stringify(jsonData)
},
success: function (response) {
renderBooleanSuggestions(response);
}
});
$.ajax({
url: "/explore/cropSuggestions",
method: 'get',
data: {
filter: JSON.stringify(jsonData)
},
success: function (response) {
renderCropSuggestions(response);
}
});
}
function enableFilter (btn, jsonData) {
var key = $(btn).attr("i-key");
jsonData[key] = [];
}
function cleanJsonData(jsonData) {
$.each(jsonData, function (key) {
if ($(this).length === 0) {
delete jsonData[key];
}
});
}
var i18nFilterMessage = {
between: 'Between ',
varEnd: ' and ',
moreThan: 'More than ',
lessThan: 'Less than ',
like: 'Like ',
varTrue: 'Yes',
varFalse: 'No',
varNull: 'Unknown'
};
document.addEventListener('DOMContentLoaded', function() {
$(window).on('click', function (event) {
var popup = document.getElementById('error-loading-popup-id');
if(event.target === popup) {
popup.style.display = "none";
}
});
$(document.getElementsByClassName("close")).on('click', function (event) {
event.preventDefault();
document.getElementById('error-loading-popup-id').style.display = "none";
});
$('#collapseFilters').on('hidden.bs.collapse', function () {
$('#collapseFilters').prev('.panel-heading').addClass('no-border');
});
$('#collapseFilters').on('hidden.bs.collapse', function () {
$("#content-area").addClass('fullwidth');
}).children().on('hidden.bs.collapse', function () {
return false;
});
$('#collapseFilters').on('show.bs.collapse', function () {
$("#content-area").removeClass('fullwidth');
}).children().on('show.bs.collapse', function (child) {
return true;
});
$('#collapseFilters').on('show.bs.collapse', function () {
$('#collapseFilters').prev('.panel-heading').removeClass('no-border');
});
});
......@@ -2635,10 +2635,7 @@ table.accessions {
.overview-page {
font-family: $default-font-family;
.main-col-header {
padding: 15px 20px 5px 20px;
.btn-default {
margin-bottom: 10px;
}
padding: 7px 15px;
}
#allfilters {
font-family: $heading-font-family;
......@@ -2652,6 +2649,9 @@ table.accessions {
color: #88ba42;
}
}
#overview-mode {
float: right;
}
.row-section-heading {
padding: 10px 15px;
font-family: $light-font-family;
......@@ -2690,7 +2690,7 @@ table.accessions {
//map-page
.map-page {
font-family: $default-font-family;
.main-col-header {
/*.main-col-header {
padding: 15px 0 5px 0;
.pull-right {
a {
......@@ -2707,7 +2707,7 @@ table.accessions {
margin-bottom: 10px;
}
}
}
}*/
.applied-filters {
padding: 15px 0 5px 0;
a {
......@@ -3090,7 +3090,7 @@ table.accessions {
}
.explore-page .nav-header {
.list-view-controls {
margin: 23px 0 13px 0;
margin: 22px 0 13px 0;
}
}
.explore-page {
......@@ -3131,6 +3131,12 @@ table.accessions {
width: 80%;
}
}
//overview page
.overview-page {
#overview-mode {
width: 80%;
}
}
}
@media (max-width: 1450px) and (min-width: 1200px) {
......@@ -3245,6 +3251,11 @@ table.accessions {
}
}
}
.overview-page.explore-page {
.main-col-header {
padding: 19px 20px;
}
}
}
@media (max-width: 1198px) {
//accession-page
......@@ -4168,6 +4179,12 @@ table.accessions {
padding-right: 15px;
}
}
//overview page
.overview-page {
#overview-mode {
width: 100%;
}
}
}