mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-12-16 05:42:13 +08:00
Merge branch 'develop' into ai-sci-10706-improved-microinteractions-on-quick-search
This commit is contained in:
commit
bdf3e20748
5 changed files with 84 additions and 35 deletions
|
|
@ -150,13 +150,16 @@ class SearchController < ApplicationController
|
|||
|
||||
def object_quick_search(class_name)
|
||||
search_model = class_name.to_s.camelize.constantize
|
||||
search_method = search_model.method(search_model.respond_to?(:code) ? :search_by_name_and_id : :search_by_name)
|
||||
search_object_classes = ["#{class_name.pluralize}.name"]
|
||||
search_object_classes << search_model::PREFIXED_ID_SQL if search_model.respond_to?(:code)
|
||||
|
||||
search_method.call(current_user,
|
||||
current_team,
|
||||
params[:query],
|
||||
limit: Constants::QUICK_SEARCH_LIMIT)
|
||||
.order(updated_at: :desc)
|
||||
search_model.search_by_search_fields_with_boolean(current_user,
|
||||
current_team,
|
||||
params[:query],
|
||||
search_object_classes,
|
||||
limit: Constants::QUICK_SEARCH_LIMIT,
|
||||
fetch_latest_versions: class_name == 'protocol')
|
||||
.order(updated_at: :desc)
|
||||
end
|
||||
|
||||
def load_vars
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="content-pane flexible with-grey-background">
|
||||
<div class="content-header">
|
||||
<div class="title-row">
|
||||
<h1 class="mt-0">
|
||||
<h1 class="mt-0 truncate !inline">
|
||||
{{ i18n.t('search.index.results_title_html', { query: localQuery }) }}
|
||||
</h1>
|
||||
</div>
|
||||
|
|
@ -103,7 +103,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule */
|
||||
/* global HelperModule GLOBAL_CONSTANTS */
|
||||
|
||||
import FoldersComponent from './groups/folders.vue';
|
||||
import ProjectsComponent from './groups/projects.vue';
|
||||
import ExperimentsComponent from './groups/experiments.vue';
|
||||
|
|
@ -265,10 +266,15 @@ export default {
|
|||
|
||||
this.localQuery = event.target.value;
|
||||
|
||||
if (event.target.value.length < 2) {
|
||||
if (event.target.value.length < GLOBAL_CONSTANTS.NAME_MIN_LENGTH) {
|
||||
this.invalidQuery = true;
|
||||
const minLength = 2;
|
||||
HelperModule.flashAlertMsg(this.i18n.t('general.query.length_too_short', { min_length: minLength }), 'danger');
|
||||
HelperModule.flashAlertMsg(this.i18n.t('general.query.length_too_short', { min_length: GLOBAL_CONSTANTS.NAME_MIN_LENGTH }), 'danger');
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target.value.length > GLOBAL_CONSTANTS.NAME_MAX_LENGTH) {
|
||||
this.invalidQuery = true;
|
||||
HelperModule.flashAlertMsg(this.i18n.t('general.query.length_too_long', { max_length: GLOBAL_CONSTANTS.NAME_MAX_LENGTH }), 'danger');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
<template>
|
||||
<GeneralDropdown ref="container" :canOpen="canOpen" :fieldOnlyOpen="true" @close="filtersOpened = false; flyoutOpened = false" @open="flyoutOpened = true">
|
||||
<template v-slot:field>
|
||||
<div class="sci--navigation--top-menu-search left-icon sci-input-container-v2" :class="{'disabled' : !currentTeam}" :title="i18n.t('nav.search')">
|
||||
<input ref="searchField" type="text" class="!pr-20" v-model="searchQuery"
|
||||
<div class="sci--navigation--top-menu-search left-icon sci-input-container-v2"
|
||||
:class="{'disabled' : !currentTeam, 'error': invalidQuery}" :title="i18n.t('nav.search')"
|
||||
>
|
||||
<input ref="searchField" type="text" class="!pr-20" v-model="searchQuery" @keydown="focusHistoryItem"
|
||||
:class="{'active': flyoutOpened}"
|
||||
@keydown="focusHistoryItem"
|
||||
@keydown.down="focusQuickSearchResults"
|
||||
@keydown.escape="closeFlyout"
|
||||
|
|
@ -10,11 +13,11 @@
|
|||
<i class="sn-icon sn-icon-search"></i>
|
||||
<div v-if="this.searchQuery.length > 1" class="flex items-center gap-1 absolute right-2 top-1.5">
|
||||
<button class="btn btn-light icon-btn btn-xs" @click="this.searchQuery = ''; $refs.searchField.focus()">
|
||||
<i class="sn-icon sn-icon-close m-0" :title="i18n.t('nav.clear')"></i>
|
||||
<i class="sn-icon sn-icon-close !m-0" :title="i18n.t('nav.clear')"></i>
|
||||
</button>
|
||||
<button class="btn btn-light icon-btn btn-xs" :title="i18n.t('search.quick_search.search_options')"
|
||||
:class="{'active': filtersOpened}" @click="filtersOpened = !filtersOpened">
|
||||
<i class="sn-icon sn-icon-search-options m-0"></i>
|
||||
<i class="sn-icon sn-icon-search-options !m-0"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -37,7 +40,7 @@
|
|||
tabindex="1"
|
||||
@keydown="focusHistoryItem"
|
||||
@keydown.enter="setQuery(query)"
|
||||
class="flex px-3 h-11 items-center gap-2 hover:bg-sn-super-light-grey cursor-pointer">
|
||||
class="flex px-3 min-h-11 items-center gap-2 hover:bg-sn-super-light-grey cursor-pointer">
|
||||
<i class="sn-icon sn-icon-history-search"></i>
|
||||
{{ query }}
|
||||
</div>
|
||||
|
|
@ -107,7 +110,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<hr class="my-2">
|
||||
<button class="btn btn-light" @click="searchValue">
|
||||
<button class="btn btn-light truncate !block leading-10" @click="searchValue">
|
||||
{{ i18n.t('search.quick_search.all_results', {query: searchQuery}) }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -116,6 +119,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule GLOBAL_CONSTANTS */
|
||||
|
||||
import GeneralDropdown from '../shared/general_dropdown.vue';
|
||||
import StringWithEllipsis from '../shared/string_with_ellipsis.vue';
|
||||
import SearchFilters from '../global_search/filters.vue';
|
||||
|
|
@ -161,6 +166,9 @@ export default {
|
|||
},
|
||||
currentTeamName() {
|
||||
return document.querySelector('body').dataset.currentTeamName;
|
||||
},
|
||||
invalidQuery() {
|
||||
return this.searchQuery.length > GLOBAL_CONSTANTS.NAME_MAX_LENGTH;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -312,6 +320,11 @@ export default {
|
|||
});
|
||||
},
|
||||
searchValue() {
|
||||
if (this.searchQuery.length > GLOBAL_CONSTANTS.NAME_MAX_LENGTH) {
|
||||
HelperModule.flashAlertMsg(this.i18n.t('general.query.length_too_long', { max_length: GLOBAL_CONSTANTS.NAME_MAX_LENGTH }), 'danger');
|
||||
return;
|
||||
}
|
||||
|
||||
window.open(`${this.searchUrl}?q=${this.searchQuery}&teams[]=${this.currentTeam}&include_archived=true`, '_self');
|
||||
},
|
||||
focusHistoryItem(event) {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,20 @@ module SearchableByNameModel
|
|||
|
||||
sql_q.limit(options[:limit] || Constants::SEARCH_LIMIT)
|
||||
end
|
||||
|
||||
def self.search_by_search_fields_with_boolean(user, teams = [], query = nil, search_fields = [], options = {})
|
||||
return if user.blank? || teams.blank?
|
||||
|
||||
sanitized_query = ActiveRecord::Base.sanitize_sql_like(query.to_s)
|
||||
sql_q = if options[:fetch_latest_versions]
|
||||
viewable_by_user(user, teams, options)
|
||||
.where_attributes_like_boolean(search_fields, sanitized_query, options)
|
||||
else
|
||||
viewable_by_user(user, teams).where_attributes_like_boolean(search_fields, sanitized_query, options)
|
||||
end
|
||||
|
||||
sql_q.limit(options[:limit] || Constants::SEARCH_LIMIT)
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/BlockLength
|
||||
end
|
||||
|
|
|
|||
|
|
@ -178,12 +178,7 @@ class Protocol < ApplicationRecord
|
|||
end || []
|
||||
|
||||
protocol_my_modules = if options[:options]&.dig(:in_repository).blank?
|
||||
protocols = distinct.joins(:my_module)
|
||||
.joins("INNER JOIN user_assignments my_module_user_assignments " \
|
||||
"ON my_module_user_assignments.assignable_type = 'MyModule' " \
|
||||
"AND my_module_user_assignments.assignable_id = my_modules.id")
|
||||
.where(my_module_user_assignments: { user_id: user })
|
||||
.where(team: teams)
|
||||
protocols = viewable_by_user_my_module_protocols(user, teams)
|
||||
unless include_archived
|
||||
protocols = protocols.joins(my_module: { experiment: :project })
|
||||
.active
|
||||
|
|
@ -233,19 +228,37 @@ class Protocol < ApplicationRecord
|
|||
where('protocols.id IN ((?) UNION (?) UNION (?))', original_without_versions, published_versions, new_drafts)
|
||||
end
|
||||
|
||||
def self.viewable_by_user(user, teams)
|
||||
# Team owners see all protocol templates in the team
|
||||
owner_role = UserRole.find_predefined_owner_role
|
||||
protocols = Protocol.where(team: teams)
|
||||
.where(protocol_type: REPOSITORY_TYPES)
|
||||
viewable_as_team_owner = protocols.joins("INNER JOIN user_assignments team_user_assignments " \
|
||||
"ON team_user_assignments.assignable_type = 'Team' " \
|
||||
"AND team_user_assignments.assignable_id = protocols.team_id")
|
||||
.where(team_user_assignments: { user_id: user, user_role_id: owner_role })
|
||||
.select(:id)
|
||||
viewable_as_assigned = protocols.with_granted_permissions(user, ProtocolPermissions::READ).select(:id)
|
||||
def self.viewable_by_user(user, teams, options = {})
|
||||
if options[:fetch_latest_versions]
|
||||
protocol_templates = latest_available_versions(teams)
|
||||
.with_granted_permissions(user, ProtocolPermissions::READ)
|
||||
.select(:id)
|
||||
protocol_my_modules = viewable_by_user_my_module_protocols(user, teams).select(:id)
|
||||
|
||||
where('protocols.id IN ((?) UNION (?))', viewable_as_team_owner, viewable_as_assigned)
|
||||
where('protocols.id IN ((?) UNION (?))', protocol_templates, protocol_my_modules)
|
||||
else
|
||||
# Team owners see all protocol templates in the team
|
||||
owner_role = UserRole.find_predefined_owner_role
|
||||
protocols = Protocol.where(team: teams)
|
||||
.where(protocol_type: REPOSITORY_TYPES)
|
||||
viewable_as_team_owner = protocols.joins("INNER JOIN user_assignments team_user_assignments " \
|
||||
"ON team_user_assignments.assignable_type = 'Team' " \
|
||||
"AND team_user_assignments.assignable_id = protocols.team_id")
|
||||
.where(team_user_assignments: { user_id: user, user_role_id: owner_role })
|
||||
.select(:id)
|
||||
viewable_as_assigned = protocols.with_granted_permissions(user, ProtocolPermissions::READ).select(:id)
|
||||
|
||||
where('protocols.id IN ((?) UNION (?))', viewable_as_team_owner, viewable_as_assigned)
|
||||
end
|
||||
end
|
||||
|
||||
def self.viewable_by_user_my_module_protocols(user, teams)
|
||||
distinct.joins(:my_module)
|
||||
.joins("INNER JOIN user_assignments my_module_user_assignments " \
|
||||
"ON my_module_user_assignments.assignable_type = 'MyModule' " \
|
||||
"AND my_module_user_assignments.assignable_id = my_modules.id")
|
||||
.where(my_module_user_assignments: { user_id: user })
|
||||
.where(team: teams)
|
||||
end
|
||||
|
||||
def self.filter_by_teams(teams = [])
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue