Commit 6bddaef0 authored by Aleksandr Sharaban's avatar Aleksandr Sharaban Committed by Matija Obreza
Browse files

Genesys UX 2016: filtering

parent e923f380
......@@ -23,6 +23,7 @@ import org.apache.commons.collections4.list.UnmodifiableList;
public class I18nListFilter<T> extends BasicFilter {
private List<ValueName<T>> options = new ArrayList<ValueName<T>>();
private List<Integer> counts = new ArrayList<Integer>();
public I18nListFilter(String name, DataType dataType) {
super(name, dataType, FilterType.I18NLIST);
......@@ -40,4 +41,12 @@ public class I18nListFilter<T> extends BasicFilter {
public List<ValueName<T>> getOptions() {
return options;
}
public List<Integer> getCounts() {
return counts;
}
public void setCounts(List<Integer> counts) {
this.counts = counts;
}
}
\ No newline at end of file
......@@ -18,14 +18,7 @@ package org.genesys2.server.service.impl;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;
......@@ -122,6 +115,15 @@ public class FilterHandler {
return Collections.unmodifiableList(this.availableFilters);
}
public Map<String, GenesysFilter> mapAvailableFilters() {
Map<String, GenesysFilter> filters = new HashMap<>();
for (GenesysFilter filter: availableFilters) {
filters.put(filter.getKey(), filter);
}
return filters;
}
public GenesysFilter getFilter(String key) {
if (key.startsWith("gm:")) {
return getMethodFilter(key);
......
......@@ -35,13 +35,7 @@ import org.genesys2.server.model.impl.Crop;
import org.genesys2.server.model.impl.FaoInstitute;
import org.genesys2.server.persistence.domain.AccessionRepository;
import org.genesys2.server.persistence.domain.MethodRepository;
import org.genesys2.server.service.CropService;
import org.genesys2.server.service.FilterConstants;
import org.genesys2.server.service.GenesysFilterService;
import org.genesys2.server.service.GenesysService;
import org.genesys2.server.service.GeoService;
import org.genesys2.server.service.InstituteService;
import org.genesys2.server.service.TaxonomyService;
import org.genesys2.server.service.*;
import org.genesys2.server.service.impl.DirectMysqlQuery.MethodResolver;
import org.genesys2.server.service.impl.FilterHandler.AppliedFilter;
import org.genesys2.server.service.impl.FilterHandler.AppliedFilters;
......@@ -52,6 +46,8 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
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.jdbc.core.ArgumentPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
......@@ -92,6 +88,9 @@ public class GenesysFilterServiceImpl implements GenesysFilterService {
@Autowired
private CropService cropService;
@Autowired
private ElasticService elasticService;
@Autowired
public void setDataSource(final DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
......@@ -192,18 +191,66 @@ public class GenesysFilterServiceImpl implements GenesysFilterService {
final List<LabelValue<String>> completed = new ArrayList<LabelValue<String>>();
if (FilterConstants.INSTCODE.equalsIgnoreCase(filter)) {
final List<FaoInstitute> faoInst = instituteService.autocomplete(ac);
for (final FaoInstitute inst : faoInst) {
completed.add(new LabelValue<String>(inst.getCode(), inst.getCode() + ", " + inst.getFullName()));
TermResult termResult = null;
try {
termResult = elasticService.termStatisticsAuto(filters, FilterConstants.INSTCODE, 20000);
} catch (SearchException e) {
LOG.error("Error occurred during search", e);
}
if (termResult != null) {
for (final FaoInstitute inst : faoInst) {
String label = inst.getCode() + ", " + inst.getFullName();
for (Term term: termResult.getTerms()) {
if (term.getTerm().equals(inst.getCode()) && term.getCount() > 0) {
label = label.concat(" (" + term.getCount() + ")");
completed.add(new LabelValue<String>(inst.getCode(), label));
break;
}
}
}
}
} else if (FilterConstants.ORGCTY_ISO3.equalsIgnoreCase(filter)) {
final List<Country> countries = geoService.autocomplete(ac);
for (final Country c : countries) {
completed.add(new LabelValue<String>(c.getCode3(), c.getCode3() + ", " + c.getName()));
TermResult termResult = null;
try {
termResult = elasticService.termStatisticsAuto(filters, FilterConstants.ORGCTY_ISO3, 20000);
} catch (SearchException e) {
LOG.error("Error occurred during search", e);
}
if (termResult != null) {
for (final Country c : countries) {
String label = c.getCode3() + ", " + c.getName();
for (Term term: termResult.getTerms()) {
if (term.getTerm().equals(c.getCode3()) && term.getCount() > 0) {
label = label.concat(" (" + term.getCount() + ")");
completed.add(new LabelValue<String>(c.getCode3(), label));
break;
}
}
}
}
} else if (FilterConstants.TAXONOMY_GENUS.equalsIgnoreCase(filter)) {
final List<String> genera = taxonomyService.autocompleteGenus(ac, crop);
for (final String value : genera) {
completed.add(new LabelValue<String>(value, value));
TermResult termResult = null;
try {
termResult = elasticService.termStatisticsAuto(filters, FilterConstants.TAXONOMY_GENUS, 20000);
} catch (SearchException e) {
LOG.error("Error occurred during search", e);
}
if (termResult != null) {
for (final String value : genera) {
String label = value;
for (Term term: termResult.getTerms()) {
if (term.getTerm().equals(value) && term.getCount() > 0) {
label = label.concat(" (" + term.getCount() + ")");
completed.add(new LabelValue<String>(value, label));
break;
}
}
}
}
} else if (FilterConstants.TAXONOMY_SPECIES.equalsIgnoreCase(filter)) {
List<String> genus = new ArrayList<>();
......@@ -216,8 +263,24 @@ public class GenesysFilterServiceImpl implements GenesysFilterService {
}
final List<String> species = taxonomyService.autocompleteSpecies(ac, crop, genus);
for (final String value : species) {
completed.add(new LabelValue<String>(value, value));
TermResult termResult = null;
try {
termResult = elasticService.termStatisticsAuto(filters, FilterConstants.TAXONOMY_SPECIES, 20000);
} catch (SearchException e) {
LOG.error("Error occurred during search", e);
}
if (termResult != null) {
for (final String value : species) {
String label = value;
for (Term term: termResult.getTerms()) {
if (term.getTerm().equals(value) && term.getCount() > 0) {
label = label.concat(" (" + term.getCount() + ")");
completed.add(new LabelValue<String>(value, label));
break;
}
}
}
}
} else if (FilterConstants.TAXONOMY_SUBTAXA.equalsIgnoreCase(filter)) {
List<String> genus = new ArrayList<>();
......@@ -237,13 +300,45 @@ public class GenesysFilterServiceImpl implements GenesysFilterService {
}
final List<String> subtaxa = taxonomyService.autocompleteSubtaxa(ac, crop, genus, species);
for (final String value : subtaxa) {
completed.add(new LabelValue<String>(value, value));
TermResult termResult = null;
try {
termResult = elasticService.termStatisticsAuto(filters, FilterConstants.TAXONOMY_SUBTAXA, 20000);
} catch (SearchException e) {
LOG.error("Error occurred during search", e);
}
if (termResult != null) {
for (final String value : subtaxa) {
String label = value;
for (Term term: termResult.getTerms()) {
if (term.getTerm().equals(value) && term.getCount() > 0) {
label = label.concat(" (" + term.getCount() + ")");
completed.add(new LabelValue<String>(value, label));
break;
}
}
}
}
} else if (FilterConstants.TAXONOMY_SCINAME.equalsIgnoreCase(filter)) {
final List<String> taxa = taxonomyService.autocompleteTaxonomy(ac);
for (final String taxonomy : taxa) {
completed.add(new LabelValue<String>(taxonomy, taxonomy));
TermResult termResult = null;
try {
termResult = elasticService.termStatisticsAuto(filters, FilterConstants.TAXONOMY_SCINAME, 20000);
} catch (SearchException e) {
LOG.error("Error occurred during search", e);
}
if (termResult != null) {
for (final String taxonomy : taxa) {
String label = taxonomy;
for (Term term: termResult.getTerms()) {
if (term.getTerm().equals(taxonomy) && term.getCount() > 0) {
label = label.concat(" (" + term.getCount() + ")");
completed.add(new LabelValue<String>(taxonomy, label));
break;
}
}
}
}
} else {
throw new RuntimeException("No autocompleter for " + filter);
......
......@@ -25,13 +25,11 @@ import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.*;
import javax.imageio.ImageIO;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.ArrayUtils;
......@@ -39,19 +37,13 @@ import org.apache.commons.lang.StringUtils;
import org.apache.http.client.utils.URIBuilder;
import org.genesys2.server.model.elastic.AccessionDetails;
import org.genesys2.server.model.filters.GenesysFilter;
import org.genesys2.server.model.filters.I18nListFilter;
import org.genesys2.server.model.genesys.Method;
import org.genesys2.server.model.genesys.Parameter;
import org.genesys2.server.model.genesys.ParameterCategory;
import org.genesys2.server.model.impl.Country;
import org.genesys2.server.model.impl.Crop;
import org.genesys2.server.service.CropService;
import org.genesys2.server.service.DownloadService;
import org.genesys2.server.service.ElasticService;
import org.genesys2.server.service.FilterConstants;
import org.genesys2.server.service.GenesysFilterService;
import org.genesys2.server.service.GenesysService;
import org.genesys2.server.service.MappingService;
import org.genesys2.server.service.TraitService;
import org.genesys2.server.service.UrlShortenerService;
import org.genesys2.server.service.*;
import org.genesys2.server.service.impl.FilterHandler;
import org.genesys2.server.service.impl.FilterHandler.AppliedFilter;
import org.genesys2.server.service.impl.FilterHandler.AppliedFilters;
......@@ -64,6 +56,7 @@ import org.springframework.beans.factory.annotation.Value;
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.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
......@@ -77,6 +70,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jhlabs.image.MapColorsFilter;
import org.springframework.web.servlet.support.RequestContextUtils;
@Controller
public class ExplorerController extends BaseController implements InitializingBean {
......@@ -110,6 +104,9 @@ public class ExplorerController extends BaseController implements InitializingBe
@Autowired
private UrlShortenerService urlShortenerService;
@Autowired
private GeoService geoService;
private final ObjectMapper mapper = new ObjectMapper();
@Value("${base.url}")
......@@ -165,8 +162,9 @@ public class ExplorerController extends BaseController implements InitializingBe
@RequestMapping("/explore")
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 {
@CookieValue(value = "columns", required=false) String[] cookieColumns) throws IOException, SearchException {
String[] selectedFilters = null;
......@@ -194,14 +192,47 @@ public class ExplorerController extends BaseController implements InitializingBe
selectedFilters = appliedFilters.getFilterNames();
final List<GenesysFilter> currentFilters = filterHandler.selectFilters(selectedFilters);
final List<GenesysFilter> availableFilters = filterHandler.listAvailableFilters();
final Map<String, GenesysFilter> availableFilters = filterHandler.mapAvailableFilters();
//assign suggested values instead of all values to the list filters
GenesysFilter filter = availableFilters.get(FilterConstants.SAMPSTAT);
List<Integer> options = new ArrayList<>();
List<Integer> counts = new ArrayList<>();
AppliedFilters tempFilters = mapper.readValue(jsonFilter, AppliedFilters.class);
final GenesysFilter finalFilter = filter;
tempFilters.removeIf(appliedFilter -> appliedFilter.getKey().equals(finalFilter.getKey()));
for (Term term : elasticService.termStatisticsAuto(tempFilters, filter.getKey(), 30).getTerms()) {
options.add(Integer.valueOf(term.getTerm()));
counts.add(term.getCount());
}
I18nListFilter<Integer> i18nListFilter = new I18nListFilter<Integer>(filter.getKey(), GenesysFilter.DataType.NUMERIC);
i18nListFilter.setCounts(counts);
i18nListFilter.build("accession.sampleStatus", options.toArray(new Integer[options.size()]));
availableFilters.put(filter.getKey(), i18nListFilter);
filter = availableFilters.get(FilterConstants.STORAGE);
options = new ArrayList<>();
counts = new ArrayList<>();
tempFilters = mapper.readValue(jsonFilter, AppliedFilters.class);
final GenesysFilter finalFilter2 = filter;
tempFilters.removeIf(appliedFilter -> appliedFilter.getKey().equals(finalFilter2.getKey()));
for (Term term : elasticService.termStatisticsAuto(tempFilters, filter.getKey(), 30).getTerms()) {
options.add(Integer.valueOf(term.getTerm()));
counts.add(term.getCount());
}
i18nListFilter = new I18nListFilter<Integer>(filter.getKey(), GenesysFilter.DataType.NUMERIC);
i18nListFilter.setCounts(counts);
i18nListFilter.build("accession.storage", options.toArray(new Integer[options.size()]));
availableFilters.put(filter.getKey(), i18nListFilter);
if (_logger.isDebugEnabled()) {
_logger.debug(appliedFilters.toString());
}
model.addAttribute("jsonFilter", appliedFilters.toString());
final Page<AccessionDetails> accessions = filterService.listAccessionDetails(appliedFilters, new PageRequest(page - 1, 50, new Sort("acceNumb")));
final Page<AccessionDetails> accessions = filterService.listAccessionDetails(appliedFilters, new PageRequest(page - 1, results, new Sort("acceNumb")));
if (_logger.isDebugEnabled()) {
_logger.debug("Got: " + accessions);
......@@ -210,13 +241,119 @@ public class ExplorerController extends BaseController implements InitializingBe
model.addAttribute("availableColumns", validDisplayColumns);
model.addAttribute("selectedColumns", cleanupDisplayColumns(columns, cookieColumns, response));
model.addAttribute("crops", cropService.list(getLocale()));
model.addAttribute("crops", getCrops(appliedFilters));
model.addAttribute("pagedData", accessions);
model.addAttribute("appliedFilters", appliedFilters);
model.addAttribute("currentFilters", currentFilters);
model.addAttribute("availableFilters", availableFilters);
return "/accession/explore";
return "/accession/explore2";
}
@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,
@RequestParam(value = "filter", required = true, defaultValue = "{}") String jsonFilter,
@RequestParam(value = "results", required = true, defaultValue = "50") int results) throws IOException, SearchException {
AppliedFilters appliedFilters = mapper.readValue(jsonFilter, AppliedFilters.class);
return filterService.listAccessionDetails(appliedFilters, new PageRequest(page - 1, results, new Sort("acceNumb")));
}
@RequestMapping(value = "/explore/listFilterSuggestions", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Map<String, GenesysFilter> getSuggestions(@RequestParam(value = "filter", required = true, defaultValue = "{}") String jsonFilter) throws IOException, SearchException {
AppliedFilters appliedFilters = mapper.readValue(jsonFilter, AppliedFilters.class);
Map<String, GenesysFilter> availableFilters = new HashMap<>();
String filterName = null;
for (GenesysFilter filter: filterHandler.listAvailableFilters()) {
if (filter.getFilterType().equals(GenesysFilter.FilterType.I18NLIST)) {
if (filter.getKey().equals(FilterConstants.SAMPSTAT)) {
filterName = "accession.sampleStatus";
} else if (filter.getKey().equals(FilterConstants.STORAGE)) {
filterName = "accession.storage";
}
if (filterName != null) {
List<Integer> options = new ArrayList<>();
List<Integer> counts = new ArrayList<>();
appliedFilters.removeIf(appliedFilter -> appliedFilter.getKey().equals(filter.getKey()));
for (Term term : elasticService.termStatisticsAuto(appliedFilters, filter.getKey(), 30).getTerms()) {
options.add(Integer.valueOf(term.getTerm()));
counts.add(term.getCount());
}
I18nListFilter<Integer> i18nListFilter = new I18nListFilter<Integer>(filter.getKey(), GenesysFilter.DataType.NUMERIC);
i18nListFilter.setCounts(counts);
i18nListFilter.build(filterName, options.toArray(new Integer[options.size()]));
availableFilters.put(filter.getKey(), i18nListFilter);
}
}
}
return availableFilters;
}
@RequestMapping(value = "/explore/booleanSuggestions", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Map<String, Map<String, Integer>> getBooleanSuggestions(@RequestParam(value = "filter", required = true, defaultValue = "{}") String jsonFilter) throws IOException, SearchException {
Map<String, Map<String, Integer>> booleanFilters = new HashMap<>();
for (GenesysFilter filter: filterHandler.listAvailableFilters()) {
if (filter.getDataType().equals(GenesysFilter.DataType.BOOLEAN)) {
Map<String, Integer> counts = new HashMap<>();
AppliedFilters appliedFilters = mapper.readValue(jsonFilter, AppliedFilters.class);
appliedFilters.removeIf(appliedFilter -> appliedFilter.getKey().equals(filter.getKey()));
for (Term term : elasticService.termStatisticsAuto(appliedFilters, filter.getKey(), 30).getTerms()) {
counts.put(term.getTerm(), term.getCount());
}
booleanFilters.put(filter.getKey(), counts);
}
}
return booleanFilters;
}
@RequestMapping(value = "/explore/cropSuggestions", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Map<String, Map<String, Integer>> getCropSuggestions(@RequestParam(value = "filter", required = true, defaultValue = "{}") String jsonFilter) throws IOException, SearchException {
AppliedFilters appliedFilters = mapper.readValue(jsonFilter, AppliedFilters.class);
return getCrops(appliedFilters);
}
private Map<String, Map<String, Integer>> getCrops(AppliedFilters appliedFilters) throws SearchException {
Map<String, Map<String, Integer>> crops = new HashMap<>();
for (Term term: elasticService.termStatisticsAuto(appliedFilters, FilterConstants.CROPS, 30).getTerms()) {
String cropShortName = term.getTerm();
Crop crop = cropService.getCrop(cropShortName);
Map<String, Integer> values = new HashMap<>();
values.put(crop.getName(Locale.getDefault()), term.getCount());
crops.put(cropShortName, values);
}
return crops;
}
@RequestMapping(value = "/explore/i18n.js")
public String getI18n(HttpServletRequest request, HttpServletResponse response, ModelMap model) {
Locale locale = RequestContextUtils.getLocale(request);
ResourceBundle bundle = ResourceBundle.getBundle("content/language", locale);
model.addAttribute("keys", bundle.getKeys());
List<Country> countries = geoService.listAll(locale);
model.addAttribute("countries", countries);
model.addAttribute("locale", locale);
response.setHeader("Cache-control", "max-age: 3600, private");
return "/accession/i18n";
}
@RequestMapping(value = "/explore/getCrops", method = RequestMethod.GET, produces = "text/plain;charset=UTF-8")
@ResponseBody
public List<Crop> getCrops(@RequestParam List<String> cropNames) {
return cropService.getCrops(cropNames);
}
/**
......
......@@ -206,6 +206,8 @@ faoInstitute.wiewsLink={0} details on FAO WIEWS website
view.accessions=Browse accessions
view.datasets=View datasets
paged.pageOfPages=Page {0} of {1}
paged.ofPages=of {0} pages
paged.resultsPerPage=Show results / page:
paged.totalElements={0} entries
accessions.number={0} accessions
......@@ -445,6 +447,7 @@ filter.internal.message.and={0} and {0}
filter.internal.message.more=More than {0}
filter.internal.message.less=Less than {0}
filter.lists=Listed on accession list
filter.more-filters=More filters
columns.add=Change displayed columns
columns.apply=Apply
......
......@@ -204,6 +204,8 @@ faoInstitute.wiewsLink={0} подробностей на веб-сайте FAO W
view.accessions=Просмотр образцов
view.datasets=Просмотр наборов данных
paged.pageOfPages=Страница {0} из {1}
paged.ofPages=из {0} страниц
paged.resultsPerPage=Показать результаты / странице:
paged.totalElements={0} записей
accessions.number={0} образцов
......@@ -443,6 +445,7 @@ filter.internal.message.and={0} и {0}
filter.internal.message.more=Больше чем {0}
filter.internal.message.less=Меньше чем {0}
filter.lists=Уже есть в списке выбранного
filter.more-filters=Больше фильтров
columns.add=Измените отображаемые столбцы
columns.apply=Применить
......
......@@ -285,7 +285,12 @@ var GenesysFilterUtil = {
}
var div = '<div class="filtval complex" x-key="' + normKey + jsonValue + '" i-key="' + key + '">' + value + '</div>';
$(element).parents('.filter-block').find('.filter-values').append(div);
//$(element).parents('.filter-block').find('.filter-values').append(div);
if (key === 'geo.latitude' || key === 'geo.longitude' || key === 'geo.elevation') {
$(element).parent().parent().parent().after(div);
} else {
$(element).parent().parent().parent().find('button.applyBtn').before(div);
}
},
// remove value from json array
removeValue : function(value, key, jsonData) {
......@@ -331,6 +336,7 @@ var GenesysFilterUtil = {
$.ajax('/' + document.documentElement.lang + '/1/additional-filter', {
type : 'GET',
data : 'filter=' + filter,
async: false,
success : function(data) {
jsonData[filter] = [];
......@@ -500,7 +506,7 @@ var GenesysFilter = {
var key = $(element).attr('i-key');
delete jsonData[key];
// Clear previous crop selection
element.parent().parent().parent().parent().find('.complex').remove();
element.parent().find('.complex').remove();
if (value !== null) {
GenesysFilterUtil.appendHtml(key, value, value, element);
......@@ -516,7 +522,7 @@ var GenesysFilter = {
var normKey = GenesysFilter.normKey(key);
var inputId = '#' + normKey + '_input';
var value = $(element).parent().find(inputId).val();
var value = $(element).parent().parent().find(inputId).val();
if (value === '') {
return;
}
......
This diff is collapsed.
<c:set var="string" value="${value}"/>
<c:if test="${fn:contains(value, 'range')}">
<c:set var="string" value="${fn:replace(value,'{range=[',between)}"/>
<c:set var="string" value="${fn:replace(string,',',varEnd)}"/>