mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 14:45:56 +08:00
Add custom repository table [SCI-1274]
This commit is contained in:
parent
2ebcdedf57
commit
8b1190060e
1267
app/assets/javascripts/repositories/repository_datatable.js
Normal file
1267
app/assets/javascripts/repositories/repository_datatable.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -3,3 +3,12 @@
|
|||
max-height: 400px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.repository-table {
|
||||
margin-top: 20px;
|
||||
|
||||
// Datatables generated name
|
||||
.dataTables_length {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,11 @@ class MyModulesController < ApplicationController
|
|||
results samples activities activities_tab
|
||||
assign_samples unassign_samples delete_samples
|
||||
toggle_task_state samples_index archive
|
||||
complete_my_module repository]
|
||||
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]
|
||||
before_action :check_edit_permissions,
|
||||
only: %I[update description due_date]
|
||||
before_action :check_destroy_permissions, only: :destroy
|
||||
|
@ -363,6 +366,123 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# AJAX actions
|
||||
def repository_index
|
||||
@repository = Repository.find_by_id(params[:repository_id])
|
||||
if @repository.nil? || !can_view_repository(@repository)
|
||||
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
|
||||
end
|
||||
|
||||
# Submit actions
|
||||
def assign_repository_records
|
||||
render_403 && return unless can_assign_repository_records(@my_module,
|
||||
@repository)
|
||||
if params[:selected_rows].present? && params[:repository_id].present?
|
||||
records_names = []
|
||||
|
||||
params[:selected_rows].each do |id|
|
||||
record = RepositoryRow.find_by_id(id)
|
||||
next if !record || @my_module.repository_rows.include?(record)
|
||||
record.last_modified_by = current_user
|
||||
record.save
|
||||
records_names << record.name
|
||||
MyModulesRepositoryRow.create!(
|
||||
my_module: @my_module,
|
||||
repository_row: record,
|
||||
assigned_by: current_user
|
||||
)
|
||||
end
|
||||
|
||||
if records_names.any?
|
||||
Activity.create(
|
||||
type_of: :assign_repository_record,
|
||||
project: @project,
|
||||
experiment: @experiment,
|
||||
my_module: @my_module,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.assign_repository_records',
|
||||
user: current_user.full_name,
|
||||
task: @my_module.name,
|
||||
repository: @repository,
|
||||
records: records_names.join(', ')
|
||||
)
|
||||
)
|
||||
flash = I18n.t('repositories.assigned_records_flash',
|
||||
records: records_names.join(', '))
|
||||
respond_to do |format|
|
||||
format.json { render json: { flash: flash }, status: :ok }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
flash: t('repositories.no_records_assigned_flash')
|
||||
}, status: :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unassign_repository_records
|
||||
render_403 && return unless can_unassign_repository_records(@my_module,
|
||||
@repository)
|
||||
if params[:selected_rows].present? && params[:repository_id].present?
|
||||
records = []
|
||||
|
||||
params[:selected_rows].each do |id|
|
||||
record = RepositoryRow.find_by_id(id)
|
||||
next unless record && @my_module.repository_rows.include?(record)
|
||||
record.last_modified_by = current_user
|
||||
record.save
|
||||
records << record
|
||||
end
|
||||
|
||||
@my_module.repository_rows.destroy(records & @my_module.repository_rows)
|
||||
if records.any?
|
||||
Activity.create(
|
||||
type_of: :unassign_repository_record,
|
||||
project: @project,
|
||||
experiment: @experiment,
|
||||
my_module: @my_module,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.unassign_repository_records',
|
||||
user: current_user.full_name,
|
||||
task: @my_module.name,
|
||||
repository: @repository,
|
||||
records: records.map(&:name).join(', ')
|
||||
)
|
||||
)
|
||||
flash = I18n.t('repositories.unassigned_records_flash',
|
||||
records: records.map(&:name).join(', '))
|
||||
respond_to do |format|
|
||||
format.json { render json: { flash: flash }, status: :ok }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
flash: t('repositories.no_records_unassigned_flash')
|
||||
}, status: :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Complete/uncomplete task
|
||||
def toggle_task_state
|
||||
respond_to do |format|
|
||||
|
@ -456,6 +576,11 @@ class MyModulesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def load_repository
|
||||
@repository = Repository.find_by_id(params[:repository_id])
|
||||
render_404 unless @repository && can_view_repository(@repository)
|
||||
end
|
||||
|
||||
def check_edit_permissions
|
||||
unless can_edit_module(@my_module)
|
||||
render_403
|
||||
|
|
|
@ -99,6 +99,24 @@ class RepositoriesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# AJAX actions
|
||||
def repository_table_index
|
||||
@repository = Repository.find_by_id(params[:repository_id])
|
||||
if @repository.nil? || !can_view_repository(@repository)
|
||||
render_403
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render json: ::RepositoryDatatable.new(view_context,
|
||||
@repository,
|
||||
nil,
|
||||
current_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_vars
|
||||
|
|
126
app/controllers/repository_columns_controller.rb
Normal file
126
app/controllers/repository_columns_controller.rb
Normal file
|
@ -0,0 +1,126 @@
|
|||
class RepositoryColumnsController < ApplicationController
|
||||
include InputSanitizeHelper
|
||||
|
||||
before_action :load_vars, except: :create
|
||||
before_action :load_vars_nested, only: :create
|
||||
before_action :check_create_permissions, only: :create
|
||||
before_action :check_update_permissions, only: :update
|
||||
before_action :check_destroy_permissions, only: %i(destroy destroy_html)
|
||||
|
||||
def create
|
||||
@repository_column = RepositoryColumn.new(repository_column_params)
|
||||
@repository_column.repository = @repository
|
||||
@repository_column.created_by = current_user
|
||||
@repository_column.data_type = :RepositoryTextValue
|
||||
|
||||
respond_to do |format|
|
||||
if @repository_column.save
|
||||
format.json do
|
||||
render json: {
|
||||
id: @repository_column.id,
|
||||
name: escape_input(@repository_column.name),
|
||||
edit_url:
|
||||
edit_repository_repository_column_path(@repository,
|
||||
@repository_column),
|
||||
update_url:
|
||||
repository_repository_column_path(@repository,
|
||||
@repository_column),
|
||||
destroy_html_url:
|
||||
repository_columns_destroy_html_path(
|
||||
@repository, @repository_column
|
||||
)
|
||||
},
|
||||
status: :ok
|
||||
end
|
||||
else
|
||||
format.json do
|
||||
render json: @repository_column.errors.to_json,
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { status: :ok }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
@repository_column.update_attributes(repository_column_params)
|
||||
if @repository_column.save
|
||||
render json: { status: :ok }
|
||||
else
|
||||
render json: @repository_column.errors.to_json,
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_html
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'repositories/delete_column_modal_body.html.erb',
|
||||
locals: { column_index: params[:column_index] }
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@del_repository_column = @repository_column.dup
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
if @repository_column.destroy
|
||||
RepositoryTable.update_state(
|
||||
@del_repository_column,
|
||||
params[:repository_column][:column_index],
|
||||
current_user
|
||||
)
|
||||
render json: { status: :ok }
|
||||
else
|
||||
render json: { status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_vars
|
||||
@repository = Repository.find_by_id(params[:repository_id])
|
||||
render_404 unless @repository
|
||||
@repository_column = RepositoryColumn.find_by_id(params[:id])
|
||||
render_404 unless @repository_column
|
||||
end
|
||||
|
||||
def load_vars_nested
|
||||
@repository = Repository.find_by_id(params[:repository_id])
|
||||
render_404 unless @repository
|
||||
end
|
||||
|
||||
def check_create_permissions
|
||||
render_403 unless can_create_columns_in_repository(@repository)
|
||||
end
|
||||
|
||||
def check_update_permissions
|
||||
render_403 unless can_edit_columns_in_repository(@repository)
|
||||
end
|
||||
|
||||
def check_destroy_permissions
|
||||
render_403 unless can_delete_columns_in_repository(@repository)
|
||||
end
|
||||
|
||||
def repository_column_params
|
||||
params.require(:repository_column).permit(:name)
|
||||
end
|
||||
end
|
248
app/controllers/repository_rows_controller.rb
Normal file
248
app/controllers/repository_rows_controller.rb
Normal file
|
@ -0,0 +1,248 @@
|
|||
class RepositoryRowsController < ApplicationController
|
||||
include InputSanitizeHelper
|
||||
include ActionView::Helpers::TextHelper
|
||||
include ApplicationHelper
|
||||
|
||||
before_action :load_vars, only: %i(edit update)
|
||||
before_action :load_repository, only: %i(create delete_records)
|
||||
|
||||
before_action :check_create_permissions, only: :create
|
||||
before_action :check_edit_permissions, only: %i(edit update)
|
||||
before_action :check_destroy_permissions, only: :delete_records
|
||||
|
||||
def create
|
||||
record = RepositoryRow.new(name: record_params[:name],
|
||||
repository: @repository,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user)
|
||||
errors = { default_fields: [],
|
||||
custom_cells: [] }
|
||||
|
||||
record.transaction do
|
||||
unless record.save
|
||||
errors[:default_fields] = record.errors.messages
|
||||
raise ActiveRecord::RecordInvalid
|
||||
end
|
||||
if params[:repository_cells]
|
||||
params[:repository_cells].each do |key, value|
|
||||
column = @repository.repository_columns.detect do |c|
|
||||
c.id == key.to_i
|
||||
end
|
||||
cell_value = RepositoryTextValue.new(
|
||||
data: value,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
repository_cell_attributes: {
|
||||
repository_row: record,
|
||||
repository_column: column
|
||||
}
|
||||
)
|
||||
unless cell_value.save
|
||||
errors[:custom_cells] << {
|
||||
"#{cell.repository_column.id}": cell_value.errors.messages
|
||||
}
|
||||
raise ActiveRecord::RecordInvalid
|
||||
end
|
||||
record_annotation_notification(record, cell_value.repository_cell)
|
||||
end
|
||||
end
|
||||
end
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { id: record.id,
|
||||
flash: t('repositories.create.success_flash',
|
||||
record: escape_input(record.name),
|
||||
repository: escape_input(@repository.name)) },
|
||||
status: :ok
|
||||
end
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
respond_to do |format|
|
||||
format.json { render json: errors, status: :bad_request }
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
json = {
|
||||
repository_row: {
|
||||
name: escape_input(@record.name),
|
||||
repository_cells: {}
|
||||
}
|
||||
}
|
||||
|
||||
# Add custom cells ids as key (easier lookup on js side)
|
||||
@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)
|
||||
}
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json { render json: json }
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
errors = {
|
||||
default_fields: [],
|
||||
custom_cells: []
|
||||
}
|
||||
|
||||
@record.transaction do
|
||||
@record.name = record_params[:name]
|
||||
unless @record.save
|
||||
errors[:default_fields] = sample.errors.messages
|
||||
raise ActiveRecord::RecordInvalid
|
||||
end
|
||||
if params[:repository_cells]
|
||||
params[:repository_cells].each do |key, value|
|
||||
existing = @record.repository_cells.detect do |c|
|
||||
c.repository_column_id == key.to_i
|
||||
end
|
||||
if existing
|
||||
# Cell exists and new value present, so update value
|
||||
existing.value.data = value
|
||||
unless existing.value.save
|
||||
errors[:custom_cells] << {
|
||||
"#{cell.repository_column_id}": existing.value.errors.messages
|
||||
}
|
||||
raise ActiveRecord::RecordInvalid
|
||||
end
|
||||
record_annotation_notification(@record, existing)
|
||||
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
|
||||
value = RepositoryTextValue.new(
|
||||
data: value,
|
||||
created_by: current_user,
|
||||
last_modified_by: current_user,
|
||||
repository_cell_attributes: {
|
||||
repository_row: @record,
|
||||
repository_column: column
|
||||
}
|
||||
)
|
||||
unless value.save
|
||||
errors[:custom_cells] << {
|
||||
"#{cell.repository_column_id}": value.errors.messages
|
||||
}
|
||||
raise ActiveRecord::RecordInvalid
|
||||
end
|
||||
record_annotation_notification(@record, value.repository_cell)
|
||||
end
|
||||
end
|
||||
# Clean up empty cells, not present in updated record
|
||||
@record.repository_cells.each do |cell|
|
||||
cell.value.destroy unless params[:repository_cells]
|
||||
.key?(cell.repository_column_id.to_s)
|
||||
end
|
||||
else
|
||||
@record.repository_cells.each { |c| c.value.destroy }
|
||||
end
|
||||
end
|
||||
|
||||
# Row sucessfully updated, so sending response to client
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
id: @record.id,
|
||||
flash: t(
|
||||
'repositories.update.success_flash',
|
||||
record: escape_input(@record.name),
|
||||
repository: escape_input(@repository.name)
|
||||
)
|
||||
},
|
||||
status: :ok
|
||||
end
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
respond_to do |format|
|
||||
format.json { render json: errors, status: :bad_request }
|
||||
end
|
||||
end
|
||||
|
||||
def delete_records
|
||||
deleted_count = 0
|
||||
if params[:selected_rows]
|
||||
params[:selected_rows].each do |row_id|
|
||||
row = @repository.repository_rows.find_by_id(row_id)
|
||||
if row && can_delete_repository_record(row)
|
||||
row.destroy && deleted_count += 1
|
||||
end
|
||||
end
|
||||
if deleted_count.zero?
|
||||
flash = t('repositories.destroy.no_deleted_records_flash')
|
||||
elsif deleted_count != params[:selected_rows].count
|
||||
not_deleted_count = params[:selected_rows].count - deleted_count
|
||||
flash = t('repositories.destroy.contains_other_records_flash',
|
||||
records_number: deleted_count,
|
||||
other_records_number: not_deleted_count)
|
||||
else
|
||||
flash = t('repositories.destroy.success_flash',
|
||||
records_number: deleted_count)
|
||||
end
|
||||
respond_to do |format|
|
||||
format.json { render json: { flash: flash }, status: :ok }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
flash: t('repositories.destroy.no_records_selected_flash')
|
||||
}, status: :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_vars
|
||||
@repository = Repository.eager_load(:repository_columns)
|
||||
.find_by_id(params[:repository_id])
|
||||
@record = RepositoryRow.eager_load(:repository_columns)
|
||||
.find_by_id(params[:id])
|
||||
render_404 unless @repository && @record
|
||||
end
|
||||
|
||||
def load_repository
|
||||
@repository = Repository.find_by_id(params[:repository_id])
|
||||
render_404 unless @repository
|
||||
end
|
||||
|
||||
def check_create_permissions
|
||||
render_403 unless can_create_repository_records(@repository)
|
||||
end
|
||||
|
||||
def check_edit_permissions
|
||||
render_403 unless can_edit_repository_records(@repository)
|
||||
end
|
||||
|
||||
def check_destroy_permissions
|
||||
render_403 unless can_delete_repository_records(@repository)
|
||||
end
|
||||
|
||||
def record_params
|
||||
params.require(:repository_row).permit(:name)
|
||||
end
|
||||
|
||||
def record_annotation_notification(record, cell, old_text = nil)
|
||||
table_url = params.fetch(:request_url) { :request_url_must_be_present }
|
||||
smart_annotation_notification(
|
||||
old_text: (old_text if old_text),
|
||||
new_text: cell.value.data,
|
||||
title: t('notifications.repository_annotation_title',
|
||||
user: current_user.full_name,
|
||||
column: cell.repository_column.name,
|
||||
record: record.name,
|
||||
repository: record.repository),
|
||||
message: t('notifications.repository_annotation_message_html',
|
||||
record: link_to(record.name, table_url),
|
||||
column: link_to(cell.repository_column.name, table_url))
|
||||
)
|
||||
end
|
||||
end
|
48
app/controllers/user_repositories_controller.rb
Normal file
48
app/controllers/user_repositories_controller.rb
Normal file
|
@ -0,0 +1,48 @@
|
|||
class UserRepositoriesController < ApplicationController
|
||||
before_action :load_vars
|
||||
|
||||
def save_table_state
|
||||
repository_table = RepositoryTable.where(user: current_user,
|
||||
repository: @repository).first
|
||||
if repository_table
|
||||
repository_table.update(state: params[:state])
|
||||
else
|
||||
RepositoryTable.create(user: current_user,
|
||||
repository: @repository,
|
||||
state: params[:state])
|
||||
end
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
status: :ok
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_table_state
|
||||
table_state = RepositoryTable.load_state(current_user,
|
||||
@repository).first
|
||||
if table_state.nil?
|
||||
RepositoryTable.create_state(current_user, @repository)
|
||||
table_state = RepositoryTable.load_state(current_user,
|
||||
@repository).first
|
||||
end
|
||||
respond_to do |format|
|
||||
if table_state
|
||||
format.json do
|
||||
render json: {
|
||||
state: table_state
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_vars
|
||||
@repository = Repository.find_by_id(params[:repository_id])
|
||||
render_403 if @repository.nil? || !can_view_repository(@repository)
|
||||
end
|
||||
end
|
346
app/datatables/repository_datatable.rb
Normal file
346
app/datatables/repository_datatable.rb
Normal file
|
@ -0,0 +1,346 @@
|
|||
require 'active_record'
|
||||
|
||||
class RepositoryDatatable < AjaxDatatablesRails::Base
|
||||
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' => [],
|
||||
'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 params[:columns].to_a[param_index].nil?
|
||||
params_col =
|
||||
params[:columns].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': @my_module ? 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)
|
||||
}
|
||||
|
||||
# Add custom columns
|
||||
record.repository_cells.each do |cell|
|
||||
row[@columns_mappings[cell.repository_column.id]] =
|
||||
custom_auto_link(cell.value.data,
|
||||
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
|
||||
.includes(
|
||||
:repository_columns,
|
||||
:created_by
|
||||
# repository_cells: :value
|
||||
).references(
|
||||
:repository_columns,
|
||||
:created_by
|
||||
)
|
||||
.where(repository: @repository)
|
||||
if @my_module
|
||||
@assigned_rows = @my_module.repository_rows
|
||||
|
||||
# repository_rows.joins(
|
||||
# "LEFT OUTER JOIN my_modules_repository_rows ON
|
||||
# (repository_row.id = my_modules_repository_rows.repository_row_id AND
|
||||
# (my_modules_repository_rows.my_module_id = #{@my_module.id} OR
|
||||
# my_modules_repository_rows.id IS NULL))"
|
||||
# )
|
||||
end
|
||||
|
||||
# Make mappings of custom columns, so we have same id for every column
|
||||
i = 5
|
||||
@columns_mappings = {}
|
||||
@repository.repository_columns.each do |column|
|
||||
@columns_mappings[column.id] = i.to_s
|
||||
i += 1
|
||||
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 = sort_records(records) if params[:order].present?
|
||||
escape_special_chars
|
||||
records = filter_records(records) if params[:search].present? &&
|
||||
!sorting_by_custom_column
|
||||
records = paginate_records(records) if !(params[:length].present? &&
|
||||
params[:length] == '-1') &&
|
||||
!sorting_by_custom_column
|
||||
records
|
||||
end
|
||||
|
||||
# Override default sort method if needed
|
||||
def sort_records(records)
|
||||
if params[:order].present? && params[:order].length == 1
|
||||
if sort_column(params[:order].values[0]) == ASSIGNED_SORT_COL
|
||||
# If "assigned" column is sorted
|
||||
if @my_module
|
||||
# Depending on the sort, order nulls first or
|
||||
# nulls last on repository_cells association
|
||||
direction = sort_null_direction(params[:order].values[0])
|
||||
records.joins(
|
||||
"LEFT OUTER JOIN my_modules_repository_rows ON
|
||||
(repository_rows.id = my_modules_repository_rows.repository_row_id
|
||||
AND (my_modules_repository_rows.my_module_id = #{@my_module.id} OR
|
||||
my_modules_repository_rows.id IS NULL))"
|
||||
).order("my_modules_repository_rows.id NULLS #{direction}")
|
||||
end
|
||||
elsif sorting_by_custom_column
|
||||
# Check if have to filter records first
|
||||
# if params[:search].present? && params[:search][:value].present?
|
||||
# # Couldn't force ActiveRecord to yield the same query as below because
|
||||
# # Rails apparently forgets to join stuff in subqueries -
|
||||
# # #justrailsthings
|
||||
# conditions = build_conditions_for(params[:search][:value])
|
||||
#
|
||||
# filter_query = %(SELECT "samples"."id" FROM "samples"
|
||||
# LEFT OUTER JOIN "sample_custom_fields" ON
|
||||
# "sample_custom_fields"."sample_id" = "samples"."id"
|
||||
# LEFT OUTER JOIN "users" ON "users"."id" = "repository_row"."user_id"
|
||||
# WHERE "samples"."team_id" = #{@team.id} AND #{conditions.to_sql})
|
||||
#
|
||||
# records = records.where("samples.id IN (#{filter_query})")
|
||||
# end
|
||||
|
||||
ci = sortable_displayed_columns[
|
||||
params[:order].values[0][:column].to_i - 1
|
||||
]
|
||||
column_id = @columns_mappings.key((ci.to_i + 1).to_s)
|
||||
dir = sort_direction(params[:order].values[0])
|
||||
|
||||
# Because repository records can have multiple custom cells,
|
||||
# we first group them by samples.id and inside that group we sort them by column_id. Because
|
||||
# we sort them ASC, sorted columns will be on top. Distinct then only
|
||||
# takes the first row and cuts the rest of every group and voila we have
|
||||
# 1 row for every sample, which are not sorted yet ...
|
||||
# records = records.select('DISTINCT ON (repository_rows.id) *')
|
||||
# .order("repository_rows.id, CASE WHEN repository_cells.repository_column_id = #{column_id} THEN 1 ELSE 2 END ASC")
|
||||
|
||||
# ... this little gem (pun intended) then takes the records query, sorts it again
|
||||
# and paginates it. sq.t0_* are determined empirically and are crucial -
|
||||
# imagine A -> B -> C transitive relation but where A and C are the
|
||||
# same. Useless right? But not when you acknowledge that find_by_sql
|
||||
# method does some funky stuff when your query spans multiple queries -
|
||||
# Sample object might have id from SampleType, name from
|
||||
# User ... chaos ensues basically. If something changes in db this might
|
||||
# change.
|
||||
# formated_date = (I18n.t 'time.formats.datatables_date').gsub!(/^\"|\"?$/, '')
|
||||
# Sample.find_by_sql("SELECT sq.t0_r0 as id, sq.t0_r1 as name, to_char( sq.t0_r4, '#{ formated_date }' ) as created_at, sq.t0_r5, s, sq.t0_r2 as user_id, sq.custom_field_id FROM (#{records.to_sql})
|
||||
# as sq ORDER BY CASE WHEN sq.custom_field_id = #{column_id} THEN 1 ELSE 2 END #{dir}, sq.value #{dir}
|
||||
# LIMIT #{per_page} OFFSET #{offset}")
|
||||
|
||||
RepositoryRow.find_by_sql(
|
||||
"SELECT repository_rows.*, values.value AS value
|
||||
FROM repository_rows
|
||||
LEFT OUTER JOIN (SELECT repository_cells.*,
|
||||
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
|
||||
WHERE repository_rows.repository_id = #{@repository.id}
|
||||
ORDER BY value #{dir} LIMIT #{per_page} OFFSET #{offset}"
|
||||
)
|
||||
else
|
||||
super(records)
|
||||
end
|
||||
else
|
||||
super(records)
|
||||
end
|
||||
end
|
||||
|
||||
# A hack that overrides the new_search_contition method default behavior of the ajax-datatables-rails gem
|
||||
# now the method checks if the column is the created_at and generate a custom SQL to parse
|
||||
# it back to the caller method
|
||||
# def new_search_condition(column, value)
|
||||
# model, column = column.split('.')
|
||||
# model = model.constantize
|
||||
# formated_date = (I18n.t 'time.formats.datatables_date').gsub!(/^\"|\"?$/, '')
|
||||
# if model == SampleCustomField
|
||||
# # Find visible (searchable) custom field IDs, and only perform filter
|
||||
# # on those custom fields
|
||||
# searchable_cfs = params[:columns].select do |_, v|
|
||||
# v['searchable'] == 'true' && @cf_mappings.values.include?(v['data'])
|
||||
# end
|
||||
# cfmi = @cf_mappings.invert
|
||||
# cf_ids = searchable_cfs.map { |_, v| cfmi[v['data']] }
|
||||
#
|
||||
# # Do an ILIKE on 'value', as well as make sure to only include
|
||||
# # custom fields that have 'custom_field_id' among visible custom fields
|
||||
# casted_column = ::Arel::Nodes::NamedFunction.new(
|
||||
# 'CAST',
|
||||
# [model.arel_table[column.to_sym].as(typecast)]
|
||||
# )
|
||||
# casted_column = casted_column.matches("%#{value}%")
|
||||
# casted_column = casted_column.and(
|
||||
# model.arel_table['custom_field_id'].in(cf_ids)
|
||||
# )
|
||||
# casted_column
|
||||
# elsif column == 'created_at'
|
||||
# casted_column = ::Arel::Nodes::NamedFunction.new('CAST',
|
||||
# [ Arel.sql("to_char( samples.created_at, '#{ formated_date }' ) AS VARCHAR") ] )
|
||||
# casted_column.matches("%#{sanitize_sql_like(value)}%")
|
||||
# else
|
||||
# casted_column = ::Arel::Nodes::NamedFunction.new('CAST',
|
||||
# [model.arel_table[column.to_sym].as(typecast)])
|
||||
# casted_column.matches("%#{sanitize_sql_like(value)}%")
|
||||
# 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(params[:order].values[0]) == 'repository_cells.value'
|
||||
end
|
||||
|
||||
# Escapes special characters in search query
|
||||
def escape_special_chars
|
||||
if params[:search].present?
|
||||
params[:search][:value] = ActiveRecord::Base
|
||||
.__send__(:sanitize_sql_like,
|
||||
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
|
||||
col = [model.constantize.table_name, column].join('.')
|
||||
end
|
||||
|
||||
def generate_sortable_displayed_columns
|
||||
sort_order = RepositoryTable.where(user: @user, repository: @repository)
|
||||
.pluck(:state)
|
||||
.first['ColReorder']
|
||||
|
||||
sort_order.shift
|
||||
sort_order.map! { |i| (i.to_i - 1).to_s }
|
||||
|
||||
@sortable_displayed_columns = sort_order
|
||||
end
|
||||
end
|
|
@ -1069,4 +1069,44 @@ module PermissionHelper
|
|||
def can_edit_and_destroy_repository(repository)
|
||||
is_admin_of_team(repository.team)
|
||||
end
|
||||
|
||||
def can_create_columns_in_repository(repository)
|
||||
is_normal_user_or_admin_of_team(repository.team)
|
||||
end
|
||||
|
||||
def can_delete_columns_in_repository(repository)
|
||||
is_normal_user_or_admin_of_team(repository.team)
|
||||
end
|
||||
|
||||
def can_edit_columns_in_repository(repository)
|
||||
is_normal_user_or_admin_of_team(repository.team)
|
||||
end
|
||||
|
||||
def can_create_repository_records(repository)
|
||||
is_normal_user_or_admin_of_team(repository.team)
|
||||
end
|
||||
|
||||
def can_edit_repository_records(repository)
|
||||
is_normal_user_or_admin_of_team(repository.team)
|
||||
end
|
||||
|
||||
def can_delete_repository_records(repository)
|
||||
is_normal_user_or_admin_of_team(repository.team)
|
||||
end
|
||||
|
||||
def can_delete_repository_record(record)
|
||||
team = record.repository.team
|
||||
is_admin_of_team(team) || (is_normal_user_of_team(team) &&
|
||||
record.created_by == current_user)
|
||||
end
|
||||
|
||||
def can_assign_repository_records(my_module, repository)
|
||||
can_edit_repository_records(repository) &&
|
||||
is_technician_or_higher_of_project(my_module.experiment.project)
|
||||
end
|
||||
|
||||
def can_unassign_repository_records(my_module, repository)
|
||||
can_edit_repository_records(repository) &&
|
||||
is_technician_or_higher_of_project(my_module.experiment.project)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -58,7 +58,9 @@ class Activity < ActiveRecord::Base
|
|||
:assign_sample,
|
||||
:unassign_sample,
|
||||
:complete_task,
|
||||
:uncomplete_task
|
||||
:uncomplete_task,
|
||||
:assign_repository_record,
|
||||
:unassign_repository_record
|
||||
]
|
||||
|
||||
validates :type_of, presence: true
|
||||
|
|
|
@ -30,6 +30,9 @@ class MyModule < ActiveRecord::Base
|
|||
has_many :my_module_antecessors, through: :inputs, source: :from, class_name: 'MyModule'
|
||||
has_many :sample_my_modules, inverse_of: :my_module, :dependent => :destroy
|
||||
has_many :samples, through: :sample_my_modules
|
||||
has_many :my_modules_repository_rows,
|
||||
inverse_of: :my_module, dependent: :destroy
|
||||
has_many :repository_rows, through: :my_modules_repository_rows
|
||||
has_many :user_my_modules, inverse_of: :my_module, :dependent => :destroy
|
||||
has_many :users, through: :user_my_modules
|
||||
has_many :activities, inverse_of: :my_module
|
||||
|
|
8
app/models/my_modules_repository_row.rb
Normal file
8
app/models/my_modules_repository_row.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
class MyModulesRepositoryRow < ActiveRecord::Base
|
||||
belongs_to :assigned_by, foreign_key: 'assigned_by_id', class_name: 'User'
|
||||
belongs_to :repository_row
|
||||
belongs_to :my_module
|
||||
|
||||
validates :repository_row, :my_module, presence: true
|
||||
validates :repository_row, uniqueness: { scope: :my_module }
|
||||
end
|
|
@ -3,6 +3,7 @@ class Repository < ActiveRecord::Base
|
|||
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
|
||||
has_many :repository_columns
|
||||
has_many :repository_rows
|
||||
has_many :repository_tables, inverse_of: :repository, dependent: :destroy
|
||||
|
||||
auto_strip_attributes :name, nullify: false
|
||||
validates :name,
|
||||
|
|
|
@ -3,6 +3,7 @@ class RepositoryCell < ActiveRecord::Base
|
|||
belongs_to :repository_column
|
||||
belongs_to :value, polymorphic: true, dependent: :destroy
|
||||
|
||||
validates :repository_column, presence: true
|
||||
validate :repository_column_data_type
|
||||
validates :repository_row, uniqueness: { scope: :repository_column }
|
||||
|
||||
|
|
|
@ -14,4 +14,10 @@ class RepositoryColumn < ActiveRecord::Base
|
|||
validates :created_by, presence: true
|
||||
validates :repository, presence: true
|
||||
validates :data_type, presence: true
|
||||
|
||||
after_create :update_repository_table_state
|
||||
|
||||
def update_repository_table_state
|
||||
RepositoryTable.update_state(self, nil, created_by)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
class RepositoryDateValue < ActiveRecord::Base
|
||||
has_one :repository_cell, as: :value
|
||||
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
|
||||
belongs_to :last_modified_by, foreign_key: :last_modified_by_id,
|
||||
class_name: 'User'
|
||||
has_one :repository_cell, as: :value, dependent: :destroy
|
||||
accepts_nested_attributes_for :repository_cell
|
||||
|
||||
validates :repository_cell, presence: true
|
||||
validates :data,
|
||||
presence: true
|
||||
|
||||
def formatted
|
||||
data
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
class RepositoryRow < ActiveRecord::Base
|
||||
belongs_to :repository
|
||||
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
|
||||
belongs_to :last_modified_by, foreign_key: :last_modified_by_id,
|
||||
class_name: 'User'
|
||||
has_many :repository_cells, dependent: :destroy
|
||||
has_many :repository_columns, through: :repository_cells
|
||||
has_many :my_modules_repository_rows,
|
||||
inverse_of: :repository_row, dependent: :destroy
|
||||
has_many :my_modules, through: :my_modules_repository_rows
|
||||
|
||||
auto_strip_attributes :name, nullify: false
|
||||
validates :name,
|
||||
|
|
60
app/models/repository_table.rb
Normal file
60
app/models/repository_table.rb
Normal file
|
@ -0,0 +1,60 @@
|
|||
class RepositoryTable < ActiveRecord::Base
|
||||
belongs_to :user, inverse_of: :repository_tables
|
||||
belongs_to :repository, inverse_of: :repository_tables
|
||||
|
||||
validates :user, :repository, presence: true
|
||||
|
||||
scope :load_state, (lambda { |user, repository|
|
||||
where(user: user, repository: repository).pluck(:state)
|
||||
})
|
||||
|
||||
def self.update_state(custom_column, column_index, user)
|
||||
repository_table = RepositoryTable.where(
|
||||
user: user,
|
||||
repository: custom_column.repository
|
||||
)
|
||||
repository_state = repository_table.first['state']
|
||||
if column_index
|
||||
# delete column
|
||||
repository_state['columns'].delete(column_index)
|
||||
repository_state['columns'].keys.each do |index|
|
||||
if index.to_i > column_index.to_i
|
||||
repository_state['columns'][(index.to_i - 1).to_s] =
|
||||
repository_state['columns'].delete(index)
|
||||
else
|
||||
index
|
||||
end
|
||||
end
|
||||
repository_state['ColReorder'].delete(column_index)
|
||||
repository_state['ColReorder'].map! do |index|
|
||||
if index.to_i > column_index.to_i
|
||||
(index.to_i - 1).to_s
|
||||
else
|
||||
index
|
||||
end
|
||||
end
|
||||
else
|
||||
# add column
|
||||
index = repository_state['columns'].count
|
||||
repository_state['columns'][index] = RepositoryDatatable::
|
||||
REPOSITORY_TABLE_DEFAULT_STATE['columns'].first
|
||||
repository_state['ColReorder'].insert(2, index)
|
||||
end
|
||||
repository_table.first.update(state: repository_state)
|
||||
end
|
||||
|
||||
def self.create_state(user, repository)
|
||||
default_columns_num = RepositoryDatatable::
|
||||
REPOSITORY_TABLE_DEFAULT_STATE['columns'].count
|
||||
repository_state =
|
||||
RepositoryDatatable::REPOSITORY_TABLE_DEFAULT_STATE.deep_dup
|
||||
repository.repository_columns.each_with_index do |_, index|
|
||||
repository_state['columns'] << RepositoryDatatable::
|
||||
REPOSITORY_TABLE_DEFAULT_STATE['columns'].first
|
||||
repository_state['ColReorder'] << (default_columns_num + index)
|
||||
end
|
||||
RepositoryTable.create(user: user,
|
||||
repository: repository,
|
||||
state: repository_state)
|
||||
end
|
||||
end
|
|
@ -1,9 +1,16 @@
|
|||
class RepositoryTextValue < ActiveRecord::Base
|
||||
has_one :repository_cell, as: :value
|
||||
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
|
||||
belongs_to :last_modified_by, foreign_key: :last_modified_by_id,
|
||||
class_name: 'User'
|
||||
has_one :repository_cell, as: :value, dependent: :destroy
|
||||
accepts_nested_attributes_for :repository_cell
|
||||
|
||||
validates :repository_cell, presence: true
|
||||
validates :value,
|
||||
validates :data,
|
||||
presence: true,
|
||||
length: { maximum: Constants::TEXT_MAX_LENGTH }
|
||||
|
||||
def formatted
|
||||
data
|
||||
end
|
||||
end
|
||||
|
|
|
@ -48,6 +48,8 @@ class User < ActiveRecord::Base
|
|||
has_many :results, inverse_of: :user
|
||||
has_many :samples, inverse_of: :user
|
||||
has_many :samples_tables, inverse_of: :user, dependent: :destroy
|
||||
has_many :repositories, inverse_of: :user
|
||||
has_many :repository_tables, inverse_of: :user, dependent: :destroy
|
||||
has_many :steps, inverse_of: :user
|
||||
has_many :custom_fields, inverse_of: :user
|
||||
has_many :reports, inverse_of: :user
|
||||
|
@ -175,6 +177,9 @@ class User < ActiveRecord::Base
|
|||
class_name: 'Protocol',
|
||||
foreign_key: 'restored_by_id',
|
||||
inverse_of: :restored_by
|
||||
has_many :assigned_repository_rows_my_modules,
|
||||
class_name: 'RepositoryRowsMyModules',
|
||||
foreign_key: 'assigned_by_id'
|
||||
|
||||
has_many :user_notifications, inverse_of: :user
|
||||
has_many :notifications, through: :user_notifications
|
||||
|
|
|
@ -5,6 +5,33 @@
|
|||
<%= render partial: "shared/sidebar" %>
|
||||
<%= render partial: "shared/secondary_navigation" %>
|
||||
|
||||
<div id="content">
|
||||
<%= render partial: "shared/repository" %>
|
||||
<h3><%= @repository.name %></h3>
|
||||
|
||||
<div class="toolbarButtons" style="display:none">
|
||||
<% if can_assign_repository_records(@my_module, @repository) %>
|
||||
<button type="button" class="btn btn-default"
|
||||
data-assign-url="<%= assign_repository_records_my_module_path(@my_module, @repository)%>"
|
||||
id="assignRepositoryRecords" onclick="onClickAssignRecords()" disabled>
|
||||
<span class="glyphicon glyphicon-ok-circle"></span>
|
||||
<span class="hidden-xs-custom"><%= t'repositories.assign_records_to_module' %></span>
|
||||
</button>
|
||||
<% end %>
|
||||
<% if can_unassign_repository_records(@my_module, @repository) %>
|
||||
<button type="button" class="btn btn-default"
|
||||
data-unassign-url="<%= unassign_repository_records_my_module_path(@my_module, @repository)%>"
|
||||
id="unassignRepositoryRecords" onclick="onClickUnassignRecords()" disabled>
|
||||
<span class="glyphicon glyphicon-ban-circle"></span>
|
||||
<span class="hidden-xs-custom"><%= t'repositories.unassign_records_from_module' %></span>
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<%= render partial: "repositories/repository_table",
|
||||
locals: {
|
||||
repository: @repository,
|
||||
my_module: @my_module,
|
||||
repository_index_link: repository_index_my_module_path(@my_module, @repository, format: :json)
|
||||
}
|
||||
%>
|
||||
</div>
|
||||
|
|
16
app/views/repositories/_delete_column_modal.html.erb
Normal file
16
app/views/repositories/_delete_column_modal.html.erb
Normal file
|
@ -0,0 +1,16 @@
|
|||
<div class="modal fade" id="deleteRepositoryColumn" tabindex="-1" role="dialog" aria-labelledby="deleteRepositoryColumnLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><%= t("repositories.modal_delete_column.title") %></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-action="delete"><%= t("repositories.modal_delete_column.delete") %></button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><%= t("general.cancel")%></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
20
app/views/repositories/_delete_column_modal_body.html.erb
Normal file
20
app/views/repositories/_delete_column_modal_body.html.erb
Normal file
|
@ -0,0 +1,20 @@
|
|||
<%= bootstrap_form_for @repository_column,
|
||||
url: repository_repository_column_path(@repository,
|
||||
@repository_column,
|
||||
format: :json),
|
||||
remote: :true,
|
||||
method: :delete,
|
||||
data: { role: "destroy-repository-column-form",
|
||||
id: @repository_column.id } do |f| %>
|
||||
<%= f.hidden_field :column_index, value: column_index %>
|
||||
<p><%= t("repositories.modal_delete_column.message", column: @repository_column.name) %></p>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||
|
||||
<%= t("repositories.modal_delete_column.alert_heading") %>
|
||||
<ul>
|
||||
<li><%= t("repositories.modal_delete_column.alert_line_1", nr: @repository_column.repository_cells.count) %></li>
|
||||
<li><%= t("repositories.modal_delete_column.alert_line_2") %></li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
20
app/views/repositories/_delete_record_modal.html.erb
Normal file
20
app/views/repositories/_delete_record_modal.html.erb
Normal file
|
@ -0,0 +1,20 @@
|
|||
<div class="modal fade" id="deleteRepositoryRecord" tabindex="-1" role="dialog" aria-labelledby="deleteRepositoryRecordLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><%= t("repositories.modal_delete_record.title") %></h4>
|
||||
<%= t("repositories.modal_delete_record.notice") %>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<%= t("repositories.modal_delete_record.other_samples") %>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal" onclick="onClickDeleteRecord();">
|
||||
<%= t("repositories.modal_delete_record.delete") %>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><%= t("general.cancel")%></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
75
app/views/repositories/_repository.html.erb
Normal file
75
app/views/repositories/_repository.html.erb
Normal file
|
@ -0,0 +1,75 @@
|
|||
<%= render partial: "repositories/delete_record_modal" %>
|
||||
<%= render partial: "repositories/delete_column_modal" %>
|
||||
|
||||
<div id="alert-container"></div>
|
||||
|
||||
<div id="repository-toolbar">
|
||||
|
||||
<% if can_create_repository_records(repository) %>
|
||||
<button type="button" class="btn btn-default editAdd" id="addRepositoryRecord" onclick="onClickAddRecord()">
|
||||
<span class="glyphicon glyphicon-plus"></span>
|
||||
<span class="hidden-xs"><%= t("repositories.add_new_record") %></span>
|
||||
</button>
|
||||
<% end %>
|
||||
|
||||
<div id="datatables-buttons" style="display: inline;">
|
||||
<div id="repository-columns-dropdown" class="dropdown">
|
||||
<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">
|
||||
<%= t('repositories.columns') %>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right smart-dropdown" id="repository-columns-list">
|
||||
<% if can_create_columns_in_repository(repository) %>
|
||||
<li class="add-new-column-form">
|
||||
<div id="new-column-form" class="form-group" data-action="<%= repository_repository_columns_path(repository) %>">
|
||||
<div class="input-group">
|
||||
<input class="form-control" id="new-column-name" placeholder="<%= t("repositories.column_new_text") %>">
|
||||
<span class="input-group-btn">
|
||||
<a id="add-new-column-button" class="btn btn-primary">
|
||||
<%= t("repositories.column_create") %>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-group inline" id="saveCancel" data-toggle="buttons" style="display:none">
|
||||
<button type="button" class="btn btn-primary" id="saveRecord" onclick="onClickSave()">
|
||||
<span class="glyphicon glyphicon-save"></span>
|
||||
<%= t("repositories.save_record") %>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" id="cancelSave" onclick="onClickCancel()">
|
||||
<span class="glyphicon glyphicon-remove visible-xs-inline"></span>
|
||||
<span class="hidden-xs"><%= t("repositories.cancel_save") %></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- These buttons are appended to table in javascript, after table initialization. -->
|
||||
<div class="toolbarButtons" style="display:none">
|
||||
<button type="button" class="btn btn-default editAdd" id="editRepositoryRecord" onclick="onClickEdit()" disabled>
|
||||
<span class="glyphicon glyphicon-pencil"></span>
|
||||
<span class="hidden-xs-custom"><%= t("repositories.edit_record") %></span>
|
||||
</button>
|
||||
|
||||
<% if can_delete_repository_records(repository) %>
|
||||
<button type="button" class="btn btn-default"
|
||||
id="deleteRepositoryRecordsButton" data-target="#deleteRepositoryRecord" data-toggle="modal" disabled>
|
||||
<span class="glyphicon glyphicon-trash"></span>
|
||||
<span class="hidden-xs-custom"><%= t'repositories.delete_record' %></span>
|
||||
<%= submit_tag I18n.t('repositories.delete_record'), :class => "hidden
|
||||
delete_repository_records_submit" %>
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= render partial: "repository_table",
|
||||
locals: {
|
||||
repository: repository,
|
||||
repository_index_link: repository_table_index_path(repository)
|
||||
}
|
||||
%>
|
44
app/views/repositories/_repository_table.html.erb
Normal file
44
app/views/repositories/_repository_table.html.erb
Normal file
|
@ -0,0 +1,44 @@
|
|||
<div class="repository-table">
|
||||
<table id="repository-table" class="table"
|
||||
data-current-uri="<%= request.original_url %>"
|
||||
data-repository-id="<%= repository.id %>"
|
||||
data-source="<%= repository_index_link %>"
|
||||
data-num-columns="<%= 5 + repository.repository_columns.count %>"
|
||||
data-create-record="<%= repository_repository_rows_path(repository) %>"
|
||||
data-delete-record="<%= repository_delete_records_path(repository) %>"
|
||||
data-max-dropdown-length="<%= Constants::NAME_TRUNCATION_LENGTH_DROPDOWN %>"
|
||||
data-save-text="<%= I18n.t('general.save') %>"
|
||||
data-edit-text="<%= I18n.t('general.edit') %>"
|
||||
data-cancel-text="<%= I18n.t('general.cancel') %>"
|
||||
data-columns-visibility-text="<%= I18n.t('repositories.columns_visibility') %>"
|
||||
data-columns-delete-text="<%= I18n.t('repositories.columns_delete') %>">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="checkbox"><input name="select_all" value="1" type="checkbox"></th>
|
||||
<% if @my_module %>
|
||||
<th id="assigned"><%= t("repositories.table.assigned") %></th>
|
||||
<% else %>
|
||||
<th id="assigned"></th>
|
||||
<% end %>
|
||||
<th id="row-name"><%= t("repositories.table.row_name") %></th>
|
||||
<th id="added-on"><%= t("repositories.table.added_on") %></th>
|
||||
<th id="added-by"><%= t("repositories.table.added_by") %></th>
|
||||
<% repository.repository_columns.each do |column| %>
|
||||
<th class="repository-column" id="<%= column.id %>"
|
||||
<%= 'data-editable' if can_edit_columns_in_repository(repository) %>
|
||||
<%= 'data-deletable' if can_delete_columns_in_repository(repository) %>
|
||||
<%= "data-edit-url='#{edit_repository_repository_column_path(repository, column)}'" %>
|
||||
<%= "data-update-url='#{repository_repository_column_path(repository, column)}'" %>
|
||||
<%= "data-destroy-html-url='#{repository_columns_destroy_html_path(repository, column)}'" %>
|
||||
>
|
||||
<%= display_tooltip(column.name) %>
|
||||
</th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<%= stylesheet_link_tag 'datatables' %>
|
||||
<%= javascript_include_tag('repositories/repository_datatable') %>
|
|
@ -74,6 +74,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<%= render partial: 'repository', locals: { repository: repo } %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
<div id="alert-container"></div>
|
||||
<h2><%= @repository.name %></h2>
|
|
@ -73,6 +73,8 @@ Rails.application.config.assets.precompile += %w(samples/sample_types_groups.js)
|
|||
Rails.application.config.assets.precompile += %w(highlightjs-github-theme.css)
|
||||
Rails.application.config.assets.precompile += %w(search.js)
|
||||
Rails.application.config.assets.precompile += %w(repositories/index.js)
|
||||
Rails.application.config.assets.precompile +=
|
||||
%w(repositories/repository_datatable.js)
|
||||
|
||||
# Libraries needed for Handsontable formulas
|
||||
Rails.application.config.assets.precompile += %w(lodash.js)
|
||||
|
|
|
@ -872,6 +872,65 @@ en:
|
|||
destroy:
|
||||
success_flash: "Table result successfully deleted."
|
||||
|
||||
repositories:
|
||||
index:
|
||||
head_title: "Repositories"
|
||||
title: "Repositories"
|
||||
no_repositories: "No repositories"
|
||||
no_teams:
|
||||
title: "Your dashboard is empty!"
|
||||
text: "It seems you're not a member of any team. See team management to sort it out."
|
||||
nav:
|
||||
breadcrumbs:
|
||||
repositories: "Repositories"
|
||||
table:
|
||||
assigned: "Assigned"
|
||||
row_name: "Name"
|
||||
added_on: "Added on"
|
||||
added_by: "Added by"
|
||||
add_new_record: "Add record"
|
||||
edit_record: "Edit"
|
||||
delete_record: "Delete"
|
||||
save_record: "Save"
|
||||
cancel_save: "Cancel"
|
||||
assign_records_to_module: "Assign"
|
||||
unassign_records_from_module: "Unassign"
|
||||
columns: "Columns"
|
||||
column_new_text: "New column"
|
||||
column_create: "Create"
|
||||
columns_delete: "Delete"
|
||||
columns_visibility: "Visible columns"
|
||||
modal_delete_record:
|
||||
title: "Delete record"
|
||||
notice: "Are you sure you want to delete the selected records?"
|
||||
other_samples: "Only records created by you will be deleted."
|
||||
delete: "Delete records"
|
||||
modal_delete_column:
|
||||
title: "Delete a column"
|
||||
message: "Are you sure you wish to permanently delete selected column %{column}? This action is irreversible."
|
||||
alert_heading: "Deleting a column has following consequences:"
|
||||
alert_line_1: "you will lose information in this column for %{nr} records;"
|
||||
alert_line_2: "the column will be deleted for all team members."
|
||||
delete: "Delete column"
|
||||
js:
|
||||
permission_error: "You don't have permission to edit this record."
|
||||
not_found_error: "This repository record does not exist."
|
||||
column_added: "New column was sucessfully created."
|
||||
empty_column_name: "Please enter column name."
|
||||
create:
|
||||
success_flash: "Successfully added record <strong>%{record}</strong> to repository <strong>%{repository}</strong>"
|
||||
update:
|
||||
success_flash: "Successfully updated record <strong>%{record}</strong> in repository <strong>%{repository}</strong>"
|
||||
destroy:
|
||||
success_flash: "%{records_number} record(s) successfully deleted."
|
||||
contains_other_records_flash: "%{records_number} record(s) successfully deleted. %{other_records_number} of the selected record(s) were created by other users and were not deleted."
|
||||
no_records_selected_flash: "There were no selected records."
|
||||
no_deleted_records_flash: "No records were deleted. %{other_records_number} of the selected records were created by other users and were not deleted."
|
||||
assigned_records_flash: "Successfully assigned record(s) <strong>%{records}</strong> to task"
|
||||
unassigned_records_flash: "Successfully unassigned record(s) <strong>%{records}</strong> from task"
|
||||
no_records_assigned_flash: "No records were assigned to task"
|
||||
no_records_unassigned_flash: "No records were unassigned from task"
|
||||
|
||||
samples:
|
||||
columns: "Columns"
|
||||
columns_visibility: "Toggle visibility"
|
||||
|
@ -1059,6 +1118,8 @@ en:
|
|||
uncomplete_module: "<i>%{user}</i> uncompleted task <strong>%{module}</strong>."
|
||||
assign_sample: "<i>%{user}</i> assigned sample(s) <strong>%{samples}</strong> to task(s) <strong>%{tasks}</strong>"
|
||||
unassign_sample: "<i>%{user}</i> unassigned sample(s) <strong>%{samples}</strong> from task(s) <strong>%{tasks}</strong>"
|
||||
assign_repository_records: "<i>%{user}</i> assigned <strong>%{repository}</strong> repository records(s) <strong>%{records}</strong> to task <strong>%{task}</strong>"
|
||||
unassign_repository_records: "<i>%{user}</i> unassigned <strong>%{repository}</strong> repository records(s) <strong>%{records}</strong> from task <strong>%{task}</strong>"
|
||||
create_step: "<i>%{user}</i> created Step %{step} <strong>%{step_name}</strong>."
|
||||
destroy_step: "<i>%{user}</i> deleted Step %{step} <strong>%{step_name}</strong>."
|
||||
add_comment_to_step: "<i>%{user}</i> commented on Step %{step} <strong>%{step_name}</strong>."
|
||||
|
@ -1594,6 +1655,8 @@ en:
|
|||
result_annotation_message_html: "Project: %{project} | Experiment: %{experiment} | Task: %{my_module}"
|
||||
sample_annotation_title: "%{user} mentioned you in Column: %{column} of Sample %{sample}"
|
||||
sample_annotation_message_html: "Sample: %{sample} | Column: %{column}"
|
||||
repository_annotation_title: "%{user} mentioned you in Column: %{column} of Record %{record} in Repository %{repository}"
|
||||
repository_annotation_message_html: "Record: %{record} | Column: %{column}"
|
||||
protocol_step_annotation_message_html: "Protocol: %{protocol}"
|
||||
email_title: "You've received a sciNote notification!"
|
||||
assign_user_to_team: "<i>%{assigned_user}</i> was added as %{role} to team <strong>%{team}</strong> by <i>%{assigned_by_user}</i>."
|
||||
|
|
|
@ -296,6 +296,15 @@ Rails.application.routes.draw do
|
|||
get 'repository/:repository_id',
|
||||
to: 'my_modules#repository',
|
||||
as: :repository
|
||||
post 'repository_index/:repository_id',
|
||||
to: 'my_modules#repository_index',
|
||||
as: :repository_index
|
||||
post 'assign_repository_records/:repository_id',
|
||||
to: 'my_modules#assign_repository_records',
|
||||
as: :assign_repository_records
|
||||
post 'unassign_repository_records/:repository_id',
|
||||
to: 'my_modules#unassign_repository_records',
|
||||
as: :unassign_repository_records
|
||||
get 'archive' # Archive view for single module
|
||||
get 'complete_my_module'
|
||||
post 'toggle_task_state'
|
||||
|
@ -403,6 +412,35 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
resources :repositories do
|
||||
post 'repository_index',
|
||||
to: 'repositories#repository_table_index',
|
||||
as: 'table_index',
|
||||
defaults: { format: 'json' }
|
||||
# Save repository table state
|
||||
post 'state_save',
|
||||
to: 'user_repositories#save_table_state',
|
||||
as: 'save_table_state',
|
||||
defaults: { format: 'json' }
|
||||
# Load repository table state
|
||||
post 'state_load',
|
||||
to: 'user_repositories#load_table_state',
|
||||
as: 'load_table_state',
|
||||
defaults: { format: 'json' }
|
||||
# Delete records from repository
|
||||
post 'delete_records',
|
||||
to: 'repository_rows#delete_records',
|
||||
as: 'delete_records',
|
||||
defaults: { format: 'json' }
|
||||
post 'repository_columns/:id/destroy_html',
|
||||
to: 'repository_columns#destroy_html',
|
||||
as: 'columns_destroy_html'
|
||||
|
||||
resources :repository_columns, only: %i(create edit update destroy)
|
||||
|
||||
resources :repository_rows, only: %i(create edit update)
|
||||
end
|
||||
|
||||
get 'search' => 'search#index'
|
||||
get 'search/new' => 'search#new', as: :new_search
|
||||
|
||||
|
|
|
@ -22,11 +22,13 @@ class AddCustomRepositories < ActiveRecord::Migration
|
|||
create_table :repository_rows do |t|
|
||||
t.belongs_to :repository, index: true
|
||||
t.integer :created_by_id, null: false
|
||||
t.integer :last_modified_by_id, null: false
|
||||
t.string :name, index: true
|
||||
t.timestamps null: true
|
||||
end
|
||||
|
||||
add_foreign_key :repository_rows, :users, column: :created_by_id
|
||||
add_foreign_key :repository_rows, :users, column: :last_modified_by_id
|
||||
|
||||
create_table :repository_cells do |t|
|
||||
t.belongs_to :repository_row, index: true
|
||||
|
@ -36,13 +38,45 @@ class AddCustomRepositories < ActiveRecord::Migration
|
|||
end
|
||||
|
||||
create_table :repository_date_values do |t|
|
||||
t.datetime :value
|
||||
t.datetime :data
|
||||
t.timestamps null: true
|
||||
t.integer :created_by_id, null: false
|
||||
t.integer :last_modified_by_id, null: false
|
||||
end
|
||||
|
||||
add_foreign_key :repository_date_values, :users, column: :created_by_id
|
||||
add_foreign_key :repository_date_values,
|
||||
:users,
|
||||
column: :last_modified_by_id
|
||||
|
||||
create_table :repository_text_values do |t|
|
||||
t.string :value
|
||||
t.string :data
|
||||
t.timestamps null: true
|
||||
t.integer :created_by_id, null: false
|
||||
t.integer :last_modified_by_id, null: false
|
||||
end
|
||||
|
||||
add_foreign_key :repository_text_values, :users, column: :created_by_id
|
||||
add_foreign_key :repository_text_values,
|
||||
:users,
|
||||
column: :last_modified_by_id
|
||||
|
||||
create_table :my_modules_repository_rows do |t|
|
||||
t.integer :repository_row_id, index: true, null: false
|
||||
t.integer :my_module_id, null: :false
|
||||
t.integer :assigned_by_id, null: false
|
||||
t.timestamps null: true
|
||||
t.index %i(my_module_id repository_row_id),
|
||||
name: 'index_my_module_ids_repository_row_ids'
|
||||
end
|
||||
|
||||
add_foreign_key :my_modules_repository_rows, :users, column: :assigned_by_id
|
||||
|
||||
create_table :repository_tables do |t|
|
||||
t.jsonb :state, null: false
|
||||
t.references :user, index: true, null: false
|
||||
t.references :repository, index: true, null: false
|
||||
t.timestamps null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue