mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-06 13:14:29 +08:00
Add tag filtering to global search [SCI-12288]
This commit is contained in:
parent
fe17f2256c
commit
c11d79bcba
15 changed files with 93 additions and 10 deletions
|
@ -157,7 +157,8 @@ class SearchController < ApplicationController
|
|||
def filter_records
|
||||
filter_datetime!(:created_at) if @filters[:created_at].present?
|
||||
filter_datetime!(:updated_at) if @filters[:updated_at].present?
|
||||
filter_users! if @filters[:users].present?
|
||||
filter_by_users! if @filters[:users].present?
|
||||
filter_by_tags! if @filters[:tags].present? && @model == MyModule
|
||||
end
|
||||
|
||||
def sort_records
|
||||
|
@ -198,7 +199,7 @@ class SearchController < ApplicationController
|
|||
@records = @records.where("#{model_name}.#{attribute} <= ?", to_date) if to_date.present?
|
||||
end
|
||||
|
||||
def filter_users!
|
||||
def filter_by_users!
|
||||
@records = @records.joins("INNER JOIN activities ON #{@model.model_name.collection}.id = activities.subject_id
|
||||
AND activities.subject_type= '#{@model.name}'")
|
||||
|
||||
|
@ -209,4 +210,9 @@ class SearchController < ApplicationController
|
|||
@records.where('activities.owner_id': user_ids)
|
||||
end
|
||||
end
|
||||
|
||||
def filter_by_tags!
|
||||
tag_ids = @filters[:tags]&.values&.collect(&:to_i)
|
||||
@records = @records.joins(:tags).where(tags: { id: tag_ids })
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,7 +9,7 @@ class TeamsController < ApplicationController
|
|||
before_action :load_vars, only: %i(sidebar export_projects export_projects_modal
|
||||
disable_tasks_sharing_modal shared_tasks_toggle)
|
||||
before_action :load_current_folder, only: :sidebar
|
||||
before_action :check_read_permissions, except: %i(view_type visible_teams visible_users current_team_users)
|
||||
before_action :check_read_permissions, except: %i(view_type visible_teams visible_users visible_tags current_team_users)
|
||||
before_action :check_export_projects_permissions, only: %i(export_projects_modal export_projects)
|
||||
|
||||
def visible_teams
|
||||
|
@ -26,6 +26,16 @@ class TeamsController < ApplicationController
|
|||
render json: users, each_serializer: UserSerializer, user: current_user
|
||||
end
|
||||
|
||||
def visible_tags
|
||||
teams = if params[:teams].present?
|
||||
current_user.teams.where(id: params[:teams])
|
||||
else
|
||||
current_team
|
||||
end
|
||||
tags = Tag.where(team: teams)
|
||||
render json: tags, each_serializer: TagSerializer
|
||||
end
|
||||
|
||||
def current_team_users
|
||||
users = current_team.users.order(:full_name)
|
||||
render json: users, each_serializer: UserSerializer, user: current_user
|
||||
|
|
|
@ -109,6 +109,7 @@
|
|||
v-if="filterModalOpened"
|
||||
:teamsUrl="teamsUrl"
|
||||
:usersUrl="usersUrl"
|
||||
:tagsUrl="tagsUrl"
|
||||
:filters="filters"
|
||||
:currentTeam="currentTeam"
|
||||
@search="applyFilters"
|
||||
|
@ -157,6 +158,10 @@ export default {
|
|||
type: String,
|
||||
required: true
|
||||
},
|
||||
tagsUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
currentTeam: {
|
||||
type: Number || String,
|
||||
required: true
|
||||
|
@ -213,7 +218,7 @@ export default {
|
|||
return Object.keys(this.filters).filter((key) => {
|
||||
if (key === 'created_at' || key === 'updated_at') {
|
||||
return this.filters[key].on || this.filters[key].from || this.filters[key].to;
|
||||
} if (key === 'teams' || key === 'users') {
|
||||
} if (key === 'teams' || key === 'users' || key === 'tags') {
|
||||
return this.filters[key].length > 0;
|
||||
}
|
||||
return this.filters[key];
|
||||
|
@ -242,6 +247,7 @@ export default {
|
|||
include_archived: urlParams.get('include_archived') === 'true',
|
||||
teams: (this.singleTeam ? [] : urlParams.getAll('teams[]').map((team) => parseInt(team, 10))),
|
||||
users: urlParams.getAll('users[]').map((user) => parseInt(user, 10)),
|
||||
tags: urlParams.getAll('tags[]').map((tag) => parseInt(tag, 10)),
|
||||
group: urlParams.get('group')
|
||||
};
|
||||
['created_at', 'updated_at'].forEach((key) => {
|
||||
|
@ -364,6 +370,7 @@ export default {
|
|||
include_archived: false,
|
||||
teams: [],
|
||||
users: [],
|
||||
tags: [],
|
||||
group: null
|
||||
};
|
||||
this.activeGroup = null;
|
||||
|
|
|
@ -13,6 +13,20 @@
|
|||
</button>
|
||||
</template>
|
||||
</div>
|
||||
<div class="sci-label mb-2">{{ i18n.t('search.filters.by_tag') }}</div>
|
||||
<div class="grow -mt-2.5">
|
||||
<SelectDropdown :options="tags"
|
||||
@change="selectTags"
|
||||
:optionRenderer="tagRenderer"
|
||||
:labelRenderer="tagRenderer"
|
||||
:multiple="true"
|
||||
class="grow"
|
||||
:value="selectedTags"
|
||||
:searchable="true"
|
||||
:placeholder="i18n.t('search.filters.by_tag_placeholder')"
|
||||
:tagsView="true">
|
||||
</SelectDropdown>
|
||||
</div>
|
||||
<div class="sci-label mb-2" data-e2e="e2e-TX-globalSearch-filters-filterByCreated">{{ i18n.t('search.filters.by_created_date') }}</div>
|
||||
<DateFilter
|
||||
:date="createdAt"
|
||||
|
@ -79,6 +93,7 @@
|
|||
|
||||
import DateFilter from './filters/date.vue';
|
||||
import SelectDropdown from '../shared/select_dropdown.vue';
|
||||
import escapeHtml from '../shared/escape_html.js';
|
||||
import axios from '../../packs/custom_axios.js';
|
||||
|
||||
export default {
|
||||
|
@ -92,6 +107,10 @@ export default {
|
|||
type: String,
|
||||
required: true
|
||||
},
|
||||
tagsUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
filters: Object,
|
||||
currentTeam: Number || String,
|
||||
searchUrl: String,
|
||||
|
@ -109,6 +128,7 @@ export default {
|
|||
this.selectedTeams = this.filters.teams;
|
||||
this.$nextTick(() => {
|
||||
this.selectedUsers = this.filters.users;
|
||||
this.selectedTags = this.filters.tags;
|
||||
});
|
||||
this.includeArchived = this.filters.include_archived;
|
||||
this.activeGroup = this.filters.group;
|
||||
|
@ -118,6 +138,8 @@ export default {
|
|||
selectedTeams() {
|
||||
this.selectedUsers = [];
|
||||
this.fetchUsers();
|
||||
this.selectedTags = [];
|
||||
this.fetchTags();
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
@ -135,9 +157,11 @@ export default {
|
|||
},
|
||||
selectedTeams: [],
|
||||
selectedUsers: [],
|
||||
selectedTags: [],
|
||||
includeArchived: true,
|
||||
teams: [],
|
||||
users: [],
|
||||
tags:[],
|
||||
searchGroups: [
|
||||
{ value: 'FoldersComponent', label: this.i18n.t('search.index.folders') },
|
||||
{ value: 'ProjectsComponent', label: this.i18n.t('search.index.projects') },
|
||||
|
@ -161,9 +185,12 @@ export default {
|
|||
userRenderer(option) {
|
||||
return `<div class="flex items-center gap-2">
|
||||
<img src="${option[2].avatar_url}" class="rounded-full w-6 h-6" />
|
||||
<div title="${option[1]}" class="truncate">${option[1]}</div>
|
||||
<div title="${escapeHtml(option[1])}" class="truncate">${escapeHtml(option[1])}</div>
|
||||
</div>`;
|
||||
},
|
||||
tagRenderer(option) {
|
||||
return `<div class="sci-tag text-white" style="background-color: ${escapeHtml(option[2])};">${escapeHtml(option[1])}</div>`;
|
||||
},
|
||||
setActiveGroup(group) {
|
||||
if (group === this.activeGroup) {
|
||||
this.activeGroup = null;
|
||||
|
@ -183,6 +210,12 @@ export default {
|
|||
this.users = response.data.data.map((user) => ([parseInt(user.id, 10), user.attributes.name, { avatar_url: user.attributes.avatar_url }]));
|
||||
});
|
||||
},
|
||||
fetchTags() {
|
||||
axios.get(this.tagsUrl, { params: { teams: this.selectedTeams } })
|
||||
.then((response) => {
|
||||
this.tags = response.data.data.map((tag) => ([parseInt(tag.id, 10), tag.attributes.name, tag.attributes.color]));
|
||||
});
|
||||
},
|
||||
selectTeams(teams) {
|
||||
if (Array.isArray(teams)) {
|
||||
this.selectedTeams = teams;
|
||||
|
@ -193,6 +226,11 @@ export default {
|
|||
this.selectedUsers = users;
|
||||
}
|
||||
},
|
||||
selectTags(tags) {
|
||||
if (Array.isArray(tags)) {
|
||||
this.selectedTags = tags;
|
||||
}
|
||||
},
|
||||
clearFilters() {
|
||||
this.createdAt = {
|
||||
on: null,
|
||||
|
@ -220,6 +258,7 @@ export default {
|
|||
updated_at: this.updatedAt,
|
||||
teams: this.selectedTeams,
|
||||
users: this.selectedUsers,
|
||||
tags: this.selectedTags,
|
||||
include_archived: this.includeArchived,
|
||||
group: this.activeGroup
|
||||
});
|
||||
|
@ -249,6 +288,10 @@ export default {
|
|||
searchParams.append('users[]', user);
|
||||
});
|
||||
|
||||
this.selectedTags.forEach((tag) => {
|
||||
searchParams.append('tags[]', tag.id);
|
||||
});
|
||||
|
||||
window.location.href = `${this.searchUrl}?${searchParams.toString()}`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
<Filters
|
||||
:teams-url="teamsUrl"
|
||||
:users-url="usersUrl"
|
||||
:tags-url="tagsUrl"
|
||||
:filters="filters"
|
||||
:currentTeam="currentTeam"
|
||||
@search="(newFilters) => { this.$emit('search', newFilters); }"
|
||||
|
@ -38,6 +39,7 @@ export default {
|
|||
props: {
|
||||
teamsUrl: String,
|
||||
usersUrl: String,
|
||||
tagsUrl: String,
|
||||
filters: Object,
|
||||
currentTeam: Number || String
|
||||
},
|
||||
|
|
|
@ -196,8 +196,8 @@ export default {
|
|||
},
|
||||
tagsRenderer(tag) {
|
||||
return `<div class="flex items-center gap-2">
|
||||
<span class="w-4 h-4 rounded-full" style="background-color: ${tag[2]}"></span>
|
||||
<span title="${tag[1]}" class="truncate">${tag[1]}</span>
|
||||
<span class="w-4 h-4 rounded-full" style="background-color: ${escapeHtml(tag[2])}"></span>
|
||||
<span title="${escapeHtml(tag[1])}" class="truncate">${escapeHtml(tag[1])}</span>
|
||||
</div>`;
|
||||
},
|
||||
usersRenderer(user) {
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
v-if="filtersOpened"
|
||||
:teamsUrl="teamsUrl"
|
||||
:usersUrl="usersUrl"
|
||||
:tagsUrl="tagsUrl"
|
||||
:currentTeam="currentTeam"
|
||||
:searchUrl="searchUrl"
|
||||
:searchQuery="searchQuery"
|
||||
|
@ -150,6 +151,10 @@ export default {
|
|||
usersUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
tagsUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
:currentTeam="currentTeam"
|
||||
:teamsUrl="teamsUrl"
|
||||
:usersUrl="usersUrl"
|
||||
:tagsUrl="tagsUrl"
|
||||
></QuickSearch>
|
||||
<MenuDropdown
|
||||
class="ml-auto"
|
||||
|
@ -104,6 +105,7 @@ export default {
|
|||
quickSearchUrl: String,
|
||||
teamsUrl: String,
|
||||
usersUrl: String,
|
||||
tagsUrl: String,
|
||||
logoUrl: String
|
||||
},
|
||||
data() {
|
||||
|
|
|
@ -80,7 +80,7 @@ export default {
|
|||
});
|
||||
},
|
||||
tagRenderer(tag) {
|
||||
return `<div class="sci-tag text-white" style="background-color: ${tag[2]};">${escapeHtml(tag[1])}</div>`;
|
||||
return `<div class="sci-tag text-white" style="background-color: ${escapeHtml(tag[2])};">${escapeHtml(tag[1])}</div>`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -116,7 +116,7 @@ class MyModule < ApplicationRecord
|
|||
)
|
||||
teams = options[:teams] || current_team || user.teams.select(:id)
|
||||
|
||||
new_query = distinct.left_joins(:task_comments, my_module_tags: :tag, user_my_modules: :user)
|
||||
new_query = distinct.left_joins(:task_comments, :tags, user_my_modules: :user)
|
||||
.readable_by_user(user, teams)
|
||||
.where_attributes_like_boolean(SEARCHABLE_ATTRIBUTES, query, options)
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ class TagSerializer < ActiveModel::Serializer
|
|||
|
||||
def urls
|
||||
{
|
||||
update: project_tag_path(object.project, object, format: :json)
|
||||
update: users_settings_team_tag_path(object.team, object, format: :json)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
:search-url="'<%= search_path(format: :json) %>'"
|
||||
teams-url="<%= visible_teams_teams_path %>"
|
||||
users-url="<%= visible_users_teams_path %>"
|
||||
tags-url="<%= visible_tags_teams_path %>"
|
||||
:single-team="<%= current_user.teams.count == 1 %>"
|
||||
:current-team="<%= current_team.id %>"
|
||||
/>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
unseen-notifications-url="<%= unseen_counter_user_notifications_path %>"
|
||||
teams-url="<%= visible_teams_teams_path %>"
|
||||
users-url="<%= visible_users_teams_path %>"
|
||||
tags-url="<%= visible_tags_teams_path %>"
|
||||
logo-url="<%= application_logo_url %>"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -494,6 +494,8 @@ en:
|
|||
title: "More search options"
|
||||
sub_title: "Refine your search by applying the filters below."
|
||||
by_type: "Filter by type"
|
||||
by_tag: "Filter by tag"
|
||||
by_tag_placeholder: "Select tags"
|
||||
by_created_date: "Filter by created date"
|
||||
by_updated_date: "Filter by updated date"
|
||||
by_team: "Filter by workspace"
|
||||
|
|
|
@ -17,6 +17,10 @@ class MigrateTagsFromProjectsToWorkspaces < ActiveRecord::Migration[7.2]
|
|||
FROM my_module_tags
|
||||
SQL
|
||||
|
||||
execute <<~SQL.squish
|
||||
SELECT setval('taggings_id_seq', (SELECT MAX(id) FROM taggings));
|
||||
SQL
|
||||
|
||||
execute <<~SQL.squish
|
||||
UPDATE tags
|
||||
SET team_id = projects.team_id
|
||||
|
|
Loading…
Add table
Reference in a new issue