Merge pull request #2984 from okriuchykhin/ok_SCI_5204

Project filters: add text field to the filters [SCI-5204]
This commit is contained in:
Urban Rotnik 2020-11-27 12:04:46 +01:00 committed by GitHub
commit fcb82fd0d2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 104 additions and 268 deletions

View file

@ -34,11 +34,10 @@
var projectsViewMode = 'cards';
var projectsViewFilter = $('.projects-view-filter.active').data('filter');
var projectsViewSearch;
var projectsChanged = false;
var projectsViewSort = $('#sortMenuDropdown a.disabled').data('sort');
var TABLE;
// Arrays with selected project and folder IDs shared between both views
var selectedProjects = [];
var selectedProjectFolders = [];
@ -483,11 +482,8 @@
}
function refreshCurrentView() {
if (projectsViewMode === 'cards') {
loadCardsView();
} else {
TABLE.draw();
}
loadCardsView();
// Also refresh sidebar tree navigation
$.ajax({
url: $('#projects-cards-view').data('projects-sidebar-url'),
@ -511,7 +507,8 @@
dataType: 'json',
data: {
filter: projectsViewFilter,
sort: projectsViewSort
sort: projectsViewSort,
search: projectsViewSearch
},
success: function(data) {
viewContainer.find('.card').remove();
@ -535,12 +532,7 @@
$(this).addClass('active');
selectedProjects = [];
projectsViewFilter = $(this).data('filter');
projectsViewFilterChanged = true;
if ($('#projects-cards-view').hasClass('active')) {
loadCardsView();
} else if (!$.isEmptyObject(TABLE)) {
TABLE.draw();
}
loadCardsView();
});
}
@ -582,6 +574,7 @@
let $foldersCB = $('#folder_search', $projectsFilter);
let $createdOnFilter = $('#calendarStartDate', $projectsFilter);
let $dueFilter = $('#calendarDueDate', $projectsFilter);
let $textFilter = $('#textSearchFilterInput', $projectsFilter);
dropdownSelector.init($membersFilter, {
optionClass: 'checkbox-icon users-dropdown-list',
@ -595,6 +588,68 @@
tagClass: 'users-dropdown-list'
});
$textFilter.click((e) => {
e.stopPropagation();
$('#textSearchFilterHistory').toggle();
}).on('input', () => {
$('#textSearchFilterHistory').hide();
});
$projectsFilter.on('click', '.projects-search-keyword', function(e) {
e.stopPropagation();
e.preventDefault();
$textFilter.val($(this).data('keyword'));
$('#textSearchFilterHistory').hide();
});
$('.project-filters-dropdown').on('show.bs.dropdown', function() {
let teamId = $projectsFilter.data('team-id');
$('#textSearchFilterHistory').find('li').remove();
try {
let storagePath = `project_filters_per_team/${teamId}/recent_search_keywords`;
let recentSearchKeywords = JSON.parse(localStorage.getItem(storagePath));
$.each(recentSearchKeywords, function(i, keyword) {
$('#textSearchFilterHistory').append($(
`<li class="dropdown-item">
<a class="projects-search-keyword" href="#" data-keyword="${keyword}">
<i class="fas fa-history"></i>
${keyword}
</a>
</li>`
));
});
} catch (error) {
console.error(error);
}
});
$('#applyProjectFiltersButton').click((e) => {
e.stopPropagation();
e.preventDefault();
let teamId = $('.projects-filters').data('team-id');
projectsViewSearch = $('#textSearchFilterInput').closest('.select-block').find('input[type=text]').val();
try {
let storagePath = `project_filters_per_team/${teamId}/recent_search_keywords`;
let recentSearchKeywords = JSON.parse(localStorage.getItem(storagePath));
if (!Array.isArray(recentSearchKeywords)) recentSearchKeywords = [];
if (recentSearchKeywords.indexOf(projectsViewSearch) !== -1) {
recentSearchKeywords.splice(recentSearchKeywords.indexOf(projectsViewSearch), 1);
}
if (recentSearchKeywords.length > 4) {
recentSearchKeywords = recentSearchKeywords.slice(0, 4);
}
recentSearchKeywords.unshift(projectsViewSearch);
localStorage.setItem(storagePath, JSON.stringify(recentSearchKeywords));
} catch (error) {
console.error(error);
}
$('.projects-filters').dropdown('toggle');
refreshCurrentView();
});
// Clear filters
$('.clear-button', $projectsFilter).click((e) => {
e.stopPropagation();
@ -604,6 +659,7 @@
$createdOnFilter.data('DateTimePicker').clear();
$dueFilter.data('DateTimePicker').clear();
$foldersCB.prop('checked', false);
$textFilter.val('');
});
// Prevent filter window close
@ -616,206 +672,6 @@
});
}
// Updates "Select all" control in a data table
function updateDataTableSelectAllCtrl() {
var $table = TABLE.table().node();
var $header = TABLE.table().header();
var $chkboxAll = $('.project-row-selector', $table);
var $chkboxChecked = $('.project-row-selector:checked', $table);
var chkboxSelectAll = $('input[name="select_all"]', $header).get(0);
// If none of the checkboxes are checked
if ($chkboxChecked.length === 0) {
chkboxSelectAll.checked = false;
if ('indeterminate' in chkboxSelectAll) {
chkboxSelectAll.indeterminate = false;
}
// If all of the checkboxes are checked
} else if ($chkboxChecked.length === $chkboxAll.length) {
chkboxSelectAll.checked = true;
if ('indeterminate' in chkboxSelectAll) {
chkboxSelectAll.indeterminate = false;
}
// If some of the checkboxes are checked
} else {
chkboxSelectAll.checked = true;
if ('indeterminate' in chkboxSelectAll) {
chkboxSelectAll.indeterminate = true;
}
}
}
function initRowSelection() {
// Handle clicks on checkbox
$('.dt-body-center .project-row-selector').change(function(e) {
// Get row ID
var $row = $(this).closest('tr');
var data = TABLE.row($row).data();
var rowId = data.DT_RowId;
// Determine whether row ID is in the list of selected project IDs
var index = $.inArray(rowId, selectedProjects);
// If checkbox is checked and row ID is not in list of selected project IDs
if (this.checked && index === -1) {
selectedProjects.push(rowId);
exportProjectsBtn.removeAttr('disabled');
// Otherwise, if checkbox is not checked and ID is in list of selected IDs
} else if (!this.checked && index !== -1) {
selectedProjects.splice(index, 1);
if (selectedProjects.length === 0) {
exportProjectsBtn.attr('disabled', 'disabled');
}
}
updateDataTableSelectAllCtrl();
e.stopPropagation();
});
// Handle click on "Select all" control
$('.dataTables_scrollHead input[name="select_all"]').change(function(e) {
if (this.checked) {
$('.project-row-selector:not(:checked)').trigger('click');
} else {
$('.project-row-selector:checked').trigger('click');
}
// Prevent click event from propagating to parent
e.stopPropagation();
});
}
function updateSelectedRows() {
TABLE.rows().every(function() {
var rowSelector = $(this.node()).find('input[type="checkbox"]');
var rowId = this.data().DT_RowId;
if ($.inArray(rowId, selectedProjects) !== -1) {
rowSelector.prop('checked', true);
} else {
rowSelector.prop('checked', false);
}
});
updateDataTableSelectAllCtrl();
}
function dataTableInit() {
var TABLE_ID = '#projects-overview-table';
TABLE = $(TABLE_ID).DataTable({
dom: "R<'row'<'col-sm-9-custom toolbar'l><'col-sm-3-custom'f>><'row'<'col-sm-12't>><'row'<'col-sm-7'i><'col-sm-5'p>>",
stateSave: true,
stateDuration: 0,
processing: true,
serverSide: true,
scrollY: '64vh',
scrollCollapse: true,
destroy: true,
ajax: {
url: $(TABLE_ID).data('source'),
global: false,
type: 'POST',
data: function(params) {
params.filter = projectsViewFilter;
// return { ...params, ...{ filter: projectsViewFilter } };
}
},
colReorder: {
fixedColumnsLeft: 9
},
columnDefs: [{
// Checkbox column needs special handling
targets: 0,
searchable: false,
orderable: false,
className: 'dt-body-center',
sWidth: '1%',
render: function() {
return "<input class='project-row-selector' type='checkbox'>";
}
}, {
targets: 8,
searchable: false,
orderable: false,
className: 'dt-body-center',
sWidth: '1%'
}],
oLanguage: {
sSearch: I18n.t('general.filter')
},
rowCallback: function(row, data) {
// Get row ID
var rowId = data.DT_RowId;
var dropdown = $(row).find('.dropdown');
var dropdownCell = dropdown.closest('td');
// If row ID is in the list of selected row IDs
if ($.inArray(rowId, selectedProjects) !== -1) {
$(row).find('input[type="checkbox"]').prop('checked', true);
}
initEditProjectButton($(row));
initArchiveRestoreButton($(row));
dropdown.off().on('show.bs.dropdown', function() {
$('body').append(dropdown.css({
left: dropdown.offset().left,
position: 'absolute',
top: dropdown.offset().top
}).detach());
});
dropdown.off().on('hidden.bs.dropdown', function() {
dropdownCell.append(dropdown.removeAttr('style').detach());
});
},
order: [[2, 'asc']],
columns: [
{ data: 'checkbox' },
{ data: 'status' },
{ data: 'name' },
{ data: 'start' },
{ data: 'visibility' },
{ data: 'users' },
{ data: 'experiments' },
{ data: 'tasks' },
{ data: 'actions' }
],
fnDrawCallback: function() {
animateSpinner(this, false);
updateDataTableSelectAllCtrl();
initRowSelection();
initFormSubmitLinks($(this));
},
stateLoadCallback: function(settings, callback) {
$.ajax({
url: $(TABLE_ID).data('state-load-source'),
dataType: 'json',
type: 'GET',
success: function(json) {
callback(json.state);
}
});
},
stateSaveCallback: function() {
// Don't do anything, state will be updated at backend, based on params
}
});
// Handle click on table cells with checkboxes
$(TABLE_ID).off().on('click', 'tbody td', function(e) {
if ($(e.target).is(
'.project-row-selector, .active-project-link, button, span'
)) {
// Skip if clicking on selector checkbox, links and buttons
return;
}
$(this).parent().find('.project-row-selector').trigger('click');
});
return TABLE;
}
$('.projects-view-mode-switch a').off().on('shown.bs.tab', function(event) {
if ($(event.target).data('mode') === 'table') {
$('#cards-wrapper').addClass('list');

View file

@ -955,6 +955,16 @@ li.module-hover {
}
}
.recent-searches {
label {
@include font-small;
font-weight: bold;
margin-bottom: .3em;
padding: 0 1.5em;
user-select: none;
}
}
.footer {
align-items: center;
border-top: $border-default;
@ -1031,4 +1041,3 @@ li.module-hover {
}
}
}

View file

@ -101,12 +101,12 @@ class ProjectsOverviewService
def filter_project_records(records)
records = records.where(archived: true) if @params[:filter] == 'archived'
records = records.where(archived: false) if @params[:filter] == 'active'
records = Project.search_by_name(@user, @team, @params[:search]) if @params[:search].present?
records = records.where_attributes_like('projects.name', @params[:search]) if @params[:search].present?
records
end
def filter_project_folder_records(records)
records = ProjectFolder.search_by_name(@user, @team, @params[:search]) if @params[:search].present?
records = records.where_attributes_like('project_folders.name', @params[:search]) if @params[:search].present?
records
end
@ -153,49 +153,4 @@ class ProjectsOverviewService
records
end
end
def fetch_dt_records
projects = @team.projects.joins(
'LEFT OUTER JOIN user_projects ON user_projects.project_id = projects.id'
)
exp_join =
'LEFT OUTER JOIN experiments ON experiments.project_id = projects.id'\
' AND ((projects.archived = true)'\
' OR (projects.archived = false AND experiments.archived = false))'
task_join =
'LEFT OUTER JOIN my_modules ON my_modules.experiment_id = experiments.id'\
' AND ((projects.archived = true)'\
' OR (projects.archived = false AND my_modules.archived = false))'
projects = projects.joins(exp_join).joins(task_join)
# Only admins see all projects of the team
unless @user.is_admin_of_team?(@team)
projects = projects.where(
'visibility = 1 OR user_projects.user_id = :user_id', user_id: @user.id
)
end
projects
.select('projects.*')
.select('(SELECT COUNT(DISTINCT user_projects.id) FROM user_projects '\
'WHERE user_projects.project_id = projects.id) AS user_count')
.select('COUNT(DISTINCT experiments.id) AS experiment_count')
.select('COUNT(DISTINCT my_modules.id) AS task_count')
.group('projects.id')
end
def search(records, value)
records.where_attributes_like('projects.name', value)
end
def sortable_columns
{
'1' => 'projects.archived',
'2' => 'projects.name',
'3' => 'projects.created_at',
'4' => 'projects.visibility',
'5' => 'user_count',
'6' => 'experiment_count',
'7' => 'task_count'
}
end
end

View file

@ -41,12 +41,8 @@
</div>
<div class="pull-right">
<!-- project search -->
<a class="projects-search-btn">
<i class="fas fa-search"></i>
</a>
<!-- project filter -->
<div class="filter-container dropdown">
<div class="filter-container dropdown project-filters-dropdown">
<div class="btn btn-light icon-btn filter-button" data-toggle="dropdown"><i class="fas fa-filter"></i></div>
<div class="dropdown-menu dropdown-menu-right projects-filters" role="menu" data-team-id="<%= current_team.id %>">
<div class="header">
@ -55,6 +51,22 @@
<i class="fas fa-times-circle"></i><%= t("projects.index.filters_modal.clear_btn") %></div>
</div>
<div class="select-block dropdown-selector-container">
<label><%= t('projects.index.filters_modal.text.label') %></label>
<div class="input-field dropdown">
<input id="textSearchFilterInput"
class="dropdown-toggle search-field"
type="text"
name="search"
autocomplete="off"
data-toggle="dropdown"
data-placeholder="<%= t('projects.index.filters_modal.text.placeholder') %>">
</div>
<div id="textSearchFilterHistory" class="dropdown-menu recent-searches" aria-labelledby="textSearchFilterInput2">
<label><%= t("projects.index.filters_modal.recent_searches_label") %></label>
</div>
</div>
<div class="select-block">
<div class="created-on-label">
<label><%= t("projects.index.filters_modal.created_on.label") %></label>
@ -109,7 +121,7 @@
</div>
<div class="footer">
<div class="btn btn-primary apply-filters"><%= t("projects.index.filters_modal.show_btn.one") %></div>
<div id="applyProjectFiltersButton" class="btn btn-primary"><%= t("projects.index.filters_modal.show_btn.one") %></div>
</div>
</div>
</div>

View file

@ -363,6 +363,9 @@ en:
contact_admins: "To invite additional users to team %{team}, contact its administrator/s."
filters_modal:
title: "Filters"
text:
label: "Contains text"
placeholder: "Enter search terms..."
created_on:
label: "Created on"
from_placeholder: "From"
@ -373,6 +376,7 @@ en:
folders:
label: "Look inside folders"
tooltip: "Hint should appear here"
recent_searches_label: "Recent searches"
show_btn:
many: "Show %{num} results"
none: "No results"