mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-11-13 01:41:09 +08:00
Merge pull request #1017 from ZmagoD/zd_SCI_2068
Write server-side endpoints for custom repositories [SCI-2068]
This commit is contained in:
commit
75d7ace8c7
25 changed files with 957 additions and 412 deletions
2
Gemfile
2
Gemfile
|
|
@ -27,7 +27,7 @@ gem 'momentjs-rails', '~> 2.17.1'
|
||||||
# JS datetime picker
|
# JS datetime picker
|
||||||
gem 'bootstrap3-datetimepicker-rails', '~> 4.15.35'
|
gem 'bootstrap3-datetimepicker-rails', '~> 4.15.35'
|
||||||
# Select elements for Bootstrap
|
# Select elements for Bootstrap
|
||||||
gem 'bootstrap-select-rails', '~> 1.6.3'
|
gem 'bootstrap-select-rails', '~> 1.12.4'
|
||||||
gem 'uglifier', '>= 1.3.0'
|
gem 'uglifier', '>= 1.3.0'
|
||||||
# jQuery & plugins
|
# jQuery & plugins
|
||||||
gem 'jquery-turbolinks'
|
gem 'jquery-turbolinks'
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ GEM
|
||||||
bootstrap-sass (3.3.7)
|
bootstrap-sass (3.3.7)
|
||||||
autoprefixer-rails (>= 5.2.1)
|
autoprefixer-rails (>= 5.2.1)
|
||||||
sass (>= 3.3.4)
|
sass (>= 3.3.4)
|
||||||
bootstrap-select-rails (1.6.3)
|
bootstrap-select-rails (1.12.4)
|
||||||
bootstrap3-datetimepicker-rails (4.15.35)
|
bootstrap3-datetimepicker-rails (4.15.35)
|
||||||
momentjs-rails (>= 2.8.1)
|
momentjs-rails (>= 2.8.1)
|
||||||
bootstrap_form (2.7.0)
|
bootstrap_form (2.7.0)
|
||||||
|
|
@ -534,7 +534,7 @@ DEPENDENCIES
|
||||||
better_errors
|
better_errors
|
||||||
binding_of_caller
|
binding_of_caller
|
||||||
bootstrap-sass (~> 3.3.7)
|
bootstrap-sass (~> 3.3.7)
|
||||||
bootstrap-select-rails (~> 1.6.3)
|
bootstrap-select-rails (~> 1.12.4)
|
||||||
bootstrap3-datetimepicker-rails (~> 4.15.35)
|
bootstrap3-datetimepicker-rails (~> 4.15.35)
|
||||||
bootstrap_form
|
bootstrap_form
|
||||||
bullet
|
bullet
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,12 @@
|
||||||
//= require tinymce-jquery
|
//= require tinymce-jquery
|
||||||
//= require jsPlumb-2.0.4-min
|
//= require jsPlumb-2.0.4-min
|
||||||
//= require jsnetworkx
|
//= require jsnetworkx
|
||||||
|
//= require dataTables.noSearchHidden
|
||||||
|
//= require bootstrap-select
|
||||||
//= require_directory ./sitewide
|
//= require_directory ./sitewide
|
||||||
//= require jquery.dataTables.yadcf
|
//= require jquery.dataTables.yadcf
|
||||||
//= require datatables
|
//= require datatables
|
||||||
//= require dataTables.noSearchHidden
|
//= require ajax-bootstrap-select.min
|
||||||
//= require bootstrap-select
|
|
||||||
//= require underscore
|
//= require underscore
|
||||||
//= require i18n.js
|
//= require i18n.js
|
||||||
//= require i18n/translations
|
//= require i18n/translations
|
||||||
|
|
|
||||||
|
|
@ -432,14 +432,20 @@ var RepositoryDatatable = (function(global) {
|
||||||
} else if ($(th).attr('id') === 'row-name') {
|
} else if ($(th).attr('id') === 'row-name') {
|
||||||
input = changeToInputField('repository_row', 'name', '');
|
input = changeToInputField('repository_row', 'name', '');
|
||||||
tr.appendChild(createTdElement(input));
|
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'), '');
|
input = changeToInputField('repository_cell', th.attr('id'), '');
|
||||||
tr.appendChild(createTdElement(input));
|
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 {
|
} else {
|
||||||
// Column we don't care for, just add empty td
|
// Column we don't care for, just add empty td
|
||||||
tr.appendChild(createTdElement(''));
|
tr.appendChild(createTdElement(''));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('table' + TABLE_ID).prepend(tr);
|
$('table' + TABLE_ID).prepend(tr);
|
||||||
selectedRecord = tr;
|
selectedRecord = tr;
|
||||||
|
|
||||||
|
|
@ -451,6 +457,8 @@ var RepositoryDatatable = (function(global) {
|
||||||
});
|
});
|
||||||
// Adjust columns width in table header
|
// Adjust columns width in table header
|
||||||
adjustTableHeader();
|
adjustTableHeader();
|
||||||
|
// Init selectpicker
|
||||||
|
_initSelectPicker();
|
||||||
}
|
}
|
||||||
|
|
||||||
global.onClickToggleAssignedRecords = function() {
|
global.onClickToggleAssignedRecords = function() {
|
||||||
|
|
@ -584,6 +592,7 @@ var RepositoryDatatable = (function(global) {
|
||||||
|
|
||||||
// Take care of custom cells
|
// Take care of custom cells
|
||||||
var cells = data.repository_row.repository_cells;
|
var cells = data.repository_row.repository_cells;
|
||||||
|
var list_columns = data.repository_row.repository_column_items;
|
||||||
$(node).children('td').each(function(i) {
|
$(node).children('td').each(function(i) {
|
||||||
var td = $(this);
|
var td = $(this);
|
||||||
var rawIndex = TABLE.column.index('fromVisible', i);
|
var rawIndex = TABLE.column.index('fromVisible', i);
|
||||||
|
|
@ -592,13 +601,17 @@ var RepositoryDatatable = (function(global) {
|
||||||
// Check if cell on this record exists
|
// Check if cell on this record exists
|
||||||
var cell = cells[$(colHeader).attr('id')];
|
var cell = cells[$(colHeader).attr('id')];
|
||||||
if (cell) {
|
if (cell) {
|
||||||
td.html(changeToInputField('repository_cell',
|
td.html(changeToFormField('repository_cell',
|
||||||
$(colHeader).attr('id'),
|
$(colHeader).attr('id'),
|
||||||
cell.value));
|
cell,
|
||||||
|
list_columns));
|
||||||
} else {
|
} else {
|
||||||
td.html(changeToInputField('repository_cell',
|
td.html(changeToFormField('repository_cell',
|
||||||
$(colHeader).attr('id'), ''));
|
$(colHeader).attr('id'),
|
||||||
|
'',
|
||||||
|
list_columns));
|
||||||
}
|
}
|
||||||
|
_initSelectPicker();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -647,13 +660,18 @@ var RepositoryDatatable = (function(global) {
|
||||||
// Record name
|
// Record name
|
||||||
data.repository_row.name = $('td input[data-object = repository_row]').val();
|
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() {
|
$(node).find('td input[data-object = repository_cell]').each(function() {
|
||||||
// Send data only and only if cell is not empty
|
// Send data only and only if cell is not empty
|
||||||
if ($(this).val().trim()) {
|
if ($(this).val().trim()) {
|
||||||
data.repository_cells[$(this).attr('name')] = $(this).val();
|
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 url;
|
||||||
var type;
|
var type;
|
||||||
|
|
@ -845,6 +863,88 @@ var RepositoryDatatable = (function(global) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper functions
|
// 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) {
|
function getColumnIndex(id) {
|
||||||
if (id < 0) {
|
if (id < 0) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -858,6 +958,24 @@ var RepositoryDatatable = (function(global) {
|
||||||
object + "' name='" + name + "' value='" + value + "'></input></div>";
|
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
|
// Return td element with content
|
||||||
function createTdElement(content) {
|
function createTdElement(content) {
|
||||||
var td = document.createElement('td');
|
var td = document.createElement('td');
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
@import "bootstrap-tagsinput";
|
@import "bootstrap-tagsinput";
|
||||||
@import "bootstrap-tagsinput-typeahead";
|
@import "bootstrap-tagsinput-typeahead";
|
||||||
@import "handsontable.full.min";
|
@import "handsontable.full.min";
|
||||||
|
@import "ajax-bootstrap-select.min";
|
||||||
@import "extend/bootstrap";
|
@import "extend/bootstrap";
|
||||||
@import "font-awesome";
|
@import "font-awesome";
|
||||||
@import "themes/scinote";
|
@import "themes/scinote";
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,20 @@ class MyModulesController < ApplicationController
|
||||||
include ActionView::Helpers::UrlHelper
|
include ActionView::Helpers::UrlHelper
|
||||||
include ApplicationHelper
|
include ApplicationHelper
|
||||||
|
|
||||||
before_action :load_vars
|
before_action :load_vars,
|
||||||
before_action :load_vars_nested, only: %I[new create]
|
only: %i(show update destroy description due_date protocols
|
||||||
before_action :load_repository, only: %I[assign_repository_records
|
results samples activities activities_tab
|
||||||
unassign_repository_records]
|
assign_samples unassign_samples delete_samples
|
||||||
before_action :check_manage_permissions, only:
|
toggle_task_state samples_index archive
|
||||||
%i(destroy description due_date)
|
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:
|
before_action :check_view_permissions, only:
|
||||||
%i(show activities activities_tab protocols results samples samples_index
|
%i(show activities activities_tab protocols results samples samples_index
|
||||||
archive)
|
archive)
|
||||||
|
|
@ -363,20 +371,18 @@ class MyModulesController < ApplicationController
|
||||||
|
|
||||||
# AJAX actions
|
# AJAX actions
|
||||||
def repository_index
|
def repository_index
|
||||||
@repository = Repository.find_by_id(params[:repository_id])
|
@draw = params[:draw].to_i
|
||||||
if @repository.nil? || !can_read_team?(@repository.team)
|
per_page = params[:length] == '-1' ? 100 : params[:length].to_i
|
||||||
render_403
|
page = (params[:start].to_i / per_page) + 1
|
||||||
else
|
records = RepositoryDatatableService.new(@repository,
|
||||||
respond_to do |format|
|
params,
|
||||||
format.html
|
current_user,
|
||||||
format.json do
|
@my_module)
|
||||||
render json: ::RepositoryDatatable.new(view_context,
|
@assigned_rows = records.assigned_rows
|
||||||
@repository,
|
@repository_row_count = records.repository_rows.count
|
||||||
@my_module,
|
@columns_mappings = records.mappings
|
||||||
current_user)
|
@repository_rows = records.repository_rows.page(page).per(per_page)
|
||||||
end
|
render 'repository_rows/index.json'
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Submit actions
|
# Submit actions
|
||||||
|
|
@ -595,7 +601,8 @@ class MyModulesController < ApplicationController
|
||||||
|
|
||||||
def load_repository
|
def load_repository
|
||||||
@repository = Repository.find_by_id(params[:repository_id])
|
@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
|
end
|
||||||
|
|
||||||
def check_manage_permissions
|
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_info_modal_vars, only: :show
|
||||||
before_action :load_vars, only: %i(edit update)
|
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_create_permissions, only: :create
|
||||||
before_action :check_manage_permissions, only: %i(edit update delete_records)
|
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
|
def create
|
||||||
record = RepositoryRow.new(repository: @repository,
|
record = RepositoryRow.new(repository: @repository,
|
||||||
created_by: current_user,
|
created_by: current_user,
|
||||||
|
|
@ -24,15 +37,31 @@ class RepositoryRowsController < ApplicationController
|
||||||
column = @repository.repository_columns.detect do |c|
|
column = @repository.repository_columns.detect do |c|
|
||||||
c.id == key.to_i
|
c.id == key.to_i
|
||||||
end
|
end
|
||||||
cell_value = RepositoryTextValue.new(
|
if column.data_type == 'RepositoryListValue'
|
||||||
data: value,
|
next if value == '-1'
|
||||||
created_by: current_user,
|
# check if item existx else revert the transaction
|
||||||
last_modified_by: current_user,
|
list_item = RepositoryListItem.where(repository_column: column)
|
||||||
repository_cell_attributes: {
|
.find(value)
|
||||||
repository_row: record,
|
cell_value = RepositoryListValue.new(
|
||||||
repository_column: column
|
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,
|
||||||
|
last_modified_by: current_user,
|
||||||
|
repository_cell_attributes: {
|
||||||
|
repository_row: record,
|
||||||
|
repository_column: column
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
if cell_value.save
|
if cell_value.save
|
||||||
record_annotation_notification(record, cell_value.repository_cell)
|
record_annotation_notification(record, cell_value.repository_cell)
|
||||||
else
|
else
|
||||||
|
|
@ -77,7 +106,8 @@ class RepositoryRowsController < ApplicationController
|
||||||
json = {
|
json = {
|
||||||
repository_row: {
|
repository_row: {
|
||||||
name: escape_input(@record.name),
|
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|
|
@record.repository_cells.each do |cell|
|
||||||
json[:repository_row][:repository_cells][cell.repository_column_id] = {
|
json[:repository_row][:repository_cells][cell.repository_column_id] = {
|
||||||
repository_cell_id: cell.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
|
end
|
||||||
|
|
||||||
|
|
@ -111,14 +143,27 @@ class RepositoryRowsController < ApplicationController
|
||||||
end
|
end
|
||||||
if existing
|
if existing
|
||||||
# Cell exists and new value present, so update value
|
# Cell exists and new value present, so update value
|
||||||
existing.value.data = value
|
if existing.value_type == 'RepositoryListValue'
|
||||||
if existing.value.save
|
item = RepositoryListItem.where(
|
||||||
record_annotation_notification(@record, existing)
|
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
|
else
|
||||||
errors[:repository_cells] << {
|
existing.value.data = value
|
||||||
"#{existing.repository_column_id}":
|
if existing.value.save
|
||||||
existing.value.errors.messages
|
record_annotation_notification(@record, existing)
|
||||||
}
|
else
|
||||||
|
errors[:repository_cells] << {
|
||||||
|
"#{existing.repository_column_id}":
|
||||||
|
existing.value.errors.messages
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
# Looks like it is a new cell, so we need to create new value, cell
|
# Looks like it is a new cell, so we need to create new value, cell
|
||||||
|
|
@ -126,15 +171,31 @@ class RepositoryRowsController < ApplicationController
|
||||||
column = @repository.repository_columns.detect do |c|
|
column = @repository.repository_columns.detect do |c|
|
||||||
c.id == key.to_i
|
c.id == key.to_i
|
||||||
end
|
end
|
||||||
cell_value = RepositoryTextValue.new(
|
if column.data_type == 'RepositoryListValue'
|
||||||
data: value,
|
next if value == '-1'
|
||||||
created_by: current_user,
|
# check if item existx else revert the transaction
|
||||||
last_modified_by: current_user,
|
list_item = RepositoryListItem.where(repository_column: column)
|
||||||
repository_cell_attributes: {
|
.find(value)
|
||||||
repository_row: @record,
|
cell_value = RepositoryListValue.new(
|
||||||
repository_column: column
|
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,
|
||||||
|
last_modified_by: current_user,
|
||||||
|
repository_cell_attributes: {
|
||||||
|
repository_row: @record,
|
||||||
|
repository_column: column
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
if cell_value.save
|
if cell_value.save
|
||||||
record_annotation_notification(@record,
|
record_annotation_notification(@record,
|
||||||
cell_value.repository_cell)
|
cell_value.repository_cell)
|
||||||
|
|
@ -147,6 +208,7 @@ class RepositoryRowsController < ApplicationController
|
||||||
end
|
end
|
||||||
# Clean up empty cells, not present in updated record
|
# Clean up empty cells, not present in updated record
|
||||||
@record.repository_cells.each do |cell|
|
@record.repository_cells.each do |cell|
|
||||||
|
next if cell.value_type == 'RepositoryListValue'
|
||||||
cell.value.destroy unless cell_params
|
cell.value.destroy unless cell_params
|
||||||
.key?(cell.repository_column_id.to_s)
|
.key?(cell.repository_column_id.to_s)
|
||||||
end
|
end
|
||||||
|
|
@ -237,6 +299,7 @@ class RepositoryRowsController < ApplicationController
|
||||||
def load_repository
|
def load_repository
|
||||||
@repository = Repository.find_by_id(params[:repository_id])
|
@repository = Repository.find_by_id(params[:repository_id])
|
||||||
render_404 unless @repository
|
render_404 unless @repository
|
||||||
|
render_403 unless can_read_team?(@repository.team)
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_create_permissions
|
def check_create_permissions
|
||||||
|
|
@ -276,4 +339,28 @@ class RepositoryRowsController < ApplicationController
|
||||||
column: link_to(cell.repository_column.name, table_url))
|
column: link_to(cell.repository_column.name, table_url))
|
||||||
)
|
)
|
||||||
end
|
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
|
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
|
|
||||||
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
|
after_create :update_repository_table_state
|
||||||
|
|
||||||
|
scope :list_type, -> { where(data_type: 'RepositoryListValue') }
|
||||||
|
|
||||||
def update_repository_table_state
|
def update_repository_table_state
|
||||||
RepositoryTableState.update_state(self, nil, created_by)
|
RepositoryTableState.update_state(self, nil, created_by)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class RepositoryTableState < ApplicationRecord
|
||||||
else
|
else
|
||||||
# add column
|
# add column
|
||||||
index = repository_state['columns'].count
|
index = repository_state['columns'].count
|
||||||
repository_state['columns'][index] = RepositoryDatatable::
|
repository_state['columns'][index] = Constants::
|
||||||
REPOSITORY_TABLE_DEFAULT_STATE['columns'].first
|
REPOSITORY_TABLE_DEFAULT_STATE['columns'].first
|
||||||
repository_state['ColReorder'].insert(2, index.to_s)
|
repository_state['ColReorder'].insert(2, index.to_s)
|
||||||
end
|
end
|
||||||
|
|
@ -52,12 +52,12 @@ class RepositoryTableState < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.create_state(user, repository)
|
def self.create_state(user, repository)
|
||||||
default_columns_num = RepositoryDatatable::
|
default_columns_num = Constants::
|
||||||
REPOSITORY_TABLE_DEFAULT_STATE['columns'].count
|
REPOSITORY_TABLE_DEFAULT_STATE['columns'].count
|
||||||
repository_state =
|
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.repository_columns.each_with_index do |_, index|
|
||||||
repository_state['columns'] << RepositoryDatatable::
|
repository_state['columns'] << Constants::
|
||||||
REPOSITORY_TABLE_DEFAULT_STATE['columns'].first
|
REPOSITORY_TABLE_DEFAULT_STATE['columns'].first
|
||||||
repository_state['ColReorder'] << (default_columns_num + index)
|
repository_state['ColReorder'] << (default_columns_num + index)
|
||||||
end
|
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
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
<th id="added-on"><%= t("repositories.table.added_on") %></th>
|
<th id="added-on"><%= t("repositories.table.added_on") %></th>
|
||||||
<th id="added-by"><%= t("repositories.table.added_by") %></th>
|
<th id="added-by"><%= t("repositories.table.added_by") %></th>
|
||||||
<% repository.repository_columns.order(:id).each do |column| %>
|
<% 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-editable' if can_manage_repository_column?(column) %>
|
||||||
<%= 'data-deletable' if can_manage_repository_column?(column) %>
|
<%= 'data-deletable' if can_manage_repository_column?(column) %>
|
||||||
<%= "data-edit-url='#{edit_repository_repository_column_path(repository, 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
|
||||||
|
|
@ -859,6 +859,31 @@ class Constants
|
||||||
]
|
]
|
||||||
}.freeze
|
}.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
|
EXPORTABLE_ZIP_EXPIRATION_DAYS = 7
|
||||||
|
|
||||||
# Very basic regex to check for validity of emails
|
# Very basic regex to check for validity of emails
|
||||||
|
|
|
||||||
|
|
@ -441,7 +441,8 @@ Rails.application.routes.draw do
|
||||||
post 'archive', to: 'protocols#archive'
|
post 'archive', to: 'protocols#archive'
|
||||||
post 'restore', to: 'protocols#restore'
|
post 'restore', to: 'protocols#restore'
|
||||||
post 'import', to: 'protocols#import'
|
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'
|
post 'protocolsio_import_save', to: 'protocols#protocolsio_import_save'
|
||||||
get 'export', to: 'protocols#export'
|
get 'export', to: 'protocols#export'
|
||||||
end
|
end
|
||||||
|
|
@ -449,7 +450,7 @@ Rails.application.routes.draw do
|
||||||
|
|
||||||
resources :repositories do
|
resources :repositories do
|
||||||
post 'repository_index',
|
post 'repository_index',
|
||||||
to: 'repositories#repository_table_index',
|
to: 'repository_rows#index',
|
||||||
as: 'table_index',
|
as: 'table_index',
|
||||||
defaults: { format: 'json' }
|
defaults: { format: 'json' }
|
||||||
# Save repository table state
|
# Save repository table state
|
||||||
|
|
@ -472,7 +473,6 @@ Rails.application.routes.draw do
|
||||||
as: 'columns_destroy_html'
|
as: 'columns_destroy_html'
|
||||||
|
|
||||||
resources :repository_columns, only: %i(create edit update destroy)
|
resources :repository_columns, only: %i(create edit update destroy)
|
||||||
|
|
||||||
resources :repository_rows, only: %i(create edit update)
|
resources :repository_rows, only: %i(create edit update)
|
||||||
member do
|
member do
|
||||||
post 'parse_sheet'
|
post 'parse_sheet'
|
||||||
|
|
@ -480,6 +480,9 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
post 'repository_list_items', to: 'repository_list_items#search',
|
||||||
|
defaults: { format: 'json' }
|
||||||
|
|
||||||
get 'repository_rows/:id', to: 'repository_rows#show',
|
get 'repository_rows/:id', to: 'repository_rows#show',
|
||||||
as: :repository_row,
|
as: :repository_row,
|
||||||
defaults: { format: 'json' }
|
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!(:team) { create :team, created_by: user }
|
||||||
let!(:user_team) { create :user_team, team: team, user: user }
|
let!(:user_team) { create :user_team, team: team, user: user }
|
||||||
let!(:repository) { create :repository, team: team, created_by: 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
|
let!(:repository_row) do
|
||||||
create :repository_row, repository: repository,
|
create :repository_row, repository: repository,
|
||||||
created_by: user,
|
created_by: user,
|
||||||
|
|
@ -41,4 +48,71 @@ describe RepositoryRowsController, type: :controller do
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ describe AssetTextDatum, type: :model do
|
||||||
it 'should have uniq asset' do
|
it 'should have uniq asset' do
|
||||||
create :asset_text_datum, asset: asset
|
create :asset_text_datum, asset: asset
|
||||||
new_atd = build :asset_text_datum, asset: asset
|
new_atd = build :asset_text_datum, asset: asset
|
||||||
# binding.pry
|
|
||||||
expect(new_atd).to_not be_valid
|
expect(new_atd).to_not be_valid
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -98,11 +98,11 @@ RSpec.describe RepositoryListValue, type: :model do
|
||||||
|
|
||||||
it 'retuns only the the item related to the list' do
|
it 'retuns only the the item related to the list' do
|
||||||
repository_row_two = create :repository_row, name: 'New row'
|
repository_row_two = create :repository_row, name: 'New row'
|
||||||
repository_list_value_two =
|
create :repository_list_value,
|
||||||
create :repository_list_value, repository_cell_attributes: {
|
repository_cell_attributes: {
|
||||||
repository_column: repository_column,
|
repository_column: repository_column,
|
||||||
repository_row: repository_row_two
|
repository_row: repository_row_two
|
||||||
}
|
}
|
||||||
list_item = create :repository_list_item,
|
list_item = create :repository_list_item,
|
||||||
data: 'new item',
|
data: 'new item',
|
||||||
repository: repository,
|
repository: repository,
|
||||||
|
|
@ -113,16 +113,15 @@ RSpec.describe RepositoryListValue, type: :model do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns an empty string if no item selected' do
|
it 'returns an empty string if no item selected' do
|
||||||
list_item = create :repository_list_item,
|
create :repository_list_item, data: 'my item',
|
||||||
data: 'my item',
|
repository: repository,
|
||||||
repository: repository,
|
repository_column: repository_column
|
||||||
repository_column: repository_column
|
expect(repository_list_value.reload.data).to be_nil
|
||||||
expect(repository_list_value.reload.data).to eq nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns an empty string if item does not exists' do
|
it 'returns an empty string if item does not exists' do
|
||||||
repository_list_value.repository_list_item = nil
|
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
|
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…
Add table
Reference in a new issue