mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-11-10 17:36:33 +08:00
Merge branch 'ux-release-1' of https://github.com/biosistemika/scinote-web into zd_SCI_2139_2121
This commit is contained in:
commit
62858bc14f
33 changed files with 1108 additions and 454 deletions
2
Gemfile
2
Gemfile
|
@ -27,7 +27,7 @@ gem 'momentjs-rails', '~> 2.17.1'
|
|||
# JS datetime picker
|
||||
gem 'bootstrap3-datetimepicker-rails', '~> 4.15.35'
|
||||
# Select elements for Bootstrap
|
||||
gem 'bootstrap-select-rails', '~> 1.6.3'
|
||||
gem 'bootstrap-select-rails', '~> 1.12.4'
|
||||
gem 'uglifier', '>= 1.3.0'
|
||||
# jQuery & plugins
|
||||
gem 'jquery-turbolinks'
|
||||
|
|
|
@ -127,7 +127,7 @@ GEM
|
|||
bootstrap-sass (3.3.7)
|
||||
autoprefixer-rails (>= 5.2.1)
|
||||
sass (>= 3.3.4)
|
||||
bootstrap-select-rails (1.6.3)
|
||||
bootstrap-select-rails (1.12.4)
|
||||
bootstrap3-datetimepicker-rails (4.15.35)
|
||||
momentjs-rails (>= 2.8.1)
|
||||
bootstrap_form (2.7.0)
|
||||
|
@ -534,7 +534,7 @@ DEPENDENCIES
|
|||
better_errors
|
||||
binding_of_caller
|
||||
bootstrap-sass (~> 3.3.7)
|
||||
bootstrap-select-rails (~> 1.6.3)
|
||||
bootstrap-select-rails (~> 1.12.4)
|
||||
bootstrap3-datetimepicker-rails (~> 4.15.35)
|
||||
bootstrap_form
|
||||
bullet
|
||||
|
|
|
@ -31,11 +31,12 @@
|
|||
//= require tinymce-jquery
|
||||
//= require jsPlumb-2.0.4-min
|
||||
//= require jsnetworkx
|
||||
//= require dataTables.noSearchHidden
|
||||
//= require bootstrap-select
|
||||
//= require_directory ./sitewide
|
||||
//= require jquery.dataTables.yadcf
|
||||
//= require datatables
|
||||
//= require dataTables.noSearchHidden
|
||||
//= require bootstrap-select
|
||||
//= require ajax-bootstrap-select.min
|
||||
//= require underscore
|
||||
//= require i18n.js
|
||||
//= require i18n/translations
|
||||
|
|
|
@ -432,14 +432,20 @@ var RepositoryDatatable = (function(global) {
|
|||
} else if ($(th).attr('id') === 'row-name') {
|
||||
input = changeToInputField('repository_row', 'name', '');
|
||||
tr.appendChild(createTdElement(input));
|
||||
} else if ($(th).hasClass('repository-column')) {
|
||||
} else if ($(th).hasClass('repository-column') &&
|
||||
$(th).attr('data-type') === 'RepositoryTextValue') {
|
||||
input = changeToInputField('repository_cell', th.attr('id'), '');
|
||||
tr.appendChild(createTdElement(input));
|
||||
} else if ($(th).hasClass('repository-column') &&
|
||||
$(th).attr('data-type') === 'RepositoryListValue') {
|
||||
input = initialListItemsRequest($(th).attr('id'));
|
||||
tr.appendChild(createTdElement(input));
|
||||
} else {
|
||||
// Column we don't care for, just add empty td
|
||||
tr.appendChild(createTdElement(''));
|
||||
}
|
||||
});
|
||||
|
||||
$('table' + TABLE_ID).prepend(tr);
|
||||
selectedRecord = tr;
|
||||
|
||||
|
@ -451,6 +457,8 @@ var RepositoryDatatable = (function(global) {
|
|||
});
|
||||
// Adjust columns width in table header
|
||||
adjustTableHeader();
|
||||
// Init selectpicker
|
||||
_initSelectPicker();
|
||||
}
|
||||
|
||||
global.onClickToggleAssignedRecords = function() {
|
||||
|
@ -584,6 +592,7 @@ var RepositoryDatatable = (function(global) {
|
|||
|
||||
// Take care of custom cells
|
||||
var cells = data.repository_row.repository_cells;
|
||||
var list_columns = data.repository_row.repository_column_items;
|
||||
$(node).children('td').each(function(i) {
|
||||
var td = $(this);
|
||||
var rawIndex = TABLE.column.index('fromVisible', i);
|
||||
|
@ -592,13 +601,17 @@ var RepositoryDatatable = (function(global) {
|
|||
// Check if cell on this record exists
|
||||
var cell = cells[$(colHeader).attr('id')];
|
||||
if (cell) {
|
||||
td.html(changeToInputField('repository_cell',
|
||||
td.html(changeToFormField('repository_cell',
|
||||
$(colHeader).attr('id'),
|
||||
cell.value));
|
||||
cell,
|
||||
list_columns));
|
||||
} else {
|
||||
td.html(changeToInputField('repository_cell',
|
||||
$(colHeader).attr('id'), ''));
|
||||
td.html(changeToFormField('repository_cell',
|
||||
$(colHeader).attr('id'),
|
||||
'',
|
||||
list_columns));
|
||||
}
|
||||
_initSelectPicker();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -647,13 +660,18 @@ var RepositoryDatatable = (function(global) {
|
|||
// Record name
|
||||
data.repository_row.name = $('td input[data-object = repository_row]').val();
|
||||
|
||||
// Custom cells
|
||||
// Custom cells text type
|
||||
$(node).find('td input[data-object = repository_cell]').each(function() {
|
||||
// Send data only and only if cell is not empty
|
||||
if ($(this).val().trim()) {
|
||||
data.repository_cells[$(this).attr('name')] = $(this).val();
|
||||
}
|
||||
});
|
||||
// Custom cells list type
|
||||
$(node).find('td[column_id]').each(function(index, el) {
|
||||
var value = $(el).attr('list_item_id');
|
||||
data.repository_cells[$(el).attr('column_id')] = value;
|
||||
});
|
||||
|
||||
var url;
|
||||
var type;
|
||||
|
@ -845,6 +863,88 @@ var RepositoryDatatable = (function(global) {
|
|||
});
|
||||
|
||||
// Helper functions
|
||||
function _listItemDropdown(options, current_value, column_id) {
|
||||
var html = '<select class="form-control selectpicker" ';
|
||||
html += 'data-abs-min-length="2" data-live-search="true" ';
|
||||
html += 'column_id="' + column_id +'">';
|
||||
html += '<option value="-1"></option>';
|
||||
$.each(options, function(index, value) {
|
||||
var selected = (current_value === value[1]) ? 'selected' : '';
|
||||
html += '<option value="' + value[0] + '" ' + selected + '>';
|
||||
html += value[1] + '</option>';
|
||||
});
|
||||
html += '</select>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function initialListItemsRequest(column_id) {
|
||||
var massage_response = [];
|
||||
$.ajax({
|
||||
url: '<%= Rails.application.routes.url_helpers.repository_list_items_path %>',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
async: false,
|
||||
data: {
|
||||
q: '',
|
||||
column_id: column_id
|
||||
}
|
||||
}).done(function(data) {
|
||||
$.each(data.list_items, function(index, el) {
|
||||
massage_response.push([el.id, el.data]);
|
||||
})
|
||||
});
|
||||
return _listItemDropdown(massage_response, '-1', column_id);
|
||||
}
|
||||
|
||||
function _initSelectPicker() {
|
||||
$('.selectpicker')
|
||||
.selectpicker({liveSearch: true})
|
||||
.ajaxSelectPicker({
|
||||
ajax: {
|
||||
url: '<%= Rails.application.routes.url_helpers.repository_list_items_path %>',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: function () {
|
||||
var params = {
|
||||
q: '{{{q}}}',
|
||||
column_id: $(this.valueOf().plugin.$element).attr('column_id')
|
||||
};
|
||||
|
||||
return params;
|
||||
}
|
||||
},
|
||||
locale: {
|
||||
emptyTitle: 'Nothing selected'
|
||||
},
|
||||
preprocessData: function(data){
|
||||
var items = [];
|
||||
if(data.hasOwnProperty('list_items')){
|
||||
items.push({
|
||||
'value': '-1',
|
||||
'text': '',
|
||||
'disabled': false
|
||||
});
|
||||
$.each(data.list_items, function(index, el) {
|
||||
items.push(
|
||||
{
|
||||
'value': el.id,
|
||||
'text': el.data,
|
||||
'disabled': false
|
||||
}
|
||||
)
|
||||
});
|
||||
}
|
||||
return items;
|
||||
},
|
||||
emptyRequest: true,
|
||||
clearOnEmpty: false,
|
||||
preserveSelected: false
|
||||
}).on('change.bs.select', function(el) {
|
||||
$(this).closest('td').attr('list_item_id', el.target.value);
|
||||
$(this).closest('td').attr('column_id', $(this).attr('column_id'));
|
||||
});;
|
||||
}
|
||||
|
||||
function getColumnIndex(id) {
|
||||
if (id < 0) {
|
||||
return false;
|
||||
|
@ -858,6 +958,24 @@ var RepositoryDatatable = (function(global) {
|
|||
object + "' name='" + name + "' value='" + value + "'></input></div>";
|
||||
}
|
||||
|
||||
// Takes an object and creates custom html element
|
||||
function changeToFormField(object, name, cell, list_columns) {
|
||||
if (cell === '') {
|
||||
var column = _.findWhere(list_columns, { column_id: parseInt(name) });
|
||||
if (column) {
|
||||
return _listItemDropdown(column.list_items, '', parseInt(name));
|
||||
} else {
|
||||
return changeToInputField(object, name, '');
|
||||
}
|
||||
} else {
|
||||
if (cell.type === 'RepositoryListValue') {
|
||||
return _listItemDropdown(cell.list_items, cell.value, parseInt(name));
|
||||
} else {
|
||||
return changeToInputField(object, name, cell.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return td element with content
|
||||
function createTdElement(content) {
|
||||
var td = document.createElement('td');
|
|
@ -16,6 +16,7 @@
|
|||
@import "bootstrap-tagsinput";
|
||||
@import "bootstrap-tagsinput-typeahead";
|
||||
@import "handsontable.full.min";
|
||||
@import "ajax-bootstrap-select.min";
|
||||
@import "extend/bootstrap";
|
||||
@import "font-awesome";
|
||||
@import "themes/scinote";
|
||||
|
|
|
@ -26,7 +26,6 @@ $toggle-btn-size: 50px;
|
|||
z-index: 1000;
|
||||
position: fixed;
|
||||
width: $wrapper-width;
|
||||
left: $wrapper-width;
|
||||
height: 100%;
|
||||
margin-left: -$wrapper-width;
|
||||
-webkit-transition: all 0.5s ease;
|
||||
|
@ -99,6 +98,7 @@ $toggle-btn-size: 50px;
|
|||
padding-left: 0;
|
||||
|
||||
#sidebar-wrapper {
|
||||
margin-left: 0;
|
||||
width: 0;
|
||||
|
||||
#slide-panel {
|
||||
|
|
|
@ -344,15 +344,6 @@
|
|||
padding: 10px 0 10px 30px;
|
||||
}
|
||||
|
||||
#power-up-button {
|
||||
margin-right: 15px;
|
||||
margin-top: 8px;
|
||||
|
||||
.btn-danger {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
// Alert
|
||||
.alert {
|
||||
border-radius: 0;
|
||||
|
@ -377,6 +368,7 @@
|
|||
|
||||
#content-wrapper {
|
||||
margin-top: 50px;
|
||||
margin-left: 83px;
|
||||
}
|
||||
|
||||
|
||||
|
|
57
app/assets/stylesheets/themes/menu_bar.scss
Normal file
57
app/assets/stylesheets/themes/menu_bar.scss
Normal file
|
@ -0,0 +1,57 @@
|
|||
@import "constants";
|
||||
@import "mixins";
|
||||
|
||||
.menu-bar {
|
||||
background-color: $color-white;
|
||||
box-shadow: inset -4px 0 0 0 $color-alto;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
overflow-x: hidden;
|
||||
padding-bottom: 16px;
|
||||
padding-top: 16px;
|
||||
position: fixed;
|
||||
width: 83px;
|
||||
|
||||
ul.nav > li {
|
||||
padding-right: 4px;
|
||||
|
||||
& > a {
|
||||
color: $color-gray;
|
||||
display: grid;
|
||||
font-size: 12px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
|
||||
& > span {
|
||||
padding-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $color-gainsboro;
|
||||
margin-right: 4px;
|
||||
padding-right: 0;
|
||||
@include box-shadow(4px 0 0 $color-theme-primary);
|
||||
|
||||
& > a {
|
||||
color: $color-emperor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul.nav-bottom {
|
||||
bottom: 0;
|
||||
padding-bottom: 16px;
|
||||
position: fixed;
|
||||
width: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-height:480px) {
|
||||
.menu-bar > ul.nav-bottom {
|
||||
position: relative;
|
||||
width: auto;
|
||||
}
|
||||
}
|
|
@ -6,12 +6,20 @@ class MyModulesController < ApplicationController
|
|||
include ActionView::Helpers::UrlHelper
|
||||
include ApplicationHelper
|
||||
|
||||
before_action :load_vars
|
||||
before_action :load_vars_nested, only: %I[new create]
|
||||
before_action :load_repository, only: %I[assign_repository_records
|
||||
unassign_repository_records]
|
||||
before_action :check_manage_permissions, only:
|
||||
%i(destroy description due_date)
|
||||
before_action :load_vars,
|
||||
only: %i(show update destroy description due_date protocols
|
||||
results samples activities activities_tab
|
||||
assign_samples unassign_samples delete_samples
|
||||
toggle_task_state samples_index archive
|
||||
complete_my_module repository repository_index
|
||||
assign_repository_records unassign_repository_records)
|
||||
before_action :load_vars_nested, only: %i(new create)
|
||||
before_action :load_repository, only: %i(assign_repository_records
|
||||
unassign_repository_records
|
||||
repository_index)
|
||||
before_action :check_manage_permissions,
|
||||
only: %i(update destroy description due_date)
|
||||
before_action :check_view_info_permissions, only: :show
|
||||
before_action :check_view_permissions, only:
|
||||
%i(show activities activities_tab protocols results samples samples_index
|
||||
archive)
|
||||
|
@ -363,20 +371,18 @@ class MyModulesController < ApplicationController
|
|||
|
||||
# AJAX actions
|
||||
def repository_index
|
||||
@repository = Repository.find_by_id(params[:repository_id])
|
||||
if @repository.nil? || !can_read_team?(@repository.team)
|
||||
render_403
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render json: ::RepositoryDatatable.new(view_context,
|
||||
@repository,
|
||||
@my_module,
|
||||
current_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
@draw = params[:draw].to_i
|
||||
per_page = params[:length] == '-1' ? 100 : params[:length].to_i
|
||||
page = (params[:start].to_i / per_page) + 1
|
||||
records = RepositoryDatatableService.new(@repository,
|
||||
params,
|
||||
current_user,
|
||||
@my_module)
|
||||
@assigned_rows = records.assigned_rows
|
||||
@repository_row_count = records.repository_rows.count
|
||||
@columns_mappings = records.mappings
|
||||
@repository_rows = records.repository_rows.page(page).per(per_page)
|
||||
render 'repository_rows/index.json'
|
||||
end
|
||||
|
||||
# Submit actions
|
||||
|
@ -595,7 +601,8 @@ class MyModulesController < ApplicationController
|
|||
|
||||
def load_repository
|
||||
@repository = Repository.find_by_id(params[:repository_id])
|
||||
render_404 unless @repository && can_read_team?(@repository.team)
|
||||
render_404 unless @repository
|
||||
render_403 unless can_read_team?(@repository.team)
|
||||
end
|
||||
|
||||
def check_manage_permissions
|
||||
|
|
28
app/controllers/repository_list_items_controller.rb
Normal file
28
app/controllers/repository_list_items_controller.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
class RepositoryListItemsController < ApplicationController
|
||||
before_action :load_vars, only: :search
|
||||
|
||||
def search
|
||||
column_list_items = @repository_column.repository_list_items
|
||||
.where('data ILIKE ?',
|
||||
"%#{search_params[:q]}%")
|
||||
.limit(Constants::SEARCH_LIMIT)
|
||||
.select(:id, :data)
|
||||
|
||||
render json: { list_items: column_list_items }, status: :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def search_params
|
||||
params.permit(:q, :column_id)
|
||||
end
|
||||
|
||||
def load_vars
|
||||
@repository_column = RepositoryColumn.find_by_id(search_params[:column_id])
|
||||
repository = @repository_column.repository if @repository_column
|
||||
unless @repository_column&.data_type == 'RepositoryListValue'
|
||||
render_404 and return
|
||||
end
|
||||
render_403 unless can_manage_repository_rows?(repository.team)
|
||||
end
|
||||
end
|
|
@ -5,10 +5,23 @@ class RepositoryRowsController < ApplicationController
|
|||
|
||||
before_action :load_info_modal_vars, only: :show
|
||||
before_action :load_vars, only: %i(edit update)
|
||||
before_action :load_repository, only: %i(create delete_records)
|
||||
before_action :load_repository, only: %i(create delete_records index)
|
||||
before_action :check_create_permissions, only: :create
|
||||
before_action :check_manage_permissions, only: %i(edit update delete_records)
|
||||
|
||||
def index
|
||||
@draw = params[:draw].to_i
|
||||
per_page = params[:length] == '-1' ? 100 : params[:length].to_i
|
||||
page = (params[:start].to_i / per_page) + 1
|
||||
records = RepositoryDatatableService.new(@repository,
|
||||
params,
|
||||
current_user)
|
||||
@assigned_rows = records.assigned_rows
|
||||
@repository_row_count = records.repository_rows.count
|
||||
@columns_mappings = records.mappings
|
||||
@repository_rows = records.repository_rows.page(page).per(per_page)
|
||||
end
|
||||
|
||||
def create
|
||||
record = RepositoryRow.new(repository: @repository,
|
||||
created_by: current_user,
|
||||
|
@ -24,6 +37,21 @@ class RepositoryRowsController < ApplicationController
|
|||
column = @repository.repository_columns.detect do |c|
|
||||
c.id == key.to_i
|
||||
end
|
||||
if column.data_type == 'RepositoryListValue'
|
||||
next if value == '-1'
|
||||
# check if item existx else revert the transaction
|
||||
list_item = RepositoryListItem.where(repository_column: column)
|
||||
.find(value)
|
||||
cell_value = RepositoryListValue.new(
|
||||
repository_list_item_id: list_item.id,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
repository_cell_attributes: {
|
||||
repository_row: record,
|
||||
repository_column: column
|
||||
}
|
||||
)
|
||||
else
|
||||
cell_value = RepositoryTextValue.new(
|
||||
data: value,
|
||||
created_by: current_user,
|
||||
|
@ -33,6 +61,7 @@ class RepositoryRowsController < ApplicationController
|
|||
repository_column: column
|
||||
}
|
||||
)
|
||||
end
|
||||
if cell_value.save
|
||||
record_annotation_notification(record, cell_value.repository_cell)
|
||||
else
|
||||
|
@ -77,7 +106,8 @@ class RepositoryRowsController < ApplicationController
|
|||
json = {
|
||||
repository_row: {
|
||||
name: escape_input(@record.name),
|
||||
repository_cells: {}
|
||||
repository_cells: {},
|
||||
repository_column_items: fetch_columns_list_items
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +115,9 @@ class RepositoryRowsController < ApplicationController
|
|||
@record.repository_cells.each do |cell|
|
||||
json[:repository_row][:repository_cells][cell.repository_column_id] = {
|
||||
repository_cell_id: cell.id,
|
||||
value: escape_input(cell.value.data)
|
||||
value: escape_input(cell.value.data),
|
||||
type: cell.value_type,
|
||||
list_items: fetch_list_items(cell)
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -111,6 +143,18 @@ class RepositoryRowsController < ApplicationController
|
|||
end
|
||||
if existing
|
||||
# Cell exists and new value present, so update value
|
||||
if existing.value_type == 'RepositoryListValue'
|
||||
item = RepositoryListItem.where(
|
||||
repository_column: existing.repository_column
|
||||
).find(value) unless value == '-1'
|
||||
if item
|
||||
existing.value.update_attribute(
|
||||
:repository_list_item_id, item.id
|
||||
)
|
||||
else
|
||||
existing.delete
|
||||
end
|
||||
else
|
||||
existing.value.data = value
|
||||
if existing.value.save
|
||||
record_annotation_notification(@record, existing)
|
||||
|
@ -120,12 +164,28 @@ class RepositoryRowsController < ApplicationController
|
|||
existing.value.errors.messages
|
||||
}
|
||||
end
|
||||
end
|
||||
else
|
||||
# Looks like it is a new cell, so we need to create new value, cell
|
||||
# will be created automatically
|
||||
column = @repository.repository_columns.detect do |c|
|
||||
c.id == key.to_i
|
||||
end
|
||||
if column.data_type == 'RepositoryListValue'
|
||||
next if value == '-1'
|
||||
# check if item existx else revert the transaction
|
||||
list_item = RepositoryListItem.where(repository_column: column)
|
||||
.find(value)
|
||||
cell_value = RepositoryListValue.new(
|
||||
repository_list_item_id: list_item.id,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
repository_cell_attributes: {
|
||||
repository_row: @record,
|
||||
repository_column: column
|
||||
}
|
||||
)
|
||||
else
|
||||
cell_value = RepositoryTextValue.new(
|
||||
data: value,
|
||||
created_by: current_user,
|
||||
|
@ -135,6 +195,7 @@ class RepositoryRowsController < ApplicationController
|
|||
repository_column: column
|
||||
}
|
||||
)
|
||||
end
|
||||
if cell_value.save
|
||||
record_annotation_notification(@record,
|
||||
cell_value.repository_cell)
|
||||
|
@ -147,6 +208,7 @@ class RepositoryRowsController < ApplicationController
|
|||
end
|
||||
# Clean up empty cells, not present in updated record
|
||||
@record.repository_cells.each do |cell|
|
||||
next if cell.value_type == 'RepositoryListValue'
|
||||
cell.value.destroy unless cell_params
|
||||
.key?(cell.repository_column_id.to_s)
|
||||
end
|
||||
|
@ -237,6 +299,7 @@ class RepositoryRowsController < ApplicationController
|
|||
def load_repository
|
||||
@repository = Repository.find_by_id(params[:repository_id])
|
||||
render_404 unless @repository
|
||||
render_403 unless can_read_team?(@repository.team)
|
||||
end
|
||||
|
||||
def check_create_permissions
|
||||
|
@ -276,4 +339,28 @@ class RepositoryRowsController < ApplicationController
|
|||
column: link_to(cell.repository_column.name, table_url))
|
||||
)
|
||||
end
|
||||
|
||||
def fetch_list_items(cell)
|
||||
return [] if cell.value_type != 'RepositoryListValue'
|
||||
RepositoryListItem.where(repository: @repository)
|
||||
.where(repository_column: cell.repository_column)
|
||||
.limit(Constants::SEARCH_LIMIT)
|
||||
.pluck(:id, :data)
|
||||
end
|
||||
|
||||
def fetch_columns_list_items
|
||||
collection = []
|
||||
@repository.repository_columns
|
||||
.list_type
|
||||
.preload(:repository_list_items)
|
||||
.each do |column|
|
||||
collection << {
|
||||
column_id: column.id,
|
||||
list_items: column.repository_list_items
|
||||
.limit(Constants::SEARCH_LIMIT)
|
||||
.pluck(:id, :data)
|
||||
}
|
||||
end
|
||||
collection
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,332 +0,0 @@
|
|||
require 'active_record'
|
||||
|
||||
class RepositoryDatatable < CustomDatatable
|
||||
include ActionView::Helpers::TextHelper
|
||||
include SamplesHelper
|
||||
include InputSanitizeHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include ApplicationHelper
|
||||
include ActiveRecord::Sanitization::ClassMethods
|
||||
|
||||
ASSIGNED_SORT_COL = 'assigned'.freeze
|
||||
|
||||
REPOSITORY_TABLE_DEFAULT_STATE = {
|
||||
'time' => 0,
|
||||
'start' => 0,
|
||||
'length' => 5,
|
||||
'order' => [[2, 'desc']],
|
||||
'search' => { 'search' => '',
|
||||
'smart' => true,
|
||||
'regex' => false,
|
||||
'caseInsensitive' => true },
|
||||
'columns' => [],
|
||||
'assigned' => 'assigned',
|
||||
'ColReorder' => [*0..4]
|
||||
}
|
||||
5.times do
|
||||
REPOSITORY_TABLE_DEFAULT_STATE['columns'] << {
|
||||
'visible' => true,
|
||||
'search' => { 'search' => '',
|
||||
'smart' => true,
|
||||
'regex' => false,
|
||||
'caseInsensitive' => true }
|
||||
}
|
||||
end
|
||||
REPOSITORY_TABLE_DEFAULT_STATE.freeze
|
||||
|
||||
def initialize(view,
|
||||
repository,
|
||||
my_module = nil,
|
||||
user = nil)
|
||||
super(view)
|
||||
@repository = repository
|
||||
@team = repository.team
|
||||
@my_module = my_module
|
||||
@user = user
|
||||
end
|
||||
|
||||
# Define sortable columns, so 1st column will be sorted by attribute
|
||||
# in sortable_columns[0]
|
||||
def sortable_columns
|
||||
sort_array = [
|
||||
ASSIGNED_SORT_COL,
|
||||
'RepositoryRow.name',
|
||||
'RepositoryRow.created_at',
|
||||
'User.full_name'
|
||||
]
|
||||
|
||||
sort_array.push(*repository_columns_sort_by)
|
||||
@sortable_columns = sort_array
|
||||
end
|
||||
|
||||
# Define attributes on which we perform search
|
||||
def searchable_columns
|
||||
search_array = [
|
||||
'RepositoryRow.name',
|
||||
'RepositoryRow.created_at',
|
||||
'User.full_name'
|
||||
]
|
||||
|
||||
# search_array.push(*repository_columns_sort_by)
|
||||
@searchable_columns ||= filter_search_array search_array
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# filters the search array by checking if the the column is visible
|
||||
def filter_search_array(input_array)
|
||||
param_index = 2
|
||||
filtered_array = []
|
||||
input_array.each do |col|
|
||||
next if columns_params.to_a[param_index].nil?
|
||||
params_col =
|
||||
columns_params.to_a.find { |v| v[1]['data'] == param_index.to_s }
|
||||
filtered_array.push(col) unless params_col[1]['searchable'] == 'false'
|
||||
param_index += 1
|
||||
end
|
||||
filtered_array
|
||||
end
|
||||
|
||||
# Get array of columns to sort by (for custom columns)
|
||||
def repository_columns_sort_by
|
||||
array = []
|
||||
@repository.repository_columns.count.times do
|
||||
array << 'RepositoryCell.value'
|
||||
end
|
||||
array
|
||||
end
|
||||
|
||||
# Returns json of current repository rows (already paginated)
|
||||
def data
|
||||
records.map do |record|
|
||||
row = {
|
||||
'DT_RowId': record.id,
|
||||
'1': assigned_row(record),
|
||||
'2': escape_input(record.name),
|
||||
'3': I18n.l(record.created_at, format: :full),
|
||||
'4': escape_input(record.created_by.full_name),
|
||||
'recordEditUrl':
|
||||
Rails.application.routes.url_helpers
|
||||
.edit_repository_repository_row_path(@repository,
|
||||
record.id),
|
||||
'recordUpdateUrl':
|
||||
Rails.application.routes.url_helpers
|
||||
.repository_repository_row_path(@repository, record.id),
|
||||
'recordInfoUrl':
|
||||
Rails.application.routes.url_helpers.repository_row_path(record.id)
|
||||
}
|
||||
|
||||
# Add custom columns
|
||||
record.repository_cells.each do |cell|
|
||||
row[@columns_mappings[cell.repository_column.id]] =
|
||||
custom_auto_link(
|
||||
display_tooltip(cell.value.data,
|
||||
Constants::NAME_MAX_LENGTH),
|
||||
simple_format: true,
|
||||
team: @team
|
||||
)
|
||||
end
|
||||
row
|
||||
end
|
||||
end
|
||||
|
||||
def assigned_row(record)
|
||||
if @assigned_rows && @assigned_rows.include?(record)
|
||||
"<span class='circle'> </span>"
|
||||
else
|
||||
"<span class='circle disabled'> </span>"
|
||||
end
|
||||
end
|
||||
|
||||
# Query database for records (this will be later paginated and filtered)
|
||||
# after that "data" function will return json
|
||||
def get_raw_records
|
||||
repository_rows = RepositoryRow
|
||||
.preload(
|
||||
:repository_columns,
|
||||
:created_by,
|
||||
repository_cells: :value
|
||||
)
|
||||
.joins(:created_by)
|
||||
.where(repository: @repository)
|
||||
|
||||
# Make mappings of custom columns, so we have same id for every column
|
||||
i = 5
|
||||
@columns_mappings = {}
|
||||
@repository.repository_columns.order(:id).each do |column|
|
||||
@columns_mappings[column.id] = i.to_s
|
||||
i += 1
|
||||
end
|
||||
|
||||
if @my_module
|
||||
@assigned_rows = @my_module.repository_rows
|
||||
.preload(
|
||||
:repository_columns,
|
||||
:created_by,
|
||||
repository_cells: :value
|
||||
)
|
||||
.joins(:created_by)
|
||||
.where(repository: @repository)
|
||||
return @assigned_rows if dt_params[:assigned] == 'assigned'
|
||||
else
|
||||
@assigned_rows = repository_rows.joins(
|
||||
'INNER JOIN my_module_repository_rows ON
|
||||
(repository_rows.id = my_module_repository_rows.repository_row_id)'
|
||||
)
|
||||
end
|
||||
|
||||
repository_rows
|
||||
end
|
||||
|
||||
# Override default behaviour
|
||||
# Don't filter and paginate records when sorting by custom column - everything
|
||||
# is done in sort_records method - you might ask why, well if you want the
|
||||
# number of samples/all samples it's dependant upon sort_record query
|
||||
def fetch_records
|
||||
records = get_raw_records
|
||||
records = filter_records(records) if dt_params[:search].present? &&
|
||||
dt_params[:search][:value].present?
|
||||
records = sort_records(records) if order_params.present?
|
||||
records = paginate_records(records) unless dt_params[:length].present? &&
|
||||
dt_params[:length] == '-1'
|
||||
escape_special_chars
|
||||
records
|
||||
end
|
||||
|
||||
# Overriden to make it work for custom columns, because they are polymorphic
|
||||
# NOTE: Function assumes the provided records/rows are only from the current
|
||||
# repository!
|
||||
def filter_records(repo_rows)
|
||||
return repo_rows unless dt_params[:search].present? &&
|
||||
dt_params[:search][:value].present?
|
||||
search_val = dt_params[:search][:value]
|
||||
|
||||
filtered_rows = repo_rows.find_by_sql(
|
||||
"SELECT DISTINCT repository_rows.*
|
||||
FROM repository_rows
|
||||
INNER JOIN (
|
||||
SELECT users.*
|
||||
FROM users
|
||||
) AS users
|
||||
ON users.id = repository_rows.created_by_id
|
||||
LEFT OUTER JOIN (
|
||||
SELECT repository_cells.repository_row_id,
|
||||
repository_text_values.data AS text_value,
|
||||
to_char(repository_date_values.data, 'DD.MM.YYYY HH24:MI')
|
||||
AS date_value
|
||||
FROM repository_cells
|
||||
INNER JOIN repository_text_values
|
||||
ON repository_text_values.id = repository_cells.value_id
|
||||
FULL OUTER JOIN repository_date_values
|
||||
ON repository_date_values.id = repository_cells.value_id
|
||||
) AS values
|
||||
ON values.repository_row_id = repository_rows.id
|
||||
WHERE repository_rows.repository_id = #{@repository.id}
|
||||
AND (repository_rows.name ILIKE '%#{search_val}%'
|
||||
OR to_char(repository_rows.created_at, 'DD.MM.YYYY HH24:MI')
|
||||
ILIKE '%#{search_val}%'
|
||||
OR users.full_name ILIKE '%#{search_val}%'
|
||||
OR text_value ILIKE '%#{search_val}%'
|
||||
OR date_value ILIKE '%#{search_val}%')"
|
||||
)
|
||||
repo_rows.where(id: filtered_rows)
|
||||
end
|
||||
|
||||
# Override default sort method if needed
|
||||
def sort_records(records)
|
||||
if sort_column(order_params) == ASSIGNED_SORT_COL
|
||||
# If "assigned" column is sorted when viewing assigned items
|
||||
return records if @my_module && dt_params[:assigned] == 'assigned'
|
||||
# If "assigned" column is sorted
|
||||
direction = sort_null_direction(order_params)
|
||||
if @my_module
|
||||
# Depending on the sort, order nulls first or
|
||||
# nulls last on repository_cells association
|
||||
records.joins(
|
||||
"LEFT OUTER JOIN my_module_repository_rows ON
|
||||
(repository_rows.id = my_module_repository_rows.repository_row_id
|
||||
AND (my_module_repository_rows.my_module_id = #{@my_module.id} OR
|
||||
my_module_repository_rows.id IS NULL))"
|
||||
).order("my_module_repository_rows.id NULLS #{direction}")
|
||||
else
|
||||
sort_assigned_records(records, order_params['dir'])
|
||||
end
|
||||
elsif sorting_by_custom_column
|
||||
ci = sortable_displayed_columns[
|
||||
order_params['column'].to_i - 1
|
||||
]
|
||||
column_id = @columns_mappings.key((ci.to_i + 1).to_s)
|
||||
dir = sort_direction(order_params)
|
||||
|
||||
records.joins(
|
||||
"LEFT OUTER JOIN (SELECT repository_cells.repository_row_id,
|
||||
repository_text_values.data AS value FROM repository_cells
|
||||
INNER JOIN repository_text_values
|
||||
ON repository_text_values.id = repository_cells.value_id
|
||||
WHERE repository_cells.repository_column_id = #{column_id}) AS values
|
||||
ON values.repository_row_id = repository_rows.id"
|
||||
).order("values.value #{dir}")
|
||||
else
|
||||
super(records)
|
||||
end
|
||||
end
|
||||
|
||||
def sort_null_direction(item)
|
||||
val = sort_direction(item)
|
||||
val == 'ASC' ? 'LAST' : 'FIRST'
|
||||
end
|
||||
|
||||
def inverse_sort_direction(item)
|
||||
val = sort_direction(item)
|
||||
val == 'ASC' ? 'DESC' : 'ASC'
|
||||
end
|
||||
|
||||
def sorting_by_custom_column
|
||||
sort_column(order_params) == 'repository_cells.value'
|
||||
end
|
||||
|
||||
# Escapes special characters in search query
|
||||
def escape_special_chars
|
||||
if dt_params[:search].present?
|
||||
dt_params[:search][:value] = ActiveRecord::Base
|
||||
.__send__(:sanitize_sql_like,
|
||||
dt_params[:search][:value])
|
||||
end
|
||||
end
|
||||
|
||||
def new_sort_column(item)
|
||||
coli = item[:column].to_i - 1
|
||||
model, column = sortable_columns[sortable_displayed_columns[coli].to_i]
|
||||
.split('.')
|
||||
|
||||
return model if model == ASSIGNED_SORT_COL
|
||||
[model.constantize.table_name, column].join('.')
|
||||
end
|
||||
|
||||
def generate_sortable_displayed_columns
|
||||
sort_order = RepositoryTableState.load_state(@user, @repository)
|
||||
.first['ColReorder']
|
||||
sort_order.shift
|
||||
sort_order.map! { |i| (i.to_i - 1).to_s }
|
||||
|
||||
@sortable_displayed_columns = sort_order
|
||||
end
|
||||
|
||||
def sort_assigned_records(records, direction)
|
||||
assigned = records.joins(:my_module_repository_rows).distinct.pluck(:id)
|
||||
unassigned = records.where.not(id: assigned).pluck(:id)
|
||||
if direction == 'asc'
|
||||
ids = assigned + unassigned
|
||||
elsif direction == 'desc'
|
||||
ids = unassigned + assigned
|
||||
end
|
||||
|
||||
order_by_index = ActiveRecord::Base.send(
|
||||
:sanitize_sql_array,
|
||||
["position((',' || repository_rows.id || ',') in ?)",
|
||||
ids.join(',') + ',']
|
||||
)
|
||||
records.order(order_by_index)
|
||||
end
|
||||
end
|
31
app/helpers/left_menu_bar_helper.rb
Normal file
31
app/helpers/left_menu_bar_helper.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
module LeftMenuBarHelper
|
||||
def projects_are_selected?
|
||||
controller_name.in? %w(projects experiments my_modules)
|
||||
end
|
||||
|
||||
def repositories_are_selected?
|
||||
controller_name == 'repositories'
|
||||
end
|
||||
|
||||
def templates_are_selected?
|
||||
# TODO
|
||||
controller_name == 'protocols'
|
||||
end
|
||||
|
||||
def reports_are_selected?
|
||||
# TODO
|
||||
controller_name == 'reports'
|
||||
end
|
||||
|
||||
def settings_are_selected?
|
||||
controller_name.in? %(registrations preferences addons teams)
|
||||
end
|
||||
|
||||
def activities_are_selected?
|
||||
controller_name == 'activities'
|
||||
end
|
||||
|
||||
def help_is_selected?
|
||||
# TODO
|
||||
end
|
||||
end
|
52
app/helpers/repository_datatable_helper.rb
Normal file
52
app/helpers/repository_datatable_helper.rb
Normal file
|
@ -0,0 +1,52 @@
|
|||
module RepositoryDatatableHelper
|
||||
include InputSanitizeHelper
|
||||
def prepare_row_columns(repository_rows,
|
||||
repository,
|
||||
columns_mappings,
|
||||
team,
|
||||
assigned_rows)
|
||||
parsed_records = []
|
||||
repository_rows.each do |record|
|
||||
row = {
|
||||
'DT_RowId': record.id,
|
||||
'1': assigned_row(record, assigned_rows),
|
||||
'2': escape_input(record.name),
|
||||
'3': I18n.l(record.created_at, format: :full),
|
||||
'4': escape_input(record.created_by.full_name),
|
||||
'recordEditUrl': Rails.application.routes.url_helpers
|
||||
.edit_repository_repository_row_path(
|
||||
repository,
|
||||
record.id
|
||||
),
|
||||
'recordUpdateUrl': Rails.application.routes.url_helpers
|
||||
.repository_repository_row_path(
|
||||
repository,
|
||||
record.id
|
||||
),
|
||||
'recordInfoUrl': Rails.application.routes.url_helpers
|
||||
.repository_row_path(record.id)
|
||||
}
|
||||
|
||||
# Add custom columns
|
||||
record.repository_cells.each do |cell|
|
||||
row[columns_mappings[cell.repository_column.id]] =
|
||||
custom_auto_link(
|
||||
display_tooltip(cell.value.data,
|
||||
Constants::NAME_MAX_LENGTH),
|
||||
simple_format: true,
|
||||
team: team
|
||||
)
|
||||
end
|
||||
parsed_records << row
|
||||
end
|
||||
parsed_records
|
||||
end
|
||||
|
||||
def assigned_row(record, assigned_rows)
|
||||
if assigned_rows&.include?(record)
|
||||
"<span class='circle'> </span>"
|
||||
else
|
||||
"<span class='circle disabled'> </span>"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -21,6 +21,8 @@ class RepositoryColumn < ApplicationRecord
|
|||
|
||||
after_create :update_repository_table_state
|
||||
|
||||
scope :list_type, -> { where(data_type: 'RepositoryListValue') }
|
||||
|
||||
def update_repository_table_state
|
||||
RepositoryTableState.update_state(self, nil, created_by)
|
||||
end
|
||||
|
|
|
@ -43,7 +43,7 @@ class RepositoryTableState < ApplicationRecord
|
|||
else
|
||||
# add column
|
||||
index = repository_state['columns'].count
|
||||
repository_state['columns'][index] = RepositoryDatatable::
|
||||
repository_state['columns'][index] = Constants::
|
||||
REPOSITORY_TABLE_DEFAULT_STATE['columns'].first
|
||||
repository_state['ColReorder'].insert(2, index.to_s)
|
||||
end
|
||||
|
@ -52,12 +52,12 @@ class RepositoryTableState < ApplicationRecord
|
|||
end
|
||||
|
||||
def self.create_state(user, repository)
|
||||
default_columns_num = RepositoryDatatable::
|
||||
default_columns_num = Constants::
|
||||
REPOSITORY_TABLE_DEFAULT_STATE['columns'].count
|
||||
repository_state =
|
||||
RepositoryDatatable::REPOSITORY_TABLE_DEFAULT_STATE.deep_dup
|
||||
Constants::REPOSITORY_TABLE_DEFAULT_STATE.deep_dup
|
||||
repository.repository_columns.each_with_index do |_, index|
|
||||
repository_state['columns'] << RepositoryDatatable::
|
||||
repository_state['columns'] << Constants::
|
||||
REPOSITORY_TABLE_DEFAULT_STATE['columns'].first
|
||||
repository_state['ColReorder'] << (default_columns_num + index)
|
||||
end
|
||||
|
|
190
app/services/repository_datatable_service.rb
Normal file
190
app/services/repository_datatable_service.rb
Normal file
|
@ -0,0 +1,190 @@
|
|||
class RepositoryDatatableService
|
||||
|
||||
attr_reader :repository_rows, :assigned_rows, :mappings
|
||||
|
||||
def initialize(repository, params, user, my_module = nil)
|
||||
@repository = repository
|
||||
@user = user
|
||||
@my_module = my_module
|
||||
@params = params
|
||||
create_columns_mappings
|
||||
process_query
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_columns_mappings
|
||||
# Make mappings of custom columns, so we have same id for every
|
||||
# column
|
||||
index = 5
|
||||
@mappings = {}
|
||||
@repository.repository_columns.order(:id).each do |column|
|
||||
@mappings[column.id] = index.to_s
|
||||
index += 1
|
||||
end
|
||||
end
|
||||
|
||||
def process_query
|
||||
order_obj = build_conditions(@params)[:order_by_column]
|
||||
search_value = build_conditions(@params)[:search_value]
|
||||
records = search_value.present? ? search(search_value) : fetch_records
|
||||
@repository_rows = sort_rows(order_obj, records)
|
||||
end
|
||||
|
||||
def fetch_records
|
||||
repository_rows = RepositoryRow.preload(:repository_columns,
|
||||
:created_by,
|
||||
repository_cells: :value)
|
||||
.joins(:created_by)
|
||||
.where(repository: @repository)
|
||||
if @my_module
|
||||
@assigned_rows = @my_module.repository_rows
|
||||
.preload(
|
||||
:repository_columns,
|
||||
:created_by,
|
||||
repository_cells: :value
|
||||
)
|
||||
.joins(:created_by)
|
||||
.where(repository: @repository)
|
||||
return @assigned_rows if @params[:assigned] == 'assigned'
|
||||
else
|
||||
@assigned_rows = repository_rows.joins(:my_module_repository_rows)
|
||||
end
|
||||
repository_rows
|
||||
end
|
||||
|
||||
def search(value)
|
||||
includes_json = { repository_cells: Extends::REPOSITORY_SEARCH_INCLUDES }
|
||||
searchable_attributes = ['repository_rows.name', 'users.full_name'] +
|
||||
Extends::REPOSITORY_EXTRA_SEARCH_ATTR
|
||||
|
||||
RepositoryRow.left_outer_joins(:created_by)
|
||||
.left_outer_joins(includes_json)
|
||||
.where(repository: @repository)
|
||||
.where_attributes_like(searchable_attributes, value)
|
||||
.distinct
|
||||
end
|
||||
|
||||
def build_conditions(params)
|
||||
search_value = params[:search][:value]
|
||||
order = params[:order].values.first
|
||||
order_by_column = { column: order[:column].to_i,
|
||||
dir: order[:dir] }
|
||||
{ search_value: search_value, order_by_column: order_by_column }
|
||||
end
|
||||
|
||||
def sortable_columns
|
||||
array = [
|
||||
'assigned',
|
||||
'repository_rows.name',
|
||||
'repository_rows.created_at',
|
||||
'users.full_name'
|
||||
]
|
||||
@repository.repository_columns.count.times do
|
||||
array << 'repository_cell.value'
|
||||
end
|
||||
array
|
||||
end
|
||||
|
||||
def sort_rows(column_obj, records)
|
||||
dir = %w(DESC ASC).find do |direction|
|
||||
direction == column_obj[:dir].upcase
|
||||
end || 'ASC'
|
||||
column_index = column_obj[:column]
|
||||
col_order = @repository.repository_table_states
|
||||
.find_by_user_id(@user.id)
|
||||
.state['ColReorder']
|
||||
column_id = col_order[column_index].to_i
|
||||
|
||||
if sortable_columns[column_id - 1] == 'assigned'
|
||||
return records if @my_module && @params[:assigned] == 'assigned'
|
||||
if @my_module
|
||||
# Depending on the sort, order nulls first or
|
||||
# nulls last on repository_cells association
|
||||
return records.joins(
|
||||
"LEFT OUTER JOIN my_module_repository_rows ON
|
||||
(repository_rows.id =
|
||||
my_module_repository_rows.repository_row_id
|
||||
AND (my_module_repository_rows.my_module_id =
|
||||
#{@my_module.id}
|
||||
OR my_module_repository_rows.id IS NULL))"
|
||||
).order(
|
||||
"my_module_repository_rows.id NULLS
|
||||
#{sort_null_direction(dir)}"
|
||||
)
|
||||
else
|
||||
return sort_assigned_records(records, dir)
|
||||
end
|
||||
elsif sortable_columns[column_id - 1] == 'repository_cell.value'
|
||||
id = @mappings.key(column_id.to_s)
|
||||
type = RepositoryColumn.find_by_id(id)
|
||||
return records unless type
|
||||
return select_type(type.data_type, records, id, dir)
|
||||
else
|
||||
return records.order(
|
||||
"#{sortable_columns[column_id - 1]} #{dir}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def sort_assigned_records(records, direction)
|
||||
assigned = records.joins(:my_module_repository_rows)
|
||||
.distinct
|
||||
.pluck(:id)
|
||||
unassigned = records.where.not(id: assigned).pluck(:id)
|
||||
if direction == 'ASC'
|
||||
ids = assigned + unassigned
|
||||
elsif direction == 'DESC'
|
||||
ids = unassigned + assigned
|
||||
end
|
||||
|
||||
order_by_index = ActiveRecord::Base.send(
|
||||
:sanitize_sql_array,
|
||||
["position((',' || repository_rows.id || ',') in ?)",
|
||||
ids.join(',') + ',']
|
||||
)
|
||||
records.order(order_by_index)
|
||||
end
|
||||
|
||||
def select_type(type, records, id, dir)
|
||||
case type
|
||||
when 'RepositoryTextValue'
|
||||
filter_by_text_value(records, id, dir)
|
||||
when 'RepositoryListValue'
|
||||
filter_by_list_value(records, id, dir)
|
||||
else
|
||||
records
|
||||
end
|
||||
end
|
||||
|
||||
def sort_null_direction(val)
|
||||
val == 'ASC' ? 'LAST' : 'FIRST'
|
||||
end
|
||||
|
||||
def filter_by_text_value(records, id, dir)
|
||||
records.joins(
|
||||
"LEFT OUTER JOIN (SELECT repository_cells.repository_row_id,
|
||||
repository_text_values.data AS value
|
||||
FROM repository_cells
|
||||
INNER JOIN repository_text_values
|
||||
ON repository_text_values.id = repository_cells.value_id
|
||||
WHERE repository_cells.repository_column_id = #{id}) AS values
|
||||
ON values.repository_row_id = repository_rows.id"
|
||||
).order("values.value #{dir}")
|
||||
end
|
||||
|
||||
def filter_by_list_value(records, id, dir)
|
||||
records.joins(
|
||||
"LEFT OUTER JOIN (SELECT repository_cells.repository_row_id,
|
||||
repository_list_items.data AS value
|
||||
FROM repository_cells
|
||||
INNER JOIN repository_list_values
|
||||
ON repository_list_values.id = repository_cells.value_id
|
||||
INNER JOIN repository_list_items
|
||||
ON repository_list_values.repository_list_item_id =
|
||||
repository_list_items.id
|
||||
WHERE repository_cells.repository_column_id = #{id}) AS values
|
||||
ON values.repository_row_id = repository_rows.id"
|
||||
).order("values.value #{dir}")
|
||||
end
|
||||
end
|
|
@ -64,6 +64,7 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= render "shared/left_menu_bar" if user_signed_in? %>
|
||||
<div id="content-wrapper">
|
||||
<%= yield :content %>
|
||||
</div>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<th id="added-on"><%= t("repositories.table.added_on") %></th>
|
||||
<th id="added-by"><%= t("repositories.table.added_by") %></th>
|
||||
<% repository.repository_columns.order(:id).each do |column| %>
|
||||
<th class="repository-column" id="<%= column.id %>"
|
||||
<th class="repository-column" id="<%= column.id %>" data-type="<%= column.data_type %>"
|
||||
<%= 'data-editable' if can_manage_repository_column?(column) %>
|
||||
<%= 'data-deletable' if can_manage_repository_column?(column) %>
|
||||
<%= "data-edit-url='#{edit_repository_repository_column_path(repository, column)}'" %>
|
||||
|
|
10
app/views/repository_rows/index.json.jbuilder
Normal file
10
app/views/repository_rows/index.json.jbuilder
Normal file
|
@ -0,0 +1,10 @@
|
|||
json.draw @draw
|
||||
json.recordsTotal @repository_rows.total_count
|
||||
json.recordsFiltered @repository_row_count
|
||||
json.data do
|
||||
json.array! prepare_row_columns(@repository_rows,
|
||||
@repository,
|
||||
@columns_mappings,
|
||||
@repository.team,
|
||||
@assigned_rows)
|
||||
end
|
48
app/views/shared/_left_menu_bar.html.erb
Normal file
48
app/views/shared/_left_menu_bar.html.erb
Normal file
|
@ -0,0 +1,48 @@
|
|||
<div id="left-menu-bar" class="menu-bar">
|
||||
<ul class="nav">
|
||||
<li class="<%= "active" if projects_are_selected? %>">
|
||||
<a id="projects-link" title="<%= t('left_menu_bar.projects') %>" href="<%= projects_path %>">
|
||||
<span class="glyphicon glyphicon-home"></span>
|
||||
<span><%= t('left_menu_bar.projects') %></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="<%= "active" if repositories_are_selected? %>">
|
||||
<a id="repositories-link" title="<%= t('left_menu_bar.repositories') %>" href="<%= team_repositories_path current_team %>">
|
||||
<span class="fa fa-cubes" aria-hidden="true"></span>
|
||||
<span><%= t('left_menu_bar.repositories') %></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="<%= "active" if templates_are_selected? %>">
|
||||
<a id="templates-link" title="<%= t('left_menu_bar.templates') %>" href="<%= protocols_path %>">
|
||||
<span class="glyphicon glyphicon-list-alt"></span>
|
||||
<span><%= t('left_menu_bar.templates') %></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="<%= "active" if reports_are_selected? %>">
|
||||
<a id="reports-link" title="<%= t('left_menu_bar.reports') %>" href="#">
|
||||
<span class="glyphicon glyphicon-indent-left"></span>
|
||||
<span><%= t('left_menu_bar.reports') %></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="<%= "active" if settings_are_selected? %>">
|
||||
<a id="settings-link" title="<%= t('left_menu_bar.settings') %>" href="<%= edit_user_registration_path %>">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
<span><%= t('left_menu_bar.settings') %></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav nav-bottom">
|
||||
<li class="<%= "active" if activities_are_selected? %>">
|
||||
<a id="activities-link" title="<%= t('left_menu_bar.activities') %>" href="<%= activities_path %>">
|
||||
<span class="glyphicon glyphicon-equalizer"></span>
|
||||
<span><%= t('left_menu_bar.activities') %></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="<%= "active" if help_is_selected? %>">
|
||||
<a id="activities-link" title="<%= t('left_menu_bar.help') %>" href="#">
|
||||
<span class="glyphicon glyphicon-paperclip"></span>
|
||||
<span><%= t('left_menu_bar.help') %></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
|
@ -86,27 +86,6 @@
|
|||
</div>
|
||||
<% end %>
|
||||
</li>
|
||||
<li id="power-up-button">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-danger dropdown-toggle"
|
||||
type="button"
|
||||
id="power-up-dropdown"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="true">
|
||||
<%=t 'nav.power_up' %>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="power-up-dropdown">
|
||||
<li class="dropdown-header"><%=t 'nav.power_up_dropdown.title' %></li>
|
||||
<li><span class="glyphicon glyphicon-certificate"></span> <%=t 'nav.power_up_dropdown.unlimited_teams' %></li>
|
||||
<li><span class="glyphicon glyphicon-certificate"></span> <%=t 'nav.power_up_dropdown.unlimited_users' %></li>
|
||||
<li><span class="glyphicon glyphicon-certificate"></span> <%=t 'nav.power_up_dropdown.unlimited_storage' %></li>
|
||||
<li><span class="glyphicon glyphicon-certificate"></span> <%=t 'nav.power_up_dropdown.manuscript' %></li>
|
||||
<li><%=t 'nav.power_up_dropdown.much_more' %></li>
|
||||
<li class="text-center"><a href="#" class=""></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- greetings -->
|
||||
<li id="user-account-dropdown" class="dropdown">
|
||||
|
|
|
@ -859,6 +859,31 @@ class Constants
|
|||
]
|
||||
}.freeze
|
||||
|
||||
# Repository default table state
|
||||
REPOSITORY_TABLE_DEFAULT_STATE = {
|
||||
'time' => 0,
|
||||
'start' => 0,
|
||||
'length' => 5,
|
||||
'order' => [[2, 'desc']],
|
||||
'search' => { 'search' => '',
|
||||
'smart' => true,
|
||||
'regex' => false,
|
||||
'caseInsensitive' => true },
|
||||
'columns' => [],
|
||||
'assigned' => 'assigned',
|
||||
'ColReorder' => [*0..4]
|
||||
}
|
||||
5.times do
|
||||
REPOSITORY_TABLE_DEFAULT_STATE['columns'] << {
|
||||
'visible' => true,
|
||||
'search' => { 'search' => '',
|
||||
'smart' => true,
|
||||
'regex' => false,
|
||||
'caseInsensitive' => true }
|
||||
}
|
||||
end
|
||||
REPOSITORY_TABLE_DEFAULT_STATE.freeze
|
||||
|
||||
EXPORTABLE_ZIP_EXPIRATION_DAYS = 7
|
||||
|
||||
# Very basic regex to check for validity of emails
|
||||
|
|
|
@ -69,14 +69,6 @@ en:
|
|||
nav:
|
||||
search: "Search for something..."
|
||||
search_button: 'Go!'
|
||||
power_up: 'Power Up!'
|
||||
power_up_dropdown:
|
||||
title: 'Go premum and power up!'
|
||||
unlimited_teams: 'Unlimited Teams'
|
||||
unlimited_users: 'Unlimited Users'
|
||||
unlimited_storage: 'Unlimited Storage'
|
||||
manuscript: 'ManuScript'
|
||||
much_more: 'And much, much more...'
|
||||
user_greeting: "Hi, %{full_name}"
|
||||
advanced_search: "Advanced search"
|
||||
title: "sciNote"
|
||||
|
@ -110,6 +102,15 @@ en:
|
|||
core_version: "sciNote core version"
|
||||
addon_versions: "Addon versions"
|
||||
|
||||
left_menu_bar:
|
||||
projects: "Projects"
|
||||
repositories: "Libraries"
|
||||
templates: "Templates"
|
||||
reports: "Reports"
|
||||
settings: "Settings"
|
||||
activities: "Activities"
|
||||
help: "Help"
|
||||
|
||||
sidebar:
|
||||
title: "Navigation"
|
||||
no_module_group: "No workflow"
|
||||
|
@ -995,12 +996,12 @@ en:
|
|||
head_title: "Libraries | %{library}"
|
||||
repository_row:
|
||||
modal_info:
|
||||
head_title: "Information for record '%{repository_row}'"
|
||||
head_title: "Information for item '%{repository_row}'"
|
||||
added_on: "Added on"
|
||||
added_by: "Added by"
|
||||
custom_field: "%{cf}: "
|
||||
title: "This record is assigned to %{nr} tasks."
|
||||
no_tasks: "This record in not assigned to any task."
|
||||
title: "This item is assigned to %{nr} tasks."
|
||||
no_tasks: "This item in not assigned to any task."
|
||||
samples:
|
||||
columns: "Columns"
|
||||
columns_visibility: "Toggle visibility"
|
||||
|
|
|
@ -439,7 +439,8 @@ Rails.application.routes.draw do
|
|||
post 'archive', to: 'protocols#archive'
|
||||
post 'restore', to: 'protocols#restore'
|
||||
post 'import', to: 'protocols#import'
|
||||
post 'protocolsio_import_create', to: 'protocols#protocolsio_import_create'
|
||||
post 'protocolsio_import_create',
|
||||
to: 'protocols#protocolsio_import_create'
|
||||
post 'protocolsio_import_save', to: 'protocols#protocolsio_import_save'
|
||||
get 'export', to: 'protocols#export'
|
||||
end
|
||||
|
@ -447,7 +448,7 @@ Rails.application.routes.draw do
|
|||
|
||||
resources :repositories do
|
||||
post 'repository_index',
|
||||
to: 'repositories#repository_table_index',
|
||||
to: 'repository_rows#index',
|
||||
as: 'table_index',
|
||||
defaults: { format: 'json' }
|
||||
# Save repository table state
|
||||
|
@ -470,7 +471,6 @@ Rails.application.routes.draw do
|
|||
as: 'columns_destroy_html'
|
||||
|
||||
resources :repository_columns, only: %i(create edit update destroy)
|
||||
|
||||
resources :repository_rows, only: %i(create edit update)
|
||||
member do
|
||||
post 'parse_sheet'
|
||||
|
@ -478,6 +478,9 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
post 'repository_list_items', to: 'repository_list_items#search',
|
||||
defaults: { format: 'json' }
|
||||
|
||||
get 'repository_rows/:id', to: 'repository_rows#show',
|
||||
as: :repository_row,
|
||||
defaults: { format: 'json' }
|
||||
|
|
86
spec/controllers/repository_list_items_controller_spec.rb
Normal file
86
spec/controllers/repository_list_items_controller_spec.rb
Normal file
|
@ -0,0 +1,86 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe RepositoryListItemsController, type: :controller do
|
||||
login_user
|
||||
render_views
|
||||
let(:user) { User.first }
|
||||
let!(:team) { create :team, created_by: user }
|
||||
let!(:user_team) { create :user_team, team: team, user: user }
|
||||
let!(:repository) { create :repository, team: team, created_by: user }
|
||||
let!(:repository_column_one) do
|
||||
create :repository_column, repository: repository,
|
||||
created_by: user,
|
||||
name: 'List Value',
|
||||
data_type: 'RepositoryListValue'
|
||||
end
|
||||
let!(:repository_list_item_one) do
|
||||
create :repository_list_item, data: 'item one',
|
||||
repository: repository,
|
||||
repository_column: repository_column_one
|
||||
end
|
||||
let!(:repository_list_item_two) do
|
||||
create :repository_list_item, data: 'item two',
|
||||
repository: repository,
|
||||
repository_column: repository_column_one
|
||||
end
|
||||
|
||||
let!(:user_two) { create :user, email: 'new@user.com' }
|
||||
let!(:team_two) { create :team, created_by: user }
|
||||
let!(:user_team_two) { create :user_team, team: team_two, user: user_two }
|
||||
let!(:repository_two) do
|
||||
create :repository, team: team_two, created_by: user_two
|
||||
end
|
||||
let!(:user_two_repository_column_two) do
|
||||
create :repository_column, repository: repository_two,
|
||||
created_by: user_two,
|
||||
name: 'List Value',
|
||||
data_type: 'RepositoryListValue'
|
||||
end
|
||||
let!(:user_two_repository_list_item_one) do
|
||||
create :repository_list_item,
|
||||
data: 'item one of user two',
|
||||
repository: repository_two,
|
||||
repository_column: user_two_repository_column_two,
|
||||
created_by: user_two,
|
||||
last_modified_by: user_two
|
||||
end
|
||||
let!(:user_two_repository_list_item_two) do
|
||||
create :repository_list_item,
|
||||
data: 'item two of user two',
|
||||
repository: repository_two,
|
||||
repository_column: user_two_repository_column_two,
|
||||
created_by: user_two,
|
||||
last_modified_by: user_two
|
||||
end
|
||||
|
||||
describe '#search' do
|
||||
let(:params) { { q: '', column_id: repository_column_one.id } }
|
||||
it 'returns all column\'s list items' do
|
||||
get :search, format: :json, params: params
|
||||
expect(response).to have_http_status(:ok)
|
||||
body = JSON.parse(response.body)
|
||||
expect(body['list_items'].count).to eq 2
|
||||
end
|
||||
|
||||
it 'returns queried item' do
|
||||
params[:q] = 'item one'
|
||||
get :search, format: :json, params: params
|
||||
expect(response).to have_http_status(:success)
|
||||
body = JSON.parse(response.body)
|
||||
expect(body['list_items'].count).to eq 1
|
||||
expect(body['list_items'].first['data']).to eq 'item one'
|
||||
end
|
||||
|
||||
it 'returns a 404 error if column does not exist' do
|
||||
params[:column_id] = 999999
|
||||
get :search, format: :json, params: params
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
|
||||
it 'returns a 403 error user does not have permissions' do
|
||||
params[:column_id] = user_two_repository_column_two.id
|
||||
get :search, format: :json, params: params
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,6 +7,13 @@ describe RepositoryRowsController, type: :controller do
|
|||
let!(:team) { create :team, created_by: user }
|
||||
let!(:user_team) { create :user_team, team: team, user: user }
|
||||
let!(:repository) { create :repository, team: team, created_by: user }
|
||||
let!(:repository_state) do
|
||||
RepositoryTableState.create(
|
||||
repository: repository,
|
||||
user: user,
|
||||
state: Constants::REPOSITORY_TABLE_DEFAULT_STATE
|
||||
)
|
||||
end
|
||||
let!(:repository_row) do
|
||||
create :repository_row, repository: repository,
|
||||
created_by: user,
|
||||
|
@ -41,4 +48,71 @@ describe RepositoryRowsController, type: :controller do
|
|||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
context '#index' do
|
||||
before do
|
||||
repository.repository_rows.destroy_all
|
||||
110.times do |index|
|
||||
create :repository_row, name: "row (#{index})",
|
||||
repository: repository,
|
||||
created_by: user,
|
||||
last_modified_by: user
|
||||
end
|
||||
end
|
||||
|
||||
describe 'json object' do
|
||||
it 'returns a valid object' do
|
||||
params = { order: { 0 => { column: '3', dir: 'asc' } },
|
||||
drow: '0',
|
||||
search: { value: '' },
|
||||
length: '10',
|
||||
start: '1',
|
||||
repository_id: repository.id }
|
||||
get :index, params: params, format: :json
|
||||
|
||||
expect(response.status).to eq 200
|
||||
expect(response).to match_response_schema('repository_row_datatables')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'pagination' do
|
||||
it 'returns first 10 records' do
|
||||
params = { order: { 0 => { column: '3', dir: 'asc' } },
|
||||
drow: '0',
|
||||
search: { value: '' },
|
||||
length: '10',
|
||||
start: '1',
|
||||
repository_id: repository.id }
|
||||
get :index, params: params, format: :json
|
||||
response_body = JSON.parse(response.body)
|
||||
expect(response_body['data'].length).to eq 10
|
||||
expect(response_body['data'].first['2']).to eq 'row (0)'
|
||||
end
|
||||
|
||||
it 'returns next 10 records' do
|
||||
params = { order: { 0 => { column: '3', dir: 'asc' } },
|
||||
drow: '0',
|
||||
search: { value: '' },
|
||||
length: '10',
|
||||
start: '11',
|
||||
repository_id: repository.id }
|
||||
get :index, params: params, format: :json
|
||||
response_body = JSON.parse(response.body)
|
||||
expect(response_body['data'].length).to eq 10
|
||||
expect(response_body['data'].first['2']).to eq 'row (10)'
|
||||
end
|
||||
|
||||
it 'returns first 25 records' do
|
||||
params = { order: { 0 => { column: '2', dir: 'desc' } },
|
||||
drow: '0',
|
||||
search: { value: '' },
|
||||
length: '25',
|
||||
start: '1',
|
||||
repository_id: repository.id }
|
||||
get :index, params: params, format: :json
|
||||
response_body = JSON.parse(response.body)
|
||||
expect(response_body['data'].length).to eq 25
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,7 +26,6 @@ describe AssetTextDatum, type: :model do
|
|||
it 'should have uniq asset' do
|
||||
create :asset_text_datum, asset: asset
|
||||
new_atd = build :asset_text_datum, asset: asset
|
||||
# binding.pry
|
||||
expect(new_atd).to_not be_valid
|
||||
end
|
||||
end
|
||||
|
|
|
@ -98,8 +98,8 @@ RSpec.describe RepositoryListValue, type: :model do
|
|||
|
||||
it 'retuns only the the item related to the list' do
|
||||
repository_row_two = create :repository_row, name: 'New row'
|
||||
repository_list_value_two =
|
||||
create :repository_list_value, repository_cell_attributes: {
|
||||
create :repository_list_value,
|
||||
repository_cell_attributes: {
|
||||
repository_column: repository_column,
|
||||
repository_row: repository_row_two
|
||||
}
|
||||
|
@ -113,16 +113,15 @@ RSpec.describe RepositoryListValue, type: :model do
|
|||
end
|
||||
|
||||
it 'returns an empty string if no item selected' do
|
||||
list_item = create :repository_list_item,
|
||||
data: 'my item',
|
||||
create :repository_list_item, data: 'my item',
|
||||
repository: repository,
|
||||
repository_column: repository_column
|
||||
expect(repository_list_value.reload.data).to eq nil
|
||||
expect(repository_list_value.reload.data).to be_nil
|
||||
end
|
||||
|
||||
it 'returns an empty string if item does not exists' do
|
||||
repository_list_value.repository_list_item = nil
|
||||
expect(repository_list_value.reload.data).to eq nil
|
||||
expect(repository_list_value.reload.data).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
133
spec/services/repository_datatable_service_spec.rb
Normal file
133
spec/services/repository_datatable_service_spec.rb
Normal file
|
@ -0,0 +1,133 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe RepositoryDatatableService do
|
||||
let!(:team) { create :team }
|
||||
let!(:user) { create :user, email: 'user_one@asdf.com' }
|
||||
let!(:repository) do
|
||||
create :repository, name: 'my repo',
|
||||
created_by: user,
|
||||
team: team
|
||||
end
|
||||
let!(:repository_column) do
|
||||
create :repository_column, name: 'My column',
|
||||
data_type: :RepositoryListValue
|
||||
end
|
||||
let!(:repository_state) do
|
||||
RepositoryTableState.create(
|
||||
repository: repository,
|
||||
user: user,
|
||||
state: Constants::REPOSITORY_TABLE_DEFAULT_STATE
|
||||
)
|
||||
end
|
||||
let!(:repository_row) do
|
||||
create :repository_row, name: 'A row',
|
||||
repository: repository,
|
||||
created_by: user,
|
||||
last_modified_by: user
|
||||
end
|
||||
let!(:repository_row_two) do
|
||||
create :repository_row, name: 'B row',
|
||||
repository: repository,
|
||||
created_by: user,
|
||||
last_modified_by: user
|
||||
end
|
||||
let!(:list_item) do
|
||||
create :repository_list_item,
|
||||
data: 'bug',
|
||||
repository: repository,
|
||||
repository_column: repository_column,
|
||||
created_by: user,
|
||||
last_modified_by: user
|
||||
end
|
||||
let!(:repository_list_value) do
|
||||
create :repository_list_value,
|
||||
repository_list_item: list_item,
|
||||
created_by: user,
|
||||
last_modified_by: user,
|
||||
repository_cell_attributes: {
|
||||
repository_column: repository_column,
|
||||
repository_row: repository_row
|
||||
}
|
||||
end
|
||||
|
||||
context 'object' do
|
||||
let(:params) do
|
||||
{ order: { 0 => { column: '2', dir: 'asc' } },
|
||||
search: { value: 'row' } }
|
||||
end
|
||||
|
||||
let(:subject) do
|
||||
RepositoryDatatableService.new(repository, params, user)
|
||||
end
|
||||
|
||||
describe '#build_conditions/1' do
|
||||
it 'parsers the contitions' do
|
||||
contitions = subject.send(:build_conditions, params)
|
||||
expect(contitions[:search_value]).to eq 'row'
|
||||
expect(contitions[:order_by_column]).to eq(
|
||||
{ column: 2, dir: 'asc' }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sortable_columns' do
|
||||
it 'returns an array of all columns that are sortable' do
|
||||
columns = subject.send(:sortable_columns)
|
||||
expect(columns.length).to eq 5
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sort_null_direction' do
|
||||
it 'returns LAST if value is ascending' do
|
||||
result = subject.send(:sort_null_direction, 'ASC')
|
||||
expect(result).to eq 'LAST'
|
||||
end
|
||||
|
||||
it 'returns FIRST if value is not ascending' do
|
||||
result = subject.send(:sort_null_direction, 'DESC')
|
||||
expect(result).to eq 'FIRST'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'ordering' do
|
||||
it 'is ordered by row name asc' do
|
||||
params = { order: { 0 => { column: '2', dir: 'asc' } },
|
||||
search: { value: '' } }
|
||||
subject = RepositoryDatatableService.new(repository,
|
||||
params,
|
||||
user)
|
||||
expect(subject.repository_rows.first.name).to eq 'A row'
|
||||
expect(subject.repository_rows.last.name).to eq 'B row'
|
||||
end
|
||||
|
||||
it 'is ordered by row name desc' do
|
||||
params = { order: { 0 => { column: '2', dir: 'desc' } },
|
||||
search: { value: '' } }
|
||||
subject = RepositoryDatatableService.new(repository,
|
||||
params,
|
||||
user)
|
||||
expect(subject.repository_rows.first.name).to eq 'B row'
|
||||
expect(subject.repository_rows.last.name).to eq 'A row'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'search' do
|
||||
before do
|
||||
create :repository_row, name: 'test',
|
||||
repository: repository,
|
||||
created_by: user,
|
||||
last_modified_by: user
|
||||
end
|
||||
|
||||
it 'returns only the searched entity' do
|
||||
params = { order: { 0 => { column: '2', dir: 'desc' } },
|
||||
search: { value: 'test' } }
|
||||
subject = RepositoryDatatableService.new(repository,
|
||||
params,
|
||||
user)
|
||||
expect(subject.repository_rows.first.name).to eq 'test'
|
||||
expect(subject.repository_rows.count).to eq 1
|
||||
end
|
||||
end
|
||||
end
|
25
spec/support/api/schemas/repository_row_datatables.json
Normal file
25
spec/support/api/schemas/repository_row_datatables.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"type": "object",
|
||||
"required": ["draw", "recordsTotal", "recordsFiltered", "data"],
|
||||
"properties": {
|
||||
"draw": { "type": "integer" },
|
||||
"recordsTotal": { "type": "integer" },
|
||||
"recordsFiltered": { "type": "integer" },
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items":{
|
||||
"required": ["DT_RowId", "1", "2", "3", "4", "recordEditUrl", "recordUpdateUrl", "recordInfoUrl"],
|
||||
"properties": {
|
||||
"DT_RowId": { "type": "integer" },
|
||||
"1": { "type": "string" },
|
||||
"2": { "type": "string" },
|
||||
"3": { "type": "string" },
|
||||
"4": { "type": "string" },
|
||||
"recordEditUrl": { "type": "string" },
|
||||
"recordUpdateUrl": { "type": "string" },
|
||||
"recordInfoUrl": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
vendor/assets/javascripts/ajax-bootstrap-select.min.js
vendored
Normal file
21
vendor/assets/javascripts/ajax-bootstrap-select.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
16
vendor/assets/stylesheets/ajax-bootstrap-select.min.css
vendored
Normal file
16
vendor/assets/stylesheets/ajax-bootstrap-select.min.css
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*!
|
||||
* Ajax Bootstrap Select
|
||||
*
|
||||
* Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
|
||||
*
|
||||
* @version 1.4.3
|
||||
* @author Adam Heim - https://github.com/truckingsim
|
||||
* @link https://github.com/truckingsim/Ajax-Bootstrap-Select
|
||||
* @copyright 2017 Adam Heim
|
||||
* @license Released under the MIT license.
|
||||
*
|
||||
* Contributors:
|
||||
* Mark Carver - https://github.com/markcarver
|
||||
*
|
||||
* Last build: 2017-11-15 1:19:47 PM EST
|
||||
*/.bootstrap-select .status{background:#f0f0f0;clear:both;color:#999;font-size:11px;font-style:italic;font-weight:500;line-height:1;margin-bottom:-5px;padding:10px 20px}
|
Loading…
Reference in a new issue