scinote-web/app/controllers/steps_controller.rb

600 lines
18 KiB
Ruby
Raw Normal View History

2016-02-12 23:52:43 +08:00
class StepsController < ApplicationController
include ActionView::Helpers::TextHelper
include ApplicationHelper
2017-04-06 19:03:13 +08:00
include StepsActions
include MarvinJsActions
before_action :load_vars, only: %i(edit update destroy show toggle_step_state checklistitem_state update_view_state
move_up move_down)
before_action :load_vars_nested, only: %i(new create)
before_action :convert_table_contents_to_utf8, only: %i(create update)
2016-02-12 23:52:43 +08:00
before_action :check_view_permissions, only: %i(show update_view_state)
before_action :check_manage_permissions, only: %i(new create edit update destroy move_up move_down)
before_action :check_complete_and_checkbox_permissions, only: %i(toggle_step_state checklistitem_state)
2016-02-12 23:52:43 +08:00
def new
@step = Step.new
respond_to do |format|
2016-12-08 18:26:13 +08:00
format.json do
2016-02-12 23:52:43 +08:00
render json: {
2016-12-08 18:26:13 +08:00
html: render_to_string(partial: 'new.html.erb')
2016-02-12 23:52:43 +08:00
}
2016-12-08 18:26:13 +08:00
end
2016-02-12 23:52:43 +08:00
end
end
def create
@step = Step.new
@step.transaction do
new_step_params = step_params
# Attach newly uploaded files, and than remove their blob ids from the parameters
new_step_params[:assets_attributes]&.each do |key, value|
next unless value[:signed_blob_id]
asset = Asset.create!(created_by: current_user, last_modified_by: current_user, team: current_team)
asset.file.attach(value[:signed_blob_id])
@step.assets << asset
new_step_params[:assets_attributes].delete(key)
2016-02-12 23:52:43 +08:00
end
@step.assign_attributes(new_step_params)
# gerate a tag that replaces img tag in database
@step.completed = false
@step.position = @protocol.number_of_steps
@step.protocol = @protocol
@step.user = current_user
@step.last_modified_by = current_user
@step.tables.each do |table|
table.created_by = current_user
table.team = current_team
end
# Update default checked state
@step.checklists.each do |checklist|
checklist.checklist_items.each do |checklist_item|
checklist_item.checked = false
2016-02-12 23:52:43 +08:00
end
end
2016-02-12 23:52:43 +08:00
# link tiny_mce_assets to the step
TinyMceAsset.update_images(@step, params[:tiny_mce_images], current_user)
@step.save!
# Post process all assets
@step.assets.each do |asset|
asset.post_process_file(@protocol.team)
end
# link tiny_mce_assets to the step
TinyMceAsset.update_images(@step, params[:tiny_mce_images], current_user)
2016-07-21 19:11:15 +08:00
create_annotation_notifications(@step)
# Generate activity
if @protocol.in_module?
log_activity(:create_step, @my_module.experiment.project, my_module: @my_module.id)
else
log_activity(:add_step_to_protocol_repository, nil, protocol: @protocol.id)
end
# Update protocol timestamp
update_protocol_ts(@step)
end
2016-02-12 23:52:43 +08:00
respond_to do |format|
if @step.errors.empty?
format.json do
2016-02-12 23:52:43 +08:00
render json: {
html: render_to_string(
partial: 'steps/step.html.erb',
locals: { step: @step }
)
},
status: :ok
end
2016-02-12 23:52:43 +08:00
else
format.json do
render json: @step.errors.to_json, status: :bad_request
end
2016-02-12 23:52:43 +08:00
end
end
end
def show
respond_to do |format|
2016-12-08 18:26:13 +08:00
format.json do
2016-02-12 23:52:43 +08:00
render json: {
2016-12-08 18:26:13 +08:00
html: render_to_string(
partial: 'steps/step.html.erb',
locals: { step: @step }
)
},
status: :ok
end
2016-02-12 23:52:43 +08:00
end
end
def edit
respond_to do |format|
2016-12-08 18:26:13 +08:00
format.json do
2016-02-12 23:52:43 +08:00
render json: {
2016-12-08 18:26:13 +08:00
html: render_to_string(partial: 'edit.html.erb')
},
status: :ok
end
2016-02-12 23:52:43 +08:00
end
end
def update
respond_to do |format|
2017-04-06 14:42:16 +08:00
old_description = @step.description
old_checklists = fetch_old_checklists_data(@step)
new_checklists = fetch_new_checklists_data
2016-02-12 23:52:43 +08:00
previous_size = @step.space_taken
step_params_all = step_params
# process only destroy update on step references. This prevents
# skipping deleting reference in case update validation fails.
# NOTE - step_params_all variable is updated
destroy_attributes(step_params_all)
# Attach newly uploaded files, and than remove their blob ids from the parameters
2019-12-03 17:17:47 +08:00
new_assets = []
step_params_all[:assets_attributes]&.each do |key, value|
next unless value[:signed_blob_id]
2019-12-03 17:17:47 +08:00
new_asset = @step.assets.create!(created_by: current_user, last_modified_by: current_user, team: current_team)
new_asset.file
.attach(value[:signed_blob_id])
new_assets.push(new_asset.id)
step_params_all[:assets_attributes].delete(key)
end
2016-02-12 23:52:43 +08:00
@step.assign_attributes(step_params_all)
@step.last_modified_by = current_user
@step.tables.each do |table|
table.created_by = current_user if table.new_record?
table.last_modified_by = current_user unless table.new_record?
table.team = current_team
end
update_checklist_items_without_callback(step_params_all)
2016-02-12 23:52:43 +08:00
if @step.save
2019-03-11 20:43:50 +08:00
TinyMceAsset.update_images(@step, params[:tiny_mce_images], current_user)
2016-02-12 23:52:43 +08:00
@step.reload
2017-04-06 14:42:16 +08:00
# generates notification on step upadate
2017-04-10 17:21:28 +08:00
update_annotation_notifications(@step,
old_description,
new_checklists,
old_checklists)
2017-04-06 14:42:16 +08:00
# Release team's space taken
team = @protocol.team
team.release_space(previous_size)
team.save
2016-02-12 23:52:43 +08:00
# Post process step assets
@step.assets.each do |asset|
2019-12-03 17:17:47 +08:00
asset.post_process_file(team) if new_assets.include? asset.id
2016-02-12 23:52:43 +08:00
end
# Generate activity
2016-07-21 19:11:15 +08:00
if @protocol.in_module?
2019-03-29 22:09:20 +08:00
log_activity(:edit_step, @my_module.experiment.project, my_module: @my_module.id)
2016-07-21 19:11:15 +08:00
else
2019-03-29 22:09:20 +08:00
log_activity(:edit_step_in_protocol_repository, nil, protocol: @protocol.id)
2016-07-21 19:11:15 +08:00
end
# Update protocol timestamp
update_protocol_ts(@step)
2016-02-12 23:52:43 +08:00
format.json {
render json: {
html: render_to_string({
2016-08-18 02:49:20 +08:00
partial: 'step.html.erb',
locals: { step: @step }
})
}
2016-02-12 23:52:43 +08:00
}
else
format.json {
render json: @step.errors.to_json, status: :bad_request
2016-02-12 23:52:43 +08:00
}
end
end
end
def update_view_state
view_state = @step.current_view_state(current_user)
view_state.state['assets']['sort'] = params.require(:assets).require(:order)
view_state.save! if view_state.changed?
respond_to do |format|
format.json do
render json: {}, status: :ok
end
end
end
2016-02-12 23:52:43 +08:00
def destroy
if @step.can_destroy?
# Update position on other steps of this module
@protocol.steps.where('position > ?', @step.position).each do |step|
step.position = step.position - 1
step.save
end
# Calculate space taken by this step
team = @protocol.team
previous_size = @step.space_taken
2016-02-12 23:52:43 +08:00
# Generate activity
if @protocol.in_module?
2019-03-29 22:09:20 +08:00
log_activity(:destroy_step, @my_module.experiment.project, my_module: @my_module.id)
else
2019-03-29 22:09:20 +08:00
log_activity(:delete_step_in_protocol_repository, nil, protocol: @protocol.id)
end
2019-03-09 00:01:59 +08:00
# Destroy the step
@step.destroy
2016-02-12 23:52:43 +08:00
# Release space taken by the step
team.release_space(previous_size)
team.save
2016-02-12 23:52:43 +08:00
# Update protocol timestamp
update_protocol_ts(@step)
2016-02-12 23:52:43 +08:00
flash[:success] = t(
'protocols.steps.destroy.success_flash',
step: (@step.position_plus_one).to_s
)
else
flash[:error] = t(
'protocols.steps.destroy.error_flash',
step: (@step.position_plus_one).to_s
)
end
2016-07-21 19:11:15 +08:00
if @protocol.in_module?
redirect_to protocols_my_module_path(@step.my_module)
else
redirect_to edit_protocol_path(@protocol)
end
2016-02-12 23:52:43 +08:00
end
# Responds to checkbox toggling in steps view
def checklistitem_state
respond_to do |format|
checked = params[:checked] == 'true'
changed = @chk_item.checked != checked
@chk_item.checked = checked
if @chk_item.save
format.json { render json: {}, status: :accepted }
# Create activity
if changed
completed_items = @chk_item.checklist.checklist_items
2018-02-10 00:05:43 +08:00
.where(checked: true).count
all_items = @chk_item.checklist.checklist_items.count
text_activity = smart_annotation_parser(@chk_item.text)
.gsub(/\s+/, ' ')
type_of = if checked
:check_step_checklist_item
else
:uncheck_step_checklist_item
end
# This should always hold true (only in module can
# check items be checked, but still check just in case)
if @protocol.in_module?
2019-03-21 04:34:47 +08:00
log_activity(type_of,
@protocol.my_module.experiment.project,
2019-03-29 22:09:20 +08:00
my_module: @my_module.id,
2019-03-21 04:34:47 +08:00
step: @chk_item.checklist.step.id,
step_position: { id: @chk_item.checklist.step.id,
value_for: 'position_plus_one' },
2019-03-21 04:34:47 +08:00
checkbox: text_activity,
num_completed: completed_items.to_s,
num_all: all_items.to_s)
2016-02-12 23:52:43 +08:00
end
end
else
format.json { render json: {}, status: :unprocessable_entity }
2016-02-12 23:52:43 +08:00
end
end
end
# Complete/uncomplete step
def toggle_step_state
respond_to do |format|
completed = params[:completed] == 'true'
changed = @step.completed != completed
@step.completed = completed
if @step.save
if @protocol.in_module?
ready_to_complete = @protocol.my_module.check_completness_status
end
2016-02-12 23:52:43 +08:00
# Create activity
if changed
completed_steps = @protocol.steps.where(completed: true).count
all_steps = @protocol.steps.count
2016-02-12 23:52:43 +08:00
type_of = completed ? :complete_step : :uncomplete_step
# Toggling step state can only occur in
# module protocols, so my_module is always
# not nil; nonetheless, check if my_module is present
if @protocol.in_module?
2019-03-21 04:34:47 +08:00
log_activity(type_of,
@protocol.my_module.experiment.project,
2019-03-29 22:09:20 +08:00
my_module: @my_module.id,
2019-03-21 04:34:47 +08:00
num_completed: completed_steps.to_s,
num_all: all_steps.to_s)
2016-02-12 23:52:43 +08:00
end
end
2016-02-12 23:52:43 +08:00
# Create localized title for complete/uncomplete button
localized_title = if !completed
t('protocols.steps.options.complete_title')
else
t('protocols.steps.options.uncomplete_title')
end
format.json do
if ready_to_complete && @protocol.my_module.uncompleted?
render json: {
task_ready_to_complete: true,
new_title: localized_title
}, status: :ok
2016-02-12 23:52:43 +08:00
else
render json: { new_title: localized_title }, status: :ok
2016-02-12 23:52:43 +08:00
end
end
else
format.json { render json: {}, status: :unprocessable_entity }
2016-02-12 23:52:43 +08:00
end
end
end
def move_up
2016-07-21 19:11:15 +08:00
respond_to do |format|
if @step.position.positive?
step_down = @step.protocol.steps.find_by(position: @step.position - 1)
@step.position -= 1
@step.save
if step_down
step_down.position += 1
step_down.save
# Update protocol timestamp
update_protocol_ts(@step)
format.json do
render json: { move_direction: 'up',
step_up_position: @step.position,
step_down_position: step_down.position },
status: :ok
2016-02-12 23:52:43 +08:00
end
2016-07-21 19:11:15 +08:00
else
format.json { render json: {}, status: :forbidden }
2016-02-12 23:52:43 +08:00
end
else
format.json { render json: {}, status: :forbidden }
2016-02-12 23:52:43 +08:00
end
end
end
def move_down
2016-07-21 19:11:15 +08:00
respond_to do |format|
if @step.position < @step.protocol.steps.count - 1
step_up = @step.protocol.steps.find_by(position: @step.position + 1)
@step.position += 1
@step.save
if step_up
step_up.position -= 1
step_up.save
# Update protocol timestamp
update_protocol_ts(@step)
format.json do
render json: { move_direction: 'down',
step_up_position: step_up.position,
step_down_position: @step.position }
2016-02-12 23:52:43 +08:00
end
2016-07-21 19:11:15 +08:00
else
format.json { render json: {}, status: :forbidden }
2016-02-12 23:52:43 +08:00
end
else
format.json { render json: {}, status: :forbidden }
2016-02-12 23:52:43 +08:00
end
end
end
private
# This function is used for partial update of step references and
# it's useful when you want to execute destroy action on attribute
# collection separately from normal update action, for example if
# you don't want that update validation interupt destroy action.
# In case of step model you can delete checkboxes, assets or tables.
def destroy_attributes(params)
update_params = {}
delete_step_tables(params)
2016-02-12 23:52:43 +08:00
extract_destroy_params(params, update_params)
2019-09-12 23:21:48 +08:00
@step.update(update_params) unless update_params.empty?
2016-02-12 23:52:43 +08:00
end
# Delete the step table
def delete_step_tables(params)
return unless params[:tables_attributes].present?
2018-02-06 23:15:10 +08:00
params[:tables_attributes].each do |_, table|
next unless table['_destroy']
table_to_destroy = Table.find_by_id(table['id'])
2018-08-21 19:26:16 +08:00
next if table_to_destroy.nil?
table_to_destroy.report_elements.destroy_all
end
end
2016-02-12 23:52:43 +08:00
# Checks if hash contains destroy parameter '_destroy' and returns
# boolean value.
def has_destroy_params?(params)
params.each do |key, values|
next unless values.respond_to?(:each)
params[key].each do |_, attrs|
return true if attrs[:_destroy] == '1'
2016-02-12 23:52:43 +08:00
end
end
false
end
# Extracts part of hash that contains destroy parameters. It deletes
# values that contains destroy parameters from original variable and
# puts them into update_params variable.
def extract_destroy_params(params, update_params)
params.each do |key, values|
next unless values.respond_to?(:each)
update_params[key] = {} unless update_params[key]
attr_params = update_params[key]
params[key].each do |pos, attrs|
if attrs[:_destroy] == '1'
if attrs[:id].present?
asset = Asset.find_by_id(attrs[:id])
if asset.try(&:locked?)
asset.errors.add(:base, 'This file is locked.')
else
attr_params[pos] = { id: attrs[:id], _destroy: '1' }
end
2016-02-12 23:52:43 +08:00
end
params[key].delete(pos)
elsif has_destroy_params?(params[key][pos])
attr_params[pos] = { id: attrs[:id] }
extract_destroy_params(params[key][pos], attr_params[pos])
2016-02-12 23:52:43 +08:00
end
end
end
end
def load_vars
@step = Step.find_by(id: params[:id])
return render_404 unless @step
2016-07-21 19:11:15 +08:00
@protocol = @step.protocol
@chk_item = ChecklistItem.find_by(id: params[:checklistitem_id]) if params[:checklistitem_id]
@my_module = @protocol.my_module if @protocol.in_module?
2016-02-12 23:52:43 +08:00
end
def load_vars_nested
@protocol = Protocol.find_by(id: params[:protocol_id])
2016-02-12 23:52:43 +08:00
return render_404 unless @protocol
2016-07-21 19:11:15 +08:00
@my_module = @protocol.my_module if @protocol.in_module?
2016-02-12 23:52:43 +08:00
end
def convert_table_contents_to_utf8
if params.include? :step and
params[:step].include? :tables_attributes then
params[:step][:tables_attributes].each do |k,v|
params[:step][:tables_attributes][k][:contents] =
v[:contents].encode(Encoding::UTF_8).force_encoding(Encoding::UTF_8)
end
end
end
2016-07-21 19:11:15 +08:00
def update_protocol_ts(step)
if step.present? && step.protocol.present?
step.protocol.update(updated_at: Time.now)
end
end
def update_checklist_items_without_callback(params)
params.dig('checklists_attributes')&.values&.each do |cl|
ck = @step.checklists.find_by(id: cl[:id])
next if ck.nil? # ck is new checklist, skip update positions
cl['checklist_items_attributes']&.each do |item|
# Here item is somehow array of index and parameters [0, paramteters<Object>], should be fixed on FE also
item_record = ck.checklist_items.find_by(id: item[1][:id])
2020-08-14 18:32:39 +08:00
next unless item_record
item_record.update_attribute('position', item[1][:position])
end
end
end
2016-02-12 23:52:43 +08:00
def check_view_permissions
render_403 unless can_read_protocol_in_module?(@protocol) || can_read_protocol_in_repository?(@protocol)
2016-02-12 23:52:43 +08:00
end
def check_manage_permissions
render_403 unless can_manage_protocol_in_module?(@protocol) || can_manage_protocol_in_repository?(@protocol)
2016-02-12 23:52:43 +08:00
end
def check_complete_and_checkbox_permissions
render_403 unless can_complete_or_checkbox_step?(@protocol)
end
2016-02-12 23:52:43 +08:00
def step_params
params.require(:step).permit(
:name,
:description,
checklists_attributes: [
:id,
:name,
:_destroy,
checklist_items_attributes: [
:id,
:text,
:position,
:_destroy
]
],
assets_attributes: [
:id,
:_destroy,
:signed_blob_id
2016-02-12 23:52:43 +08:00
],
tables_attributes: [
:id,
2017-01-17 00:11:08 +08:00
:name,
2016-02-12 23:52:43 +08:00
:contents,
:_destroy
2019-05-03 21:24:28 +08:00
],
marvin_js_assets_attributes: %i(
id
_destroy
)
2016-02-12 23:52:43 +08:00
)
end
2019-03-09 00:01:59 +08:00
2019-03-21 04:34:47 +08:00
def log_activity(type_of, project = nil, message_items = {})
2019-03-29 22:09:20 +08:00
default_items = { step: @step.id,
step_position: { id: @step.id, value_for: 'position_plus_one' } }
2019-03-21 04:34:47 +08:00
message_items = default_items.merge(message_items)
2019-03-09 00:01:59 +08:00
Activities::CreateActivityService
.call(activity_type: type_of,
owner: current_user,
subject: @protocol,
team: current_team,
project: project,
2019-03-21 04:34:47 +08:00
message_items: message_items)
2019-03-09 00:01:59 +08:00
end
2016-02-12 23:52:43 +08:00
end