diff --git a/app/assets/javascripts/projects/index.js b/app/assets/javascripts/projects/index.js
index fd05178d9..4ebd61ef2 100644
--- a/app/assets/javascripts/projects/index.js
+++ b/app/assets/javascripts/projects/index.js
@@ -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($(
+ `
+
+
+ ${keyword}
+
+ `
+ ));
+ });
+ } 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 "";
- }
- }, {
- 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');
diff --git a/app/assets/stylesheets/projects.scss b/app/assets/stylesheets/projects.scss
index adb91f96a..6bb8457e7 100644
--- a/app/assets/stylesheets/projects.scss
+++ b/app/assets/stylesheets/projects.scss
@@ -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 {
}
}
}
-
diff --git a/app/services/projects_overview_service.rb b/app/services/projects_overview_service.rb
index cfd7d1027..477a922da 100644
--- a/app/services/projects_overview_service.rb
+++ b/app/services/projects_overview_service.rb
@@ -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
diff --git a/app/views/projects/index/_toolbar.html.erb b/app/views/projects/index/_toolbar.html.erb
index ca3ab6449..45639b2e1 100644
--- a/app/views/projects/index/_toolbar.html.erb
+++ b/app/views/projects/index/_toolbar.html.erb
@@ -41,12 +41,8 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
@@ -109,7 +121,7 @@
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 35ab7747a..554ef6b46 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -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"