Commit 3746c3b3 authored by Florian Hübsch's avatar Florian Hübsch
Browse files

Merge pull request #225 from ninjaconcept/225-search

Suche: Pagination, Suchvorschläge uniq
parents e339b3b8 228c85f7
......@@ -137,19 +137,8 @@ module Chemotion
requires :materials, type: Hash
end
# before do
# error!('401 Unauthorized', 401) unless ElementPolicy.new(@current_user, Reaction).create?
# end
post do
ap params
attributes = declared(params, include_missing: false).symbolize_keys
ap attributes
materials = attributes.delete(:materials)
collection_id = attributes.delete(:collection_id)
......
......@@ -53,7 +53,7 @@ module Chemotion
Collection.belongs_to_or_shared_by(current_user.id).find(params[:collection_id]).samples.includes(:molecule)
else
# All collection
Sample.joins(:collections).where('collections.user_id = ?', current_user.id).references(:collections).includes(:molecule).uniq
Sample.for_user(current_user.id).includes(:molecule).uniq
end.order("created_at DESC")
paginate(scope)
......
......@@ -2,29 +2,54 @@ module Chemotion
class SearchAPI < Grape::API
include Grape::Kaminari
# TODO implement search cache?
helpers do
def serialization_by_elements(elements)
serialized_samples = elements.fetch(:samples, []).map{|s| SampleSerializer.new(s).serializable_hash.deep_symbolize_keys}
serialized_reactions = elements.fetch(:reactions, []).map{|s| ReactionSerializer.new(s).serializable_hash.deep_symbolize_keys}
serialized_wellplates = elements.fetch(:wellplates, []).map{|s| WellplateSerializer.new(s).serializable_hash.deep_symbolize_keys}
serialized_screens = elements.fetch(:screens, []).map{|s| ScreenSerializer.new(s).serializable_hash.deep_symbolize_keys}
def page_size
7
end
def pages(total_elements)
total_elements.fdiv(page_size).ceil
end
def serialization_by_elements_and_page(elements, page=1)
samples = elements.fetch(:samples, [])
reactions = elements.fetch(:reactions, [])
wellplates = elements.fetch(:wellplates, [])
screens = elements.fetch(:screens, [])
serialized_samples = Kaminari.paginate_array(samples).page(page).per(page_size).map{|s| SampleSerializer.new(s).serializable_hash.deep_symbolize_keys}
serialized_reactions = Kaminari.paginate_array(reactions).page(page).per(page_size).map{|s| ReactionSerializer.new(s).serializable_hash.deep_symbolize_keys}
serialized_wellplates = Kaminari.paginate_array(wellplates).page(page).per(page_size).map{|s| WellplateSerializer.new(s).serializable_hash.deep_symbolize_keys}
serialized_screens = Kaminari.paginate_array(screens).page(page).per(page_size).map{|s| ScreenSerializer.new(s).serializable_hash.deep_symbolize_keys}
{
samples: {
elements: serialized_samples,
totalElements: elements.fetch(:samples, []).size
totalElements: samples.size,
page: page,
pages: pages(samples.size),
per_page: page_size
},
reactions: {
elements: serialized_reactions,
totalElements: elements.fetch(:reactions, []).size
totalElements: reactions.size,
page: page,
pages: pages(reactions.size),
per_page: page_size
},
wellplates: {
elements: serialized_wellplates,
totalElements: elements.fetch(:wellplates, []).size
totalElements: wellplates.size,
page: page,
pages: pages(wellplates.size),
per_page: page_size
},
screens: {
elements: serialized_screens,
totalElements: elements.fetch(:screens, []).size
totalElements: screens.size,
page: page,
pages: pages(screens.size),
per_page: page_size
}
}
end
......@@ -32,15 +57,15 @@ module Chemotion
def scope_by_search_by_method_arg_and_collection_id(search_by_method, arg, collection_id)
scope = case search_by_method
when 'sum_formula', 'iupac_name', 'sample_name'
Sample.search_by(search_by_method, arg)
Sample.for_user(current_user.id).search_by(search_by_method, arg)
when 'reaction_name'
Reaction.search_by(search_by_method, arg)
Reaction.for_user(current_user.id).search_by(search_by_method, arg)
when 'wellplate_name'
Wellplate.search_by(search_by_method, arg)
Wellplate.for_user(current_user.id).search_by(search_by_method, arg)
when 'screen_name'
Screen.search_by(search_by_method, arg)
Screen.for_user(current_user.id).search_by(search_by_method, arg)
when 'substring'
AllElementSearch.new(arg).search_by_substring
AllElementSearch.new(arg, current_user.id).search_by_substring
end
unless params[:collection_id] == "all"
......@@ -57,29 +82,29 @@ module Chemotion
case scope.first
when Sample
elements[:samples] = scope
elements[:reactions] = scope.flat_map(&:reactions).uniq
elements[:wellplates] = scope.flat_map(&:well).compact.flat_map(&:wellplate).uniq
elements[:screens] = elements[:wellplates].flat_map(&:screen).compact.uniq
elements[:reactions] = (Reaction.for_user(current_user.id).by_material_ids(scope.map(&:id)) + Reaction.for_user(current_user.id).by_reactant_ids(scope.map(&:id)) + Reaction.for_user(current_user.id).by_product_ids(scope.map(&:id))).uniq
elements[:wellplates] = Wellplate.for_user(current_user.id).by_sample_ids(scope.map(&:id)).uniq
elements[:screens] = Screen.for_user(current_user.id).by_wellplate_ids(elements[:wellplates].map(&:id))
when Reaction
elements[:reactions] = scope
elements[:samples] = scope.flat_map(&:samples).uniq
elements[:wellplates] = elements[:samples].flat_map(&:well).compact.flat_map(&:wellplate).uniq
elements[:screens] = elements[:wellplates].flat_map(&:screen).compact.uniq
elements[:samples] = (Sample.for_user(current_user.id).by_reaction_reactant_ids(scope.map(&:id)) + Sample.for_user(current_user.id).by_reaction_product_ids(scope.map(&:id)) + Sample.for_user(current_user.id).by_reaction_material_ids(scope.map(&:id))).uniq
elements[:wellplates] = Wellplate.for_user(current_user.id).by_sample_ids(elements[:samples].map(&:id)).uniq
elements[:screens] = Screen.for_user(current_user.id).by_wellplate_ids(elements[:wellplates].map(&:id))
when Wellplate
elements[:wellplates] = scope
elements[:screens] = scope.flat_map(&:screen).compact.uniq
elements[:samples] = scope.flat_map(&:wells).compact.flat_map(&:sample).uniq
elements[:reactions] = elements[:samples].flat_map(&:reactions).uniq
elements[:screens] = Screen.for_user(current_user.id).by_wellplate_ids(elements[:wellplates].map(&:id)).uniq
elements[:samples] = Sample.for_user(current_user.id).by_wellplate_ids(elements[:wellplates].map(&:id)).uniq
elements[:reactions] = (Reaction.for_user(current_user.id).by_material_ids(elements[:samples].map(&:id)) + Reaction.for_user(current_user.id).by_reactant_ids(elements[:samples].map(&:id)) + Reaction.for_user(current_user.id).by_product_ids(elements[:samples].map(&:id))).uniq
when Screen
elements[:screens] = scope
elements[:wellplates] = scope.flat_map(&:wellplates).uniq
elements[:samples] = elements[:wellplates].flat_map(&:wells).compact.flat_map(&:sample).uniq
elements[:reactions] = elements[:samples].flat_map(&:reactions).uniq
elements[:wellplates] = Wellplate.for_user(current_user.id).by_screen_ids(scope.map(&:id)).uniq
elements[:samples] = Sample.for_user(current_user.id).by_wellplate_ids(elements[:wellplates].map(&:id)).uniq
elements[:reactions] = (Reaction.for_user(current_user.id).by_material_ids(elements[:samples].map(&:id)) + Reaction.for_user(current_user.id).by_reactant_ids(elements[:samples].map(&:id)) + Reaction.for_user(current_user.id).by_product_ids(elements[:samples].map(&:id))).uniq
when AllElementSearch::Results
elements[:samples] = scope.samples
elements[:reactions] = (scope.reactions + elements[:samples].flat_map(&:reactions)).uniq
elements[:wellplates] = (scope.wellplates + elements[:samples].flat_map(&:well).compact.flat_map(&:wellplate)).uniq
elements[:screens] = (scope.screens + elements[:wellplates].flat_map(&:screen).compact).uniq
elements[:reactions] = (scope.reactions + (Reaction.for_user(current_user.id).by_material_ids(elements[:samples].map(&:id)) + Reaction.for_user(current_user.id).by_reactant_ids(elements[:samples].map(&:id)) + Reaction.for_user(current_user.id).by_product_ids(elements[:samples].map(&:id)))).uniq
elements[:wellplates] = (scope.wellplates + Wellplate.for_user(current_user.id).by_sample_ids(elements[:samples].map(&:id))).uniq
elements[:screens] = (scope.screens + Screen.for_user(current_user.id).by_wellplate_ids(elements[:wellplates].map(&:id))).uniq
end
elements
......@@ -88,8 +113,9 @@ module Chemotion
resource :search do
namespace :all do
desc "Return all matched elements and associations"
desc "Return all matched elements and associations for substring query"
params do
optional :page, type: Integer
requires :selection, type: Hash
requires :collection_id, type: String
end
......@@ -100,13 +126,14 @@ module Chemotion
scope = scope_by_search_by_method_arg_and_collection_id(search_by_method, arg, params[:collection_id])
serialization_by_elements(elements_by_scope(scope))
serialization_by_elements_and_page(elements_by_scope(scope), params[:page])
end
end
namespace :samples do
desc "Return samples and associated elements by search selection"
params do
optional :page, type: Integer
requires :selection, type: Hash
requires :collection_id, type: String
end
......@@ -122,13 +149,14 @@ module Chemotion
samples = scope.by_collection_id(params[:collection_id].to_i)
end
serialization_by_elements(elements_by_scope(samples))
serialization_by_elements_and_page(elements_by_scope(samples), params[:page])
end
end
namespace :reactions do
desc "Return reactions and associated elements by search selection"
params do
optional :page, type: Integer
requires :selection, type: Hash
requires :collection_id, type: String
end
......@@ -144,13 +172,14 @@ module Chemotion
reactions = scope.by_collection_id(params[:collection_id].to_i)
end
serialization_by_elements(elements_by_scope(reactions))
serialization_by_elements_and_page(elements_by_scope(reactions), params[:page])
end
end
namespace :wellplates do
desc "Return wellplates and associated elements by search selection"
params do
optional :page, type: Integer
requires :selection, type: Hash
requires :collection_id, type: String
end
......@@ -166,13 +195,14 @@ module Chemotion
wellplates = scope.by_collection_id(params[:collection_id].to_i)
end
serialization_by_elements(elements_by_scope(wellplates))
serialization_by_elements_and_page(elements_by_scope(wellplates), params[:page])
end
end
namespace :screens do
desc "Return wellplates and associated elements by search selection"
params do
optional :page, type: Integer
requires :selection, type: Hash
requires :collection_id, type: String
end
......@@ -188,7 +218,7 @@ module Chemotion
screens = scope.by_collection_id(params[:collection_id].to_i)
end
serialization_by_elements(elements_by_scope(screens))
serialization_by_elements_and_page(elements_by_scope(screens), params[:page])
end
end
......
......@@ -9,42 +9,42 @@ module Chemotion
suggestions
end
def search_possibilities_by_type(type)
def search_possibilities_by_type_and_user_id(type, user_id)
case type
when 'sample'
{
sample_name: Sample.by_name(params[:query]).pluck(:name),
sum_formula: Molecule.by_formula(params[:query]).map(&:sum_formular),
iupac_name: Molecule.by_iupac_name(params[:query]).map(&:iupac_name)
sample_name: Sample.for_user(user_id).by_name(params[:query]).pluck(:name).uniq,
sum_formula: Molecule.for_user(user_id).by_formula(params[:query]).map(&:sum_formular).uniq,
iupac_name: Molecule.for_user(user_id).by_iupac_name(params[:query]).map(&:iupac_name).uniq
}
when 'reaction'
{
reaction_name: Reaction.by_name(params[:query]).pluck(:name),
sample_name: Sample.with_reactions.by_name(params[:query]).pluck(:name),
iupac_name: Molecule.with_reactions.by_iupac_name(params[:query]).map(&:iupac_name)
reaction_name: Reaction.for_user(user_id).by_name(params[:query]).pluck(:name).uniq,
sample_name: Sample.for_user(user_id).with_reactions.by_name(params[:query]).pluck(:name).uniq,
iupac_name: Molecule.for_user(user_id).with_reactions.by_iupac_name(params[:query]).map(&:iupac_name).uniq
}
when 'wellplate'
{
wellplate_name: Wellplate.by_name(params[:query]).pluck(:name),
sample_name: Sample.with_wellplates.by_name(params[:query]).pluck(:name),
iupac_name: Molecule.with_wellplates.by_iupac_name(params[:query]).map(&:iupac_name)
wellplate_name: Wellplate.for_user(user_id).by_name(params[:query]).pluck(:name).uniq,
sample_name: Sample.for_user(user_id).with_wellplates.by_name(params[:query]).pluck(:name).uniq,
iupac_name: Molecule.for_user(user_id).with_wellplates.by_iupac_name(params[:query]).map(&:iupac_name).uniq
}
when 'screen'
{
screen_name: Screen.by_name(params[:query]).pluck(:name),
conditions: Screen.by_conditions(params[:query]).pluck(:conditions),
requirements: Screen.by_requirements(params[:query]).pluck(:requirements)
screen_name: Screen.for_user(user_id).by_name(params[:query]).pluck(:name).uniq,
conditions: Screen.for_user(user_id).by_conditions(params[:query]).pluck(:conditions).uniq,
requirements: Screen.for_user(user_id).by_requirements(params[:query]).pluck(:requirements).uniq
}
else
{
sample_name: Sample.by_name(params[:query]).pluck(:name),
sum_formula: Molecule.by_formula(params[:query]).map(&:sum_formular),
iupac_name: Molecule.by_iupac_name(params[:query]).map(&:iupac_name),
reaction_name: Reaction.by_name(params[:query]).pluck(:name),
wellplate_name: Wellplate.by_name(params[:query]).pluck(:name),
screen_name: Screen.by_name(params[:query]).pluck(:name),
conditions: Screen.by_conditions(params[:query]).pluck(:conditions),
requirements: Screen.by_requirements(params[:query]).pluck(:requirements)
sample_name: Sample.for_user(user_id).by_name(params[:query]).pluck(:name).uniq,
sum_formula: Molecule.for_user(user_id).by_formula(params[:query]).map(&:sum_formular).uniq,
iupac_name: Molecule.for_user(user_id).by_iupac_name(params[:query]).map(&:iupac_name).uniq,
reaction_name: Reaction.for_user(user_id).by_name(params[:query]).pluck(:name).uniq,
wellplate_name: Wellplate.for_user(user_id).by_name(params[:query]).pluck(:name).uniq,
screen_name: Screen.for_user(user_id).by_name(params[:query]).pluck(:name).uniq,
conditions: Screen.for_user(user_id).by_conditions(params[:query]).pluck(:conditions).uniq,
requirements: Screen.for_user(user_id).by_requirements(params[:query]).pluck(:requirements).uniq
}
end
end
......@@ -55,11 +55,12 @@ module Chemotion
namespace :all do
desc 'Return all suggestions for AutoCompleteInput'
params do
requires :user_id, type: Integer, desc: 'Current user id'
requires :query, type: String, desc: 'Search query'
end
route_param :query do
get do
search_possibilities = search_possibilities_by_type('all')
search_possibilities = search_possibilities_by_type_and_user_id('all', params[:user_id])
{suggestions: search_possibilities_to_suggestions(search_possibilities)}
end
end
......@@ -72,7 +73,7 @@ module Chemotion
end
route_param :query do
get do
search_possibilities = search_possibilities_by_type('sample')
search_possibilities = search_possibilities_by_type_and_user_id('sample', params[:user_id])
{suggestions: search_possibilities_to_suggestions(search_possibilities)}
end
end
......@@ -85,7 +86,7 @@ module Chemotion
end
route_param :query do
get do
search_possibilities = search_possibilities_by_type('reaction')
search_possibilities = search_possibilities_by_type_and_user_id('reaction', params[:user_id])
{suggestions: search_possibilities_to_suggestions(search_possibilities)}
end
end
......@@ -98,7 +99,7 @@ module Chemotion
end
route_param :query do
get do
search_possibilities = search_possibilities_by_type('wellplate')
search_possibilities = search_possibilities_by_type_and_user_id('wellplate', params[:user_id])
{suggestions: search_possibilities_to_suggestions(search_possibilities)}
end
end
......@@ -111,7 +112,7 @@ module Chemotion
end
route_param :query do
get do
search_possibilities = search_possibilities_by_type('screen')
search_possibilities = search_possibilities_by_type_and_user_id('screen', params[:user_id])
{suggestions: search_possibilities_to_suggestions(search_possibilities)}
end
end
......
......@@ -111,7 +111,7 @@ Aviator.setRoutes({
} else {
console.log("generateEmptyReaction")
ElementActions.generateEmptyReaction()
}
}
}
},
'/:reactionID': 'show',
......@@ -125,6 +125,7 @@ Aviator.setRoutes({
} else {
ElementActions.fetchWellplateById(wellplateId);
}
//UIActions.selectTab(3)
}
},
'/:wellplateId': 'showOrNew'
......
......@@ -78,7 +78,6 @@ export default class ElementsTable extends React.Component {
let elementsDidChange = elements && ! deepEqual(elements, this.state.elements);
let currentElementDidChange = ! deepEqual(currentElement, this.state.currentElement);
if (elementsDidChange) {
this.setState({
elements, page, pages, perPage, totalElements, currentElement
......
......@@ -54,6 +54,28 @@ export default class List extends React.Component {
handleTabSelect(tab) {
UIActions.selectTab(tab);
// TODO sollte in tab action handler
let type;
switch(tab) {
case 1:
type = 'sample';
break;
case 2:
type = 'reaction';
break;
case 3:
type = 'wellplate';
break;
case 4:
type = 'screen';
}
let uiState = UIStore.getState();
let page = uiState[type].page;
UIActions.setPagination({type: type, page: page})
}
render() {
......
......@@ -24,8 +24,8 @@ class ElementActions {
// -- Search --
fetchBasedOnSearchSelectionAndCollection(selection, collectionId) {
SearchFetcher.fetchBasedOnSearchSelectionAndCollection(selection, collectionId)
fetchBasedOnSearchSelectionAndCollection(selection, collectionId, currentPage) {
SearchFetcher.fetchBasedOnSearchSelectionAndCollection(selection, collectionId, currentPage)
.then((result) => {
this.dispatch(result);
}).catch((errorMessage) => {
......
......@@ -5,7 +5,7 @@ import Wellplate from '../models/Wellplate';
import Screen from '../models/Screen';
export default class SearchFetcher {
static fetchBasedOnSearchSelectionAndCollection(selection, collectionId) {
static fetchBasedOnSearchSelectionAndCollection(selection, collectionId, currentPage) {
let promise = fetch('/api/v1/search/' + selection.elementType, {
credentials: 'same-origin',
method: 'POST',
......@@ -15,7 +15,8 @@ export default class SearchFetcher {
},
body: JSON.stringify({
selection: selection,
collection_id: collectionId
collection_id: collectionId,
page: currentPage
})
})
.then((response) => {
......@@ -23,19 +24,31 @@ export default class SearchFetcher {
}).then((json) => {
let samples = {
elements: json.samples.elements.map((s) => new Sample(s)),
totalElements: json.samples.totalElements
totalElements: json.samples.totalElements,
page: json.samples.page,
pages: json.samples.pages,
perPage: json.samples.per_page
}
let reactions = {
reactions: json.reactions.elements.map((r) => new Reaction(r)),
totalElements: json.reactions.totalElements
elements: json.reactions.elements.map((r) => new Reaction(r)),
totalElements: json.reactions.totalElements,
page: json.reactions.page,
pages: json.reactions.pages,
perPage: json.reactions.per_page
}
let wellplates = {
wellplates: json.wellplates.elements.map((w) => new Wellplate(w)),
totalElements: json.wellplates.totalElements
elements: json.wellplates.elements.map((w) => new Wellplate(w)),
totalElements: json.wellplates.totalElements,
page: json.wellplates.page,
pages: json.wellplates.pages,
perPage: json.wellplates.per_page
}
let screens = {
screens: json.screens.elements.map((s) => new Screen(s)),
totalElements: json.screens.totalElements
elements: json.screens.elements.map((s) => new Screen(s)),
totalElements: json.screens.totalElements,
page: json.screens.page,
pages: json.screens.pages,
perPage: json.screens.per_page
}
return {
......
import 'whatwg-fetch';
export default class SuggestionsFetcher {
static fetchSuggestions(endpoint, query) {
// TODO: scope on current collection?
let promise = fetch(endpoint + query + '.json', {
static fetchSuggestionsForCurrentUser(endpoint, query, userId) {
let promise = fetch(endpoint + query + '.json?user_id=' + userId, {
credentials: 'same-origin'
}).then(response => {
return response.json();
......
......@@ -9,6 +9,7 @@ import SuggestionStore from '../stores/SuggestionStore';
import ElementActions from '../actions/ElementActions';
import UIStore from '../stores/UIStore';
import UIActions from '../actions/UIActions';
import UserStore from '../stores/UserStore';
export default class Search extends React.Component {
constructor(props) {
......@@ -24,11 +25,12 @@ export default class Search extends React.Component {
UIActions.setSearchSelection(selection);
let uiState = UIStore.getState();
ElementActions.fetchBasedOnSearchSelectionAndCollection(selection, uiState.currentCollection.id);
ElementActions.fetchBasedOnSearchSelectionAndCollection(selection, uiState.currentCollection.id, 1);
}
search(query) {
let promise = SuggestionsFetcher.fetchSuggestions('/api/v1/suggestions/' + this.state.elementType + '/', query);
let userState = UserStore.getState();
let promise = SuggestionsFetcher.fetchSuggestionsForCurrentUser('/api/v1/suggestions/' + this.state.elementType + '/', query, userState.currentUser.id);
return promise;
}
......
......@@ -14,28 +14,28 @@ class ElementStore {
totalElements: 0,
page: null,
pages: null,
per_page: null
perPage: null
},
reactions: {
elements: [],
totalElements: 0,
page: null,
pages: null,
per_page: null
perPage: null
},
wellplates: {
elements: [],
totalElements: 0,
page: null,
pages: null,
per_page: null
perPage: null
},
screens: {
elements: [],
totalElements: 0,
page: null,
pages: null,
per_page: null
perPage: null
}
},
currentElement: null
......@@ -71,6 +71,7 @@ class ElementStore {
handleCreateScreen: ElementActions.createScreen,
handleUnselectCurrentElement: UIActions.deselectAllElements,
// FIXME ElementStore listens to UIActions?
handleSetPagination: UIActions.setPagination,
handleRefreshElements: ElementActions.refreshElements,
handleGenerateEmptyElement: [ElementActions.generateEmptyWellplate, ElementActions.generateEmptyScreen, ElementActions.generateEmptySample, ElementActions.generateEmptyReaction],
......@@ -265,19 +266,29 @@ class ElementStore {
this.waitFor(UIStore.dispatchToken);
let uiState = UIStore.getState();