mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 23:16:15 +08:00
Merge branch 'features/protocol_versioning' into sb_SCI-8007
This commit is contained in:
commit
a25a49a051
|
@ -8,7 +8,7 @@
|
|||
let submitBtn = $(this).find('input[type="submit"]');
|
||||
|
||||
$(this).find('input:checked').each((_, el) => {
|
||||
let select = $(el).closest('.row').find('select');
|
||||
let select = $(el).closest('.new-member-item').find('select');
|
||||
let selectValue = parseInt(select.val(), 10);
|
||||
values.push(selectValue);
|
||||
count += 1;
|
||||
|
@ -48,7 +48,7 @@
|
|||
|
||||
$(document).on('click', '.user-assignment-dropdown .user-role-selector', function() {
|
||||
let roleId = $(this).data('role-id');
|
||||
$(this).closest('.dropdown').find('#user_assignment_user_role_id').val(roleId);
|
||||
$(this).closest('.dropdown').find('#user_assignment_user_role_id, .default-public-user-role-id').val(roleId);
|
||||
$(this).closest('form').trigger('submit');
|
||||
});
|
||||
}
|
||||
|
|
|
@ -403,8 +403,8 @@ var ProtocolsIndex = (function() {
|
|||
let protocolsContainer = '.protocols-container';
|
||||
let versionsModal = '#protocol-versions-modal';
|
||||
|
||||
protocolsTableEl.on('click', '.protocol-versions-link', function(e) {
|
||||
$.get(this.href, function(data) {
|
||||
function loadVersionModal(href) {
|
||||
$.get(href, function(data) {
|
||||
$(protocolsContainer).append($.parseHTML(data.html));
|
||||
$(versionsModal).modal('show');
|
||||
inlineEditing.init();
|
||||
|
@ -415,12 +415,16 @@ var ProtocolsIndex = (function() {
|
|||
$(versionsModal).remove();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protocolsTableEl.on('click', '.protocol-versions-link', function(e) {
|
||||
loadVersionModal(this.href);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
$(protocolsContainer).on('click', '#protocolVersions', function() {
|
||||
$(`tr[data-row-id=${rowsSelected[0]}] .protocol-versions-link`).click();
|
||||
loadVersionModal($(`tr[data-row-id=${rowsSelected[0]}]`).data('versions-url'));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -512,13 +516,12 @@ var ProtocolsIndex = (function() {
|
|||
url: $el.data('url'),
|
||||
data: JSON.stringify({ ids: rowsSelected }),
|
||||
contentType: 'application/json'
|
||||
},
|
||||
(data) => {
|
||||
animateSpinner(null, false);
|
||||
HelperModule.flashAlertMsg(data.message, 'success');
|
||||
reloadTable();
|
||||
}
|
||||
).error((data) => {
|
||||
).success((data) => {
|
||||
animateSpinner(null, false);
|
||||
HelperModule.flashAlertMsg(data.message, 'success');
|
||||
reloadTable();
|
||||
}).error((data) => {
|
||||
animateSpinner(null, false);
|
||||
HelperModule.flashAlertMsg(data.responseJSON.message, 'danger');
|
||||
});
|
||||
|
@ -691,6 +694,8 @@ var ProtocolsIndex = (function() {
|
|||
actionToolbar.find('.btn').removeClass('btn-primary').addClass('btn-light');
|
||||
actionToolbar.find('.btn:visible').first().addClass('btn-primary').removeClass('btn-light');
|
||||
actionToolbar.find('.btn').removeClass('notransition');
|
||||
|
||||
actionToolbar.find('.emptyPlaceholder').toggleClass('hidden', actionToolbar.find('.btn:visible').length > 0);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* global animateSpinner PerfectSb initHandsOnTable */
|
||||
/* global HelperModule */
|
||||
/* global HelperModule dropdownSelector */
|
||||
/* eslint-disable no-use-before-define, no-alert */
|
||||
|
||||
function applyClickCallbackOnProtocolCards() {
|
||||
|
@ -34,6 +34,7 @@ function applyClickCallbackOnProtocolCards() {
|
|||
$('.full-preview-panel').attr('data-url', currProtocolCard.data('url'));
|
||||
$('.full-preview-panel').attr('data-protocol-source', currProtocolCard.data('protocol-source'));
|
||||
$('.full-preview-panel').attr('data-id', currProtocolCard.data('id'));
|
||||
$('.full-preview-panel').data('id', currProtocolCard.data('id'));
|
||||
$('.convert-protocol').attr('disabled', false);
|
||||
|
||||
// Set base tag to account for relative links in the iframe
|
||||
|
@ -194,6 +195,16 @@ function handleFormSubmit(modal) {
|
|||
e.preventDefault(); // avoid to execute the actual submit of the form.
|
||||
e.stopPropagation();
|
||||
animateSpinner(modal, true);
|
||||
|
||||
const visibility = $('#protocol-preview-modal .modal-footer #visibility').prop('checked');
|
||||
const defaultPublicUserRoleId = $('#protocol-preview-modal .modal-footer #default_public_user_role_id')
|
||||
.prop('value');
|
||||
const visibilityField = $('#protocol-preview-modal #protocol_visibility');
|
||||
const defaultPublicUserRoleIdField = $('#protocol-preview-modal #protocol_default_public_user_role_id');
|
||||
|
||||
visibilityField.prop('value', visibility ? 'visible' : 'hidden');
|
||||
defaultPublicUserRoleIdField.prop('value', defaultPublicUserRoleId);
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
|
@ -215,7 +226,7 @@ function handleFormSubmit(modal) {
|
|||
|
||||
function initLoadProtocolModalPreview() {
|
||||
$('.convert-protocol').off('click').on('click', function(e) {
|
||||
var link = $('.full-preview-panel');
|
||||
const link = $('.full-preview-panel');
|
||||
animateSpinner(null, true);
|
||||
$.ajax({
|
||||
url: link.data('url'),
|
||||
|
@ -226,10 +237,11 @@ function initLoadProtocolModalPreview() {
|
|||
},
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
var modal = $('#protocol-preview-modal');
|
||||
var modalTitle = modal.find('.modal-title');
|
||||
var modalBody = modal.find('.modal-body');
|
||||
var modalFooter = modal.find('.modal-footer');
|
||||
$('#protocolsioModal').modal('hide');
|
||||
const modal = $('#protocol-preview-modal');
|
||||
const modalTitle = modal.find('.modal-title');
|
||||
const modalBody = modal.find('.modal-body');
|
||||
const modalFooter = modal.find('.modal-footer');
|
||||
|
||||
modalTitle.html(data.title);
|
||||
modalBody.html(data.html);
|
||||
|
@ -243,6 +255,7 @@ function initLoadProtocolModalPreview() {
|
|||
showFormErrors(modal, data.validation_errors);
|
||||
}
|
||||
|
||||
initProtocolModalPreview();
|
||||
initFormSubmits();
|
||||
handleFormSubmit(modal);
|
||||
},
|
||||
|
@ -272,3 +285,36 @@ applySearchCallback();
|
|||
|
||||
// Trigger initial retrieval of latest publications
|
||||
$('form.protocols-search-bar').submit();
|
||||
|
||||
function initProtocolModalPreview() {
|
||||
$('#protocol-preview-modal').on('change', '#visibility', function() {
|
||||
const checkbox = this;
|
||||
$('#protocol-preview-modal #roleSelectWrapper').toggleClass('hidden', !checkbox.checked);
|
||||
});
|
||||
|
||||
|
||||
const roleSelector = '#protocol-preview-modal #role_selector';
|
||||
|
||||
dropdownSelector.init(roleSelector, {
|
||||
noEmptyOption: true,
|
||||
singleSelect: true,
|
||||
closeOnSelect: true,
|
||||
selectAppearance: 'simple',
|
||||
onChange: function() {
|
||||
$('#protocol-preview-modal #default_public_user_role_id').val(dropdownSelector.getValues(roleSelector));
|
||||
}
|
||||
});
|
||||
|
||||
$('#protocol-preview-modal')
|
||||
.on('ajax:error', 'form', function(e, error) {
|
||||
let msg = error.responseJSON.error;
|
||||
$('#protocol-preview-modal #protocol_name').parent().addClass('error').attr('data-error-text', msg);
|
||||
})
|
||||
.on('ajax:success', 'form', function(e, data) {
|
||||
if (data.message) {
|
||||
HelperModule.flashAlertMsg(data.message, 'success');
|
||||
}
|
||||
$('#protocol-preview-modal #protocol_name').parent().removeClass('error');
|
||||
$('#protocol-preview-modal').modal('hide');
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,26 @@
|
|||
#user_assignments_modal {
|
||||
|
||||
.btn-role-select {
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-white;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: $color-black;
|
||||
opacity: 1;
|
||||
|
||||
.caret {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
min-height: calc(100vh - 200px);
|
||||
overflow: auto;
|
||||
|
@ -7,9 +29,7 @@
|
|||
|
||||
.member-item {
|
||||
border-bottom: 1px solid $modal-header-border-color;
|
||||
|
||||
margin: 0 .5em;
|
||||
padding: .5em;
|
||||
padding: .5em 0;
|
||||
}
|
||||
|
||||
.user-assignment-role .dropdown-toggle,
|
||||
|
|
|
@ -426,6 +426,7 @@
|
|||
left: -125px;
|
||||
max-width: 100vw;
|
||||
width: 506px;
|
||||
padding: 15px 0;
|
||||
|
||||
.dropdown-header,
|
||||
.dropdown-body {
|
||||
|
|
|
@ -162,7 +162,7 @@
|
|||
&.selected {
|
||||
background: $color-white;
|
||||
color: $brand-primary;
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.fas {
|
||||
|
|
|
@ -18,6 +18,15 @@
|
|||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
tbody {
|
||||
td:not(:first-child) {
|
||||
max-width: 30rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
|
@ -46,10 +55,15 @@
|
|||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
height: calc(100% - var(--datatable-pagination-row) - var(--protocol-toolbar-size));
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.dataTables_scrollBody {
|
||||
height: 100%;
|
||||
|
||||
td:not(:first-child) {
|
||||
padding: 14px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// Cells
|
||||
|
@ -123,6 +137,18 @@
|
|||
width: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.select-block.has-draft {
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.input-field:focus-within,
|
||||
.datetime-picker-container:focus-within {
|
||||
border: 1px solid $brand-focus;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&.archived {
|
||||
|
|
|
@ -26,4 +26,43 @@
|
|||
.general-error {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
.footer {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.left-section {
|
||||
column-gap: 32px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.default-role-container {
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sci-input-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 12px;
|
||||
flex-grow: 1;
|
||||
|
||||
label {
|
||||
margin-bottom: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-section {
|
||||
column-gap: 8px;
|
||||
display: flex;
|
||||
flex-basis: 0;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,6 +56,11 @@
|
|||
top: 0;
|
||||
width: 100%;
|
||||
|
||||
.emptyPlaceholder {
|
||||
color: $color-volcano;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-right: .25em;
|
||||
}
|
||||
|
|
|
@ -233,6 +233,10 @@
|
|||
float: left;
|
||||
margin: 2px 4px 2px 0;
|
||||
|
||||
.fas {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&:nth-last-child(1) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
@import "constants";
|
||||
|
||||
.dropdown-menu .divider {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.sci-dropdown {
|
||||
[data-toggle="dropdown"] {
|
||||
.caret {
|
||||
|
|
|
@ -7,6 +7,24 @@
|
|||
}
|
||||
|
||||
.modal {
|
||||
.modal-dialog {
|
||||
.modal-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: 1.15em;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 0 0 1.15em 0;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 1.15em 0 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-absolute-close-button {
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
|
|
|
@ -38,7 +38,7 @@ module AccessPermissions
|
|||
@user_assignment.update!(permitted_update_params)
|
||||
|
||||
log_activity(:change_user_role_on_project, @user_assignment)
|
||||
propagate_job(user_assignment)
|
||||
propagate_job(@user_assignment)
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
|
|
|
@ -51,12 +51,17 @@ module AccessPermissions
|
|||
permitted_create_params[:resource_members].each do |_k, user_assignment_params|
|
||||
next unless user_assignment_params[:assign] == '1'
|
||||
|
||||
user_assignment = UserAssignment.new(user_assignment_params)
|
||||
user_assignment.assignable = @protocol
|
||||
user_assignment.assigned = :manually
|
||||
user_assignment.team = current_team
|
||||
user_assignment.assigned_by = current_user
|
||||
user_assignment.save!
|
||||
user_assignment = UserAssignment.find_or_initialize_by(
|
||||
assignable: @protocol,
|
||||
user_id: user_assignment_params[:user_id],
|
||||
team: current_team
|
||||
)
|
||||
|
||||
user_assignment.update!(
|
||||
user_role_id: user_assignment_params[:user_role_id],
|
||||
assigned_by: current_user,
|
||||
assigned: :manually
|
||||
)
|
||||
|
||||
created_count += 1
|
||||
log_activity(:protocol_template_access_granted, user_assignment)
|
||||
|
|
|
@ -49,10 +49,6 @@ class ProtocolsController < ApplicationController
|
|||
update_from_parent_modal
|
||||
delete_steps
|
||||
)
|
||||
before_action :check_manage_parent_in_repository_permissions, only: %i(
|
||||
update_parent
|
||||
update_parent_modal
|
||||
)
|
||||
before_action :check_manage_all_in_repository_permissions, only: :make_private
|
||||
before_action :check_restore_all_in_repository_permissions, only: :restore
|
||||
before_action :check_archive_all_in_repository_permissions, only: :archive
|
||||
|
@ -300,7 +296,7 @@ class ProtocolsController < ApplicationController
|
|||
log_activity(:create_protocol_in_repository, nil, protocol: @protocol.id)
|
||||
redirect_to protocol_path(@protocol)
|
||||
else
|
||||
render json: { error: @protocol.errors.full_messages.join(', ') }, status: :unprocessable_entity
|
||||
render json: { error: @protocol.errors.messages.map { |_k, v| v }.join(', ') }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -393,6 +389,7 @@ class ProtocolsController < ApplicationController
|
|||
flash[:error] = draft.errors.full_messages.join(', ')
|
||||
redirect_to protocols_path
|
||||
else
|
||||
log_activity(:protocol_template_draft_created, nil, protocol: @protocol.id)
|
||||
redirect_to protocol_path(draft)
|
||||
end
|
||||
rescue StandardError => e
|
||||
|
@ -410,7 +407,7 @@ class ProtocolsController < ApplicationController
|
|||
Protocol.transaction do
|
||||
begin
|
||||
@protocol.unlink
|
||||
rescue Exception
|
||||
rescue StandardError
|
||||
transaction_error = true
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
@ -477,47 +474,6 @@ class ProtocolsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def update_parent
|
||||
respond_to do |format|
|
||||
if @protocol.parent.can_destroy?
|
||||
transaction_error = false
|
||||
Protocol.transaction do
|
||||
@protocol.update_parent(current_user)
|
||||
rescue StandardError
|
||||
transaction_error = true
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
if transaction_error
|
||||
# Bad request error
|
||||
format.json do
|
||||
render json: {
|
||||
message: t('my_modules.protocols.update_parent_error')
|
||||
},
|
||||
status: :bad_request
|
||||
end
|
||||
else
|
||||
# Everything good, record activity, display flash & render 200
|
||||
log_activity(:update_protocol_in_repository_from_task,
|
||||
@protocol.my_module.experiment.project,
|
||||
my_module: @protocol.my_module.id,
|
||||
protocol_repository: @protocol.parent.id)
|
||||
flash[:success] = t(
|
||||
'my_modules.protocols.update_parent_flash'
|
||||
)
|
||||
flash.keep(:success)
|
||||
format.json { render json: {}, status: :ok }
|
||||
end
|
||||
else
|
||||
format.json do
|
||||
render json: {
|
||||
message: t('my_modules.protocols.update_parent_error_locked')
|
||||
}, status: :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_from_parent
|
||||
protocol_can_destroy = @protocol.can_destroy?
|
||||
respond_to do |format|
|
||||
|
@ -530,7 +486,7 @@ class ProtocolsController < ApplicationController
|
|||
else
|
||||
@protocol.parent.parent
|
||||
end
|
||||
@protocol.update_from_parent(current_user, source_parent.latest_published_version)
|
||||
@protocol.update_from_parent(current_user, source_parent.latest_published_version_or_self)
|
||||
rescue StandardError
|
||||
transaction_error = true
|
||||
raise ActiveRecord::Rollback
|
||||
|
@ -769,9 +725,9 @@ class ProtocolsController < ApplicationController
|
|||
Protocol.transaction do
|
||||
begin
|
||||
protocol = @importer.import_new_protocol(@db_json)
|
||||
rescue Exception
|
||||
rescue StandardError
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
p_name =
|
||||
|
@ -813,7 +769,7 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
# Create folder and xml file for each protocol and populate it
|
||||
@protocols.each do |protocol|
|
||||
protocol = protocol.latest_published_version || protocol
|
||||
protocol = protocol.latest_published_version_or_self
|
||||
protocol_dir = get_guid(protocol.id).to_s
|
||||
ostream.put_next_entry("#{protocol_dir}/eln.xml")
|
||||
ostream.print(generate_protocol_xml(protocol))
|
||||
|
@ -912,19 +868,6 @@ class ProtocolsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def update_parent_modal
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
title: t('my_modules.protocols.confirm_link_update_modal.update_parent_title'),
|
||||
message: t('my_modules.protocols.confirm_link_update_modal.update_parent_message'),
|
||||
btn_text: t('general.update'),
|
||||
url: update_parent_protocol_path(@protocol)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_from_parent_modal
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
|
@ -1107,10 +1050,12 @@ class ProtocolsController < ApplicationController
|
|||
begin
|
||||
Protocol.transaction do
|
||||
@protocols.find_each do |protocol|
|
||||
protocol = protocol.parent if protocol.parent_id
|
||||
protocol.method(action).call(current_user)
|
||||
end
|
||||
end
|
||||
rescue
|
||||
rescue StandardError => e
|
||||
Rails.logger.error e.message
|
||||
rollbacked = true
|
||||
end
|
||||
|
||||
|
@ -1173,7 +1118,7 @@ class ProtocolsController < ApplicationController
|
|||
def check_clone_permissions
|
||||
load_team_and_type
|
||||
protocol = Protocol.find_by(id: params[:ids][0])
|
||||
@original = protocol.latest_published_version || protocol
|
||||
@original = protocol.latest_published_version_or_self
|
||||
|
||||
if @original.blank? ||
|
||||
!can_clone_protocol_in_repository?(@original) || @type == :archive
|
||||
|
@ -1200,13 +1145,6 @@ class ProtocolsController < ApplicationController
|
|||
can_delete_protocol_draft_in_repository?(@protocol)
|
||||
end
|
||||
|
||||
def check_manage_parent_in_repository_permissions
|
||||
@protocol = Protocol.find_by_id(params[:id])
|
||||
render_403 unless @protocol.present? &&
|
||||
can_read_protocol_in_module?(@protocol) &&
|
||||
can_manage_protocol_in_repository?(@protocol.parent)
|
||||
end
|
||||
|
||||
def check_manage_all_in_repository_permissions
|
||||
@protocols = Protocol.where(id: params[:protocol_ids])
|
||||
@protocols.find_each do |protocol|
|
||||
|
@ -1251,7 +1189,7 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
def check_load_from_repository_permissions
|
||||
@protocol = Protocol.find_by(id: params[:id])
|
||||
@source = Protocol.find_by(id: params[:source_id])&.latest_published_version
|
||||
@source = Protocol.find_by(id: params[:source_id])&.latest_published_version_or_self
|
||||
|
||||
render_403 unless @protocol.present? && @source.present? &&
|
||||
(can_manage_protocol_in_module?(@protocol) &&
|
||||
|
|
|
@ -193,9 +193,9 @@ module Users
|
|||
Protocol.where(id: p_ids).find_each do |protocol|
|
||||
protocol.record_timestamps = false
|
||||
protocol.added_by = new_owner
|
||||
protocol.archived_by = new_owner unless protocol.archived_by.nil?
|
||||
protocol.restored_by = new_owner unless protocol.restored_by.nil?
|
||||
protocol.save!
|
||||
protocol.archived_by = new_owner if protocol.archived_by == user_assignment.user
|
||||
protocol.restored_by = new_owner if protocol.restored_by == user_assignment.user
|
||||
protocol.save!(validate: false)
|
||||
protocol.user_assignments.find_by(user: new_owner)&.destroy!
|
||||
protocol.user_assignments.create!(user: new_owner, user_role: UserRole.find_predefined_owner_role)
|
||||
end
|
||||
|
|
|
@ -3,6 +3,8 @@ class LoadFromRepositoryProtocolsDatatable < CustomDatatable
|
|||
include ActiveRecord::Sanitization::ClassMethods
|
||||
include InputSanitizeHelper
|
||||
|
||||
PREFIXED_ID_SQL = "('#{Protocol::ID_PREFIX}' || COALESCE(\"protocols\".\"parent_id\", \"protocols\".\"id\"))".freeze
|
||||
|
||||
def initialize(view, team, user)
|
||||
super(view)
|
||||
@team = team
|
||||
|
@ -12,8 +14,8 @@ class LoadFromRepositoryProtocolsDatatable < CustomDatatable
|
|||
def sortable_columns
|
||||
@sortable_columns ||= [
|
||||
'Protocol.name',
|
||||
'nr_of_versions',
|
||||
'Protocol.id',
|
||||
'Protocol.version_number',
|
||||
'adjusted_parent_id',
|
||||
'protocol_keywords_str',
|
||||
'full_username_str',
|
||||
'Protocol.published_on'
|
||||
|
@ -23,20 +25,18 @@ class LoadFromRepositoryProtocolsDatatable < CustomDatatable
|
|||
def searchable_columns
|
||||
@searchable_columns ||= [
|
||||
'Protocol.name',
|
||||
'Protocol.id',
|
||||
'Protocol.published_on'
|
||||
"Protocol.#{PREFIXED_ID_SQL}",
|
||||
'Protocol.published_on',
|
||||
'Protocol.version_number',
|
||||
'ProtocolKeyword.name'
|
||||
]
|
||||
end
|
||||
|
||||
# This hack is needed to display a correct amount of
|
||||
# searched entries (needed for pagination).
|
||||
# This is needed because of usage of GROUP operator in SQL.
|
||||
# See https://github.com/antillas21/ajax-datatables-rails/issues/112
|
||||
def as_json(options = {})
|
||||
def as_json(_options = {})
|
||||
{
|
||||
draw: dt_params[:draw].to_i,
|
||||
recordsTotal: get_raw_records.length,
|
||||
recordsFiltered: filter_records(get_raw_records).length,
|
||||
recordsTotal: get_raw_records_base.distinct.count,
|
||||
recordsFiltered: records.present? ? records.first.filtered_count : 0,
|
||||
data: data
|
||||
}
|
||||
end
|
||||
|
@ -46,33 +46,55 @@ class LoadFromRepositoryProtocolsDatatable < CustomDatatable
|
|||
# Returns json of current protocols (already paginated)
|
||||
def data
|
||||
records.map do |record|
|
||||
parent = record.parent || record
|
||||
{
|
||||
'DT_RowId': record.id,
|
||||
DT_RowId: parent.id,
|
||||
'0': escape_input(record.name),
|
||||
'1': record.nr_of_versions,
|
||||
'2': record.code,
|
||||
'1': record.version_number,
|
||||
'2': parent.code,
|
||||
'3': keywords_html(record),
|
||||
'4': escape_input(record.full_username_str),
|
||||
'4': escape_input(record.published_by.full_name),
|
||||
'5': I18n.l(record.published_on, format: :full)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def get_raw_records_base
|
||||
records =
|
||||
Protocol
|
||||
.where(team: @team)
|
||||
.where(protocols: { protocol_type: Protocol.protocol_types[:in_repository_published_original] })
|
||||
.joins("LEFT OUTER JOIN protocols protocol_versions "\
|
||||
"ON protocol_versions.protocol_type = #{Protocol.protocol_types[:in_repository_published_version]} "\
|
||||
"AND protocol_versions.parent_id = protocols.id")
|
||||
.joins('LEFT OUTER JOIN "protocol_protocol_keywords" '\
|
||||
'ON "protocol_protocol_keywords"."protocol_id" = "protocols"."id"')
|
||||
.joins('LEFT OUTER JOIN "protocol_keywords"'\
|
||||
'ON "protocol_protocol_keywords"."protocol_keyword_id" = "protocol_keywords"."id"')
|
||||
.joins('LEFT OUTER JOIN users ON users.id = protocols.published_by_id').active
|
||||
def new_search_condition(column, value)
|
||||
model, column = column.split('.', 2)
|
||||
model = model.constantize
|
||||
|
||||
records.group('"protocols"."id"')
|
||||
casted_column = case column
|
||||
when PREFIXED_ID_SQL
|
||||
::Arel::Nodes::SqlLiteral.new(PREFIXED_ID_SQL)
|
||||
when 'published_on'
|
||||
::Arel::Nodes::NamedFunction.new(
|
||||
'CAST', [Arel.sql("to_char( protocols.published_on, '#{formated_date}' ) AS VARCHAR")]
|
||||
)
|
||||
else
|
||||
::Arel::Nodes::NamedFunction.new('CAST', [model.arel_table[column.to_sym].as(typecast)])
|
||||
end
|
||||
casted_column.matches("%#{ActiveRecord::Base.sanitize_sql_like(value)}%")
|
||||
end
|
||||
|
||||
def fetch_records
|
||||
super.select('COUNT("protocols"."id") OVER() AS filtered_count')
|
||||
end
|
||||
|
||||
def get_raw_records_base
|
||||
original_without_versions = @team.protocols
|
||||
.left_outer_joins(:published_versions)
|
||||
.where(protocol_type: Protocol.protocol_types[:in_repository_published_original])
|
||||
.where(published_versions: { id: nil })
|
||||
.select(:id)
|
||||
|
||||
published_versions = @team.protocols
|
||||
.where(protocol_type: Protocol.protocol_types[:in_repository_published_version])
|
||||
.order('parent_id, version_number DESC')
|
||||
.select('DISTINCT ON (parent_id) id')
|
||||
|
||||
Protocol.where("protocols.id IN ((#{original_without_versions.to_sql}) UNION (#{published_versions.to_sql}))")
|
||||
.active
|
||||
.with_granted_permissions(@user, ProtocolPermissions::READ)
|
||||
end
|
||||
|
||||
# OVERRIDE - query database for records (this will be
|
||||
|
@ -80,10 +102,17 @@ class LoadFromRepositoryProtocolsDatatable < CustomDatatable
|
|||
# will return json
|
||||
def get_raw_records
|
||||
get_raw_records_base
|
||||
.preload(:parent, :protocol_keywords, user_assignments: %i(user user_role))
|
||||
.joins('LEFT OUTER JOIN "protocol_protocol_keywords" ' \
|
||||
'ON "protocol_protocol_keywords"."protocol_id" = "protocols"."id"')
|
||||
.joins('LEFT OUTER JOIN "protocol_keywords" ' \
|
||||
'ON "protocol_protocol_keywords"."protocol_keyword_id" = "protocol_keywords"."id"')
|
||||
.joins('LEFT OUTER JOIN "users" ON "users"."id" = "protocols"."published_by_id"')
|
||||
.group('"protocols"."id"')
|
||||
.select(
|
||||
'"protocols".*',
|
||||
'STRING_AGG("protocol_keywords"."name", \', \') AS "protocol_keywords_str"',
|
||||
'COUNT("protocol_versions"."id") + 1 AS "nr_of_versions"',
|
||||
'COALESCE("protocols"."parent_id", "protocols"."id") AS adjusted_parent_id',
|
||||
'STRING_AGG(DISTINCT("protocol_keywords"."name"), \', \') AS "protocol_keywords_str"',
|
||||
'MAX("users"."full_name") AS "full_username_str"'
|
||||
)
|
||||
end
|
||||
|
@ -91,59 +120,15 @@ class LoadFromRepositoryProtocolsDatatable < CustomDatatable
|
|||
# Various helper methods
|
||||
|
||||
def keywords_html(record)
|
||||
if record.protocol_keywords_str.blank?
|
||||
if record.protocol_keywords.blank?
|
||||
"<i>#{I18n.t('protocols.no_keywords')}</i>"
|
||||
else
|
||||
kws = record.protocol_keywords_str.split(', ')
|
||||
res = []
|
||||
kws.sort_by(&:downcase).each do |kw|
|
||||
res << "<a href='#' data-action='filter' data-param='#{kw}'>#{kw}</a>"
|
||||
record.protocol_keywords.sort_by { |kw| kw.name.downcase }.each do |kw|
|
||||
sanitized_kw = sanitize_input(kw.name)
|
||||
res << "<a href='#' data-action='filter' data-param='#{sanitized_kw}'>#{sanitized_kw}</a>"
|
||||
end
|
||||
sanitize_input(res.join(', '))
|
||||
res.join(', ')
|
||||
end
|
||||
end
|
||||
|
||||
def timestamp_column_html(record)
|
||||
if @type == :public
|
||||
I18n.l(record.published_on, format: :full)
|
||||
else
|
||||
I18n.l(record.created_at, format: :full)
|
||||
end
|
||||
end
|
||||
|
||||
# OVERRIDE - This is only called when filtering results;
|
||||
# when using GROUP BY function, SQL cannot perform a WHERE
|
||||
# clause on aggregated columns (protocol keywords & users' full_name), but
|
||||
# since we want those 2 columns to be searchable/filterable, we do an "inner"
|
||||
# query where we select only protocol IDs which are filtered by those 2 columns
|
||||
# using HAVING keyword (which is the correct way to filter aggregated columns).
|
||||
# Another OR is then appended to the WHERE clause, checking if protocol is inside
|
||||
# this list of IDs.
|
||||
def build_conditions_for(query)
|
||||
# Inner query to retrieve list of protocol IDs where concatenated
|
||||
# protocol keywords string, or user's full_name contains searched query
|
||||
search_val = dt_params[:search][:value]
|
||||
records_having = get_raw_records_base.having(
|
||||
::Arel::Nodes::NamedFunction.new(
|
||||
'CAST',
|
||||
[::Arel::Nodes::SqlLiteral.new("string_agg(\"protocol_keywords\".\"name\", ' ') AS #{typecast}")]
|
||||
).matches("%#{sanitize_sql_like(search_val)}%").to_sql +
|
||||
' OR ' +
|
||||
::Arel::Nodes::NamedFunction.new(
|
||||
'CAST',
|
||||
[::Arel::Nodes::SqlLiteral.new("max(\"users\".\"full_name\") AS #{typecast}")]
|
||||
).matches("%#{sanitize_sql_like(search_val)}%").to_sql +
|
||||
' OR ' +
|
||||
::Arel::Nodes::NamedFunction.new(
|
||||
'CAST',
|
||||
[::Arel::Nodes::SqlLiteral.new("COUNT(\"protocol_versions\".\"id\") + 1 AS #{typecast}")]
|
||||
).matches("%#{sanitize_sql_like(search_val)}%").to_sql
|
||||
).select(:id)
|
||||
|
||||
# Call parent function
|
||||
criteria = super(query)
|
||||
|
||||
# Aight, now append another or
|
||||
criteria.or(Protocol.arel_table[:id].in(records_having.arel))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,7 +30,7 @@ class ProtocolsDatatable < CustomDatatable
|
|||
'adjusted_parent_id',
|
||||
'nr_of_versions',
|
||||
'protocol_keywords_str',
|
||||
'nr_of_linked_children',
|
||||
'nr_of_linked_tasks',
|
||||
'nr_of_assigned_users',
|
||||
'full_username_str',
|
||||
'published_on',
|
||||
|
@ -54,8 +54,8 @@ class ProtocolsDatatable < CustomDatatable
|
|||
def as_json(_options = {})
|
||||
{
|
||||
draw: dt_params[:draw].to_i,
|
||||
recordsTotal: get_raw_records.length,
|
||||
recordsFiltered: filter_records(get_raw_records).length,
|
||||
recordsTotal: get_raw_records_base.distinct.count,
|
||||
recordsFiltered: records.present? ? records.first.filtered_count : 0,
|
||||
data: data
|
||||
}
|
||||
end
|
||||
|
@ -79,7 +79,7 @@ class ProtocolsDatatable < CustomDatatable
|
|||
casted_column = ::Arel::Nodes::NamedFunction.new('CAST',
|
||||
[model.arel_table[column.to_sym].as(typecast)])
|
||||
end
|
||||
casted_column.matches("%#{value}%")
|
||||
casted_column.matches("%#{ActiveRecord::Base.sanitize_sql_like(value)}%")
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -91,7 +91,8 @@ class ProtocolsDatatable < CustomDatatable
|
|||
{
|
||||
DT_RowId: record.id,
|
||||
DT_RowAttr: {
|
||||
'data-permissions-url': permissions_protocol_path(parent)
|
||||
'data-permissions-url': permissions_protocol_path(parent),
|
||||
'data-versions-url': versions_modal_protocol_path(parent)
|
||||
},
|
||||
'1': name_html(parent),
|
||||
'2': parent.code,
|
||||
|
@ -108,6 +109,10 @@ class ProtocolsDatatable < CustomDatatable
|
|||
end
|
||||
end
|
||||
|
||||
def fetch_records
|
||||
super.select('COUNT("protocols"."id") OVER() AS filtered_count')
|
||||
end
|
||||
|
||||
def filter_protocols_records(records)
|
||||
if params[:name_and_keywords].present?
|
||||
records = records.where_attributes_like(['protocols.name', 'protocol_keywords.name'], params[:name_and_keywords])
|
||||
|
@ -120,7 +125,13 @@ class ProtocolsDatatable < CustomDatatable
|
|||
records = records.where('protocols.updated_at > ?', params[:modified_on_from]) if params[:modified_on_from].present?
|
||||
records = records.where('protocols.updated_at < ?', params[:modified_on_to]) if params[:modified_on_to].present?
|
||||
records = records.where(protocols: { published_by_id: params[:published_by] }) if params[:published_by].present?
|
||||
records = records.where(all_user_assignments: { user_id: params[:members] }) if params[:members].present?
|
||||
|
||||
if params[:members].present?
|
||||
records = records.joins('LEFT OUTER JOIN "user_assignments" "all_user_assignments" '\
|
||||
'ON "all_user_assignments"."assignable_type" = \'Protocol\' '\
|
||||
'AND "all_user_assignments"."assignable_id" = "protocols"."id"')
|
||||
.where(all_user_assignments: { user_id: params[:members] })
|
||||
end
|
||||
|
||||
if params[:archived_on_from].present?
|
||||
records = records.where('protocols.archived_on > ?', params[:archived_on_from])
|
||||
|
@ -144,6 +155,7 @@ class ProtocolsDatatable < CustomDatatable
|
|||
.select(:id)
|
||||
published_versions = @team.protocols
|
||||
.where(protocol_type: Protocol.protocol_types[:in_repository_published_version])
|
||||
.order(:parent_id, version_number: :desc)
|
||||
.select('DISTINCT ON (parent_id) id')
|
||||
new_drafts = @team.protocols
|
||||
.where(protocol_type: Protocol.protocol_types[:in_repository_draft], parent_id: nil)
|
||||
|
@ -157,38 +169,49 @@ class ProtocolsDatatable < CustomDatatable
|
|||
"(#{new_drafts.to_sql}))"
|
||||
)
|
||||
|
||||
records =
|
||||
records
|
||||
.preload(:parent, :latest_published_version, :draft, :protocol_keywords, user_assignments: %i(user user_role))
|
||||
.joins("LEFT OUTER JOIN protocols protocol_versions " \
|
||||
"ON protocol_versions.protocol_type = #{Protocol.protocol_types[:in_repository_published_version]} " \
|
||||
"AND protocol_versions.parent_id = protocols.parent_id")
|
||||
.joins('LEFT OUTER JOIN "protocol_protocol_keywords" ' \
|
||||
'ON "protocol_protocol_keywords"."protocol_id" = "protocols"."id"')
|
||||
.joins('LEFT OUTER JOIN "protocol_keywords" ' \
|
||||
'ON "protocol_protocol_keywords"."protocol_keyword_id" = "protocol_keywords"."id"')
|
||||
.with_granted_permissions(@user, ProtocolPermissions::READ)
|
||||
|
||||
records = records.joins('LEFT OUTER JOIN "users" "archived_users"
|
||||
ON "archived_users"."id" = "protocols"."archived_by_id"')
|
||||
records = records.joins('LEFT OUTER JOIN "users" ON "users"."id" = "protocols"."published_by_id"')
|
||||
|
||||
records = @type == :archived ? records.archived : records.active
|
||||
|
||||
records = filter_protocols_records(records)
|
||||
records.group('"protocols"."id"')
|
||||
records.with_granted_permissions(@user, ProtocolPermissions::READ)
|
||||
end
|
||||
|
||||
# Query database for records (this will be later paginated and filtered)
|
||||
# after that "data" function will return json
|
||||
def get_raw_records
|
||||
get_raw_records_base.select(
|
||||
records =
|
||||
get_raw_records_base
|
||||
.preload(:parent, :latest_published_version, :draft, :protocol_keywords, user_assignments: %i(user user_role))
|
||||
.joins("LEFT OUTER JOIN protocols protocol_versions " \
|
||||
"ON protocol_versions.protocol_type = #{Protocol.protocol_types[:in_repository_published_version]} " \
|
||||
"AND protocol_versions.parent_id = protocols.parent_id")
|
||||
.joins("LEFT OUTER JOIN protocols self_linked_task_protocols " \
|
||||
"ON self_linked_task_protocols.protocol_type = #{Protocol.protocol_types[:linked]} " \
|
||||
"AND self_linked_task_protocols.parent_id = protocols.id")
|
||||
.joins("LEFT OUTER JOIN protocols parent_linked_task_protocols " \
|
||||
"ON parent_linked_task_protocols.protocol_type = #{Protocol.protocol_types[:linked]} " \
|
||||
"AND parent_linked_task_protocols.parent_id = protocols.parent_id")
|
||||
.joins("LEFT OUTER JOIN protocols version_linked_task_protocols " \
|
||||
"ON version_linked_task_protocols.protocol_type = #{Protocol.protocol_types[:linked]} " \
|
||||
"AND version_linked_task_protocols.parent_id = protocol_versions.id " \
|
||||
"AND version_linked_task_protocols.parent_id != protocols.id")
|
||||
.joins('LEFT OUTER JOIN "protocol_protocol_keywords" ' \
|
||||
'ON "protocol_protocol_keywords"."protocol_id" = "protocols"."id"')
|
||||
.joins('LEFT OUTER JOIN "protocol_keywords" ' \
|
||||
'ON "protocol_protocol_keywords"."protocol_keyword_id" = "protocol_keywords"."id"')
|
||||
.joins('LEFT OUTER JOIN "users" "archived_users" ON "archived_users"."id" = "protocols"."archived_by_id"')
|
||||
.joins('LEFT OUTER JOIN "users" ON "users"."id" = "protocols"."published_by_id"')
|
||||
.group('"protocols"."id"')
|
||||
|
||||
records = filter_protocols_records(records)
|
||||
records.select(
|
||||
'"protocols".*',
|
||||
'COALESCE("protocols"."parent_id", "protocols"."id") AS adjusted_parent_id',
|
||||
'STRING_AGG(DISTINCT("protocol_keywords"."name"), \', \') AS "protocol_keywords_str"',
|
||||
"CASE WHEN protocols.protocol_type = #{Protocol.protocol_types[:in_repository_draft]}" \
|
||||
"THEN COUNT(DISTINCT(\"protocol_versions\".\"id\")) ELSE COUNT(DISTINCT(\"protocol_versions\".\"id\")) + 1 " \
|
||||
"CASE WHEN protocols.protocol_type = #{Protocol.protocol_types[:in_repository_draft]} " \
|
||||
"THEN 0 ELSE COUNT(DISTINCT(\"protocol_versions\".\"id\")) + 1 " \
|
||||
"END AS nr_of_versions",
|
||||
'(COUNT(DISTINCT("self_linked_task_protocols"."id")) + ' \
|
||||
'COUNT(DISTINCT("parent_linked_task_protocols"."id")) + ' \
|
||||
'COUNT(DISTINCT("version_linked_task_protocols"."id"))) AS nr_of_linked_tasks',
|
||||
'COUNT("user_assignments"."id") AS "nr_of_assigned_users"',
|
||||
'MAX("users"."full_name") AS "full_username_str"', # "Hack" to get single username
|
||||
'MAX("archived_users"."full_name") AS "archived_full_username_str"'
|
||||
|
@ -222,8 +245,8 @@ class ProtocolsDatatable < CustomDatatable
|
|||
|
||||
def modules_html(record)
|
||||
"<a href='#' data-action='load-linked-children'" \
|
||||
"data-url='#{linked_children_protocol_path(record)}'>" \
|
||||
"#{record.nr_of_linked_children}" \
|
||||
"data-url='#{linked_children_protocol_path(record.parent || record)}'>" \
|
||||
"#{record.nr_of_linked_tasks}" \
|
||||
"</a>"
|
||||
end
|
||||
|
||||
|
@ -251,36 +274,4 @@ class ProtocolsDatatable < CustomDatatable
|
|||
def modified_timestamp(record)
|
||||
I18n.l(record.updated_at, format: :full)
|
||||
end
|
||||
|
||||
# OVERRIDE - This is only called when filtering results;
|
||||
# when using GROUP BY function, SQL cannot perform a WHERE
|
||||
# clause on aggregated columns (protocol keywords & users' full_name), but
|
||||
# since we want those 2 columns to be searchable/filterable, we do an "inner"
|
||||
# query where we select only protocol IDs which are filtered by those 2 columns
|
||||
# using HAVING keyword (which is the correct way to filter aggregated columns).
|
||||
# Another OR is then appended to the WHERE clause, checking if protocol is inside
|
||||
# this list of IDs.
|
||||
# def build_conditions_for(query)
|
||||
# # Inner query to retrieve list of protocol IDs where concatenated
|
||||
# # protocol keywords string, or user's full_name contains searched query
|
||||
# search_val = dt_params[:search][:value]
|
||||
# records_having = get_raw_records_base.having(
|
||||
# ::Arel::Nodes::NamedFunction.new(
|
||||
# 'CAST',
|
||||
# [::Arel::Nodes::SqlLiteral.new("string_agg(\"protocol_keywords\".\"name\", ' ') AS #{typecast}")]
|
||||
# ).matches("%#{sanitize_sql_like(search_val)}%").to_sql +
|
||||
# " OR " +
|
||||
# ::Arel::Nodes::NamedFunction.new(
|
||||
# 'CAST',
|
||||
# [::Arel::Nodes::SqlLiteral.new("max(\"users\".\"full_name\") AS #{typecast}")]
|
||||
# ).matches("%#{sanitize_sql_like(search_val)}%").to_sql
|
||||
# ).select(:id)
|
||||
|
||||
# # Call parent function
|
||||
# criteria = super(query)
|
||||
|
||||
# # Aight, now append another or
|
||||
# criteria = criteria.or(Protocol.arel_table[:id].in(records_having.arel))
|
||||
# criteria
|
||||
# end
|
||||
end
|
||||
|
|
|
@ -158,7 +158,7 @@
|
|||
<PublishProtocol v-if="publishing"
|
||||
:protocol="protocol"
|
||||
@publish="publishProtocol"
|
||||
@close="closePublishModal"
|
||||
@cancel="closePublishModal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
</a>
|
||||
</div>
|
||||
<div class="actions-block">
|
||||
<a class="btn btn-light icon-btn pull-right" data-toggle="modal" data-target="#print-protocol-modal" tabindex="0">
|
||||
<a class="btn btn-light icon-btn pull-right" :href="protocol.attributes.urls.print_protocol_url" target="_blank">
|
||||
<span class="fas fa-print" aria-hidden="true"></span>
|
||||
</a>
|
||||
<button class="btn btn-light" @click="openVersionsModal">{{ i18n.t("protocols.header.versions") }}</button>
|
||||
<button v-if="!protocol.attributes.published" @click="$emit('publish')" class="btn btn-primary">{{ i18n.t("protocols.header.publish") }}</button>
|
||||
<button v-if="protocol.attributes.urls.publish_url" @click="$emit('publish')" class="btn btn-primary">{{ i18n.t("protocols.header.publish") }}</button>
|
||||
<button v-if="protocol.attributes.urls.save_as_draft_url" @click="saveAsdraft" class="btn btn-secondary">{{ i18n.t("protocols.header.save_as_draft") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -46,6 +46,16 @@ module UserAssignments
|
|||
end
|
||||
|
||||
def assign_users_to_protocol(protocol)
|
||||
if protocol.parent_id && protocol.in_repository_draft?
|
||||
Protocol.transaction(requires_new: true) do
|
||||
protocol.parent.user_assignments.find_each do |user_assignment|
|
||||
protocol.parent.sync_child_protocol_user_assignment(user_assignment, protocol.id)
|
||||
end
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
return unless protocol.visible?
|
||||
|
||||
protocol.create_public_user_assignments!(@assigned_by)
|
||||
|
|
|
@ -60,6 +60,11 @@ module Assignable
|
|||
|
||||
private
|
||||
|
||||
def after_user_assignment_changed(user_assignment = nil)
|
||||
# Optional, redefine in the assignable model.
|
||||
# Will be called when an assignment is changed (save/destroy) for the assignable model.
|
||||
end
|
||||
|
||||
def create_users_assignments
|
||||
return if skip_user_assignments
|
||||
|
||||
|
|
|
@ -404,14 +404,6 @@ class MyModule < ApplicationRecord
|
|||
clone_tinymce_assets(target_my_module, target_my_module.experiment.project.team)
|
||||
target_my_module.protocols << protocol.deep_clone_my_module(self, current_user)
|
||||
target_my_module.reload
|
||||
|
||||
# fixes linked protocols
|
||||
target_my_module.protocols.each do |protocol|
|
||||
next unless protocol.linked?
|
||||
|
||||
protocol.updated_at = protocol.parent_updated_at
|
||||
protocol.save
|
||||
end
|
||||
end
|
||||
|
||||
# Find an empty position for the restored module. It's
|
||||
|
|
|
@ -54,23 +54,22 @@ class Protocol < ApplicationRecord
|
|||
validate :linked_parent_type_constrain
|
||||
validates :added_by, presence: true
|
||||
validates :parent, presence: true
|
||||
validates :parent_updated_at, presence: true
|
||||
end
|
||||
with_options if: :in_repository? do
|
||||
validates :name, presence: true
|
||||
validates :added_by, presence: true
|
||||
validates :my_module, absence: true
|
||||
validates :parent_updated_at, absence: true
|
||||
validate :version_number_constraint
|
||||
end
|
||||
with_options if: :in_repository_published_version? do
|
||||
validates :parent, presence: true
|
||||
validate :versions_same_name_constrain
|
||||
validate :parent_type_constraint
|
||||
validate :versions_same_name_constraint
|
||||
end
|
||||
with_options if: :in_repository_draft? do
|
||||
# Only one draft can exist for each protocol
|
||||
validate :ensure_single_draft
|
||||
validate :versions_same_name_constrain
|
||||
validate :versions_same_name_constraint
|
||||
end
|
||||
with_options if: -> { in_repository? && !parent } do |protocol|
|
||||
# Active protocol must have unique name inside its team
|
||||
|
@ -123,6 +122,7 @@ class Protocol < ApplicationRecord
|
|||
class_name: 'User',
|
||||
inverse_of: :published_protocols, optional: true
|
||||
has_many :linked_children,
|
||||
-> { linked },
|
||||
class_name: 'Protocol',
|
||||
foreign_key: 'parent_id'
|
||||
has_one :next_version,
|
||||
|
@ -222,11 +222,8 @@ class Protocol < ApplicationRecord
|
|||
end
|
||||
|
||||
def self.viewable_by_user(user, teams)
|
||||
where(my_module: MyModule.viewable_by_user(user, teams))
|
||||
.or(where(team: teams)
|
||||
.where('protocol_type = 3 OR '\
|
||||
'(protocol_type = 2 AND added_by_id = :user_id)',
|
||||
user_id: user.id))
|
||||
where(team: teams, protocol_type: REPOSITORY_TYPES).with_granted_permissions(user, ProtocolPermissions::READ)
|
||||
.or(where(my_module: MyModule.viewable_by_user(user, teams)))
|
||||
end
|
||||
|
||||
def self.filter_by_teams(teams = [])
|
||||
|
@ -260,6 +257,20 @@ class Protocol < ApplicationRecord
|
|||
in_repository_draft? && parent.blank?
|
||||
end
|
||||
|
||||
def newer_published_version_present?
|
||||
if in_repository_published_original?
|
||||
published_versions.any?
|
||||
elsif in_repository_published_version?
|
||||
parent.published_versions.where('version_number > ?', version_number).any?
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def latest_published_version_or_self
|
||||
latest_published_version || self
|
||||
end
|
||||
|
||||
def permission_parent
|
||||
in_module? ? my_module : team
|
||||
end
|
||||
|
@ -336,27 +347,12 @@ class Protocol < ApplicationRecord
|
|||
unlinked? || linked?
|
||||
end
|
||||
|
||||
def linked_no_diff?
|
||||
linked? &&
|
||||
updated_at == parent_updated_at &&
|
||||
parent.updated_at == parent_updated_at
|
||||
end
|
||||
|
||||
def newer_than_parent?
|
||||
linked? && parent.updated_at == parent_updated_at &&
|
||||
updated_at > parent_updated_at
|
||||
linked? && updated_at > parent.published_on
|
||||
end
|
||||
|
||||
def parent_newer?
|
||||
linked? &&
|
||||
updated_at == parent_updated_at &&
|
||||
parent.updated_at > parent_updated_at
|
||||
end
|
||||
|
||||
def parent_and_self_newer?
|
||||
linked? &&
|
||||
parent.updated_at > parent_updated_at &&
|
||||
updated_at > parent_updated_at
|
||||
linked? && parent.newer_published_version_present?
|
||||
end
|
||||
|
||||
def number_of_steps
|
||||
|
@ -380,9 +376,6 @@ class Protocol < ApplicationRecord
|
|||
end
|
||||
|
||||
def make_private(user)
|
||||
# Don't update "updated_at" timestamp
|
||||
self.record_timestamps = false
|
||||
|
||||
self.added_by = user
|
||||
self.published_on = nil
|
||||
self.archived_by = nil
|
||||
|
@ -483,8 +476,6 @@ class Protocol < ApplicationRecord
|
|||
result = true
|
||||
begin
|
||||
Protocol.transaction do
|
||||
self.record_timestamps = false
|
||||
|
||||
# First, destroy all keywords
|
||||
protocol_protocol_keywords.destroy_all
|
||||
if keywords.present?
|
||||
|
@ -503,31 +494,11 @@ class Protocol < ApplicationRecord
|
|||
|
||||
def unlink
|
||||
self.parent = nil
|
||||
self.parent_updated_at = nil
|
||||
self.linked_at = nil
|
||||
self.protocol_type = Protocol.protocol_types[:unlinked]
|
||||
save!
|
||||
end
|
||||
|
||||
def update_parent(current_user)
|
||||
ActiveRecord::Base.no_touching do
|
||||
# First, destroy parent's step contents
|
||||
parent.destroy_contents
|
||||
parent.reload
|
||||
|
||||
# Now, clone step contents
|
||||
Protocol.clone_contents(self, parent, current_user, false)
|
||||
end
|
||||
|
||||
# Lastly, update the metadata
|
||||
parent.reload
|
||||
parent.record_timestamps = false
|
||||
parent.updated_at = updated_at
|
||||
parent.save!
|
||||
self.record_timestamps = false
|
||||
self.parent_updated_at = updated_at
|
||||
save!
|
||||
end
|
||||
|
||||
def update_from_parent(current_user, source)
|
||||
ActiveRecord::Base.no_touching do
|
||||
# First, destroy step contents
|
||||
|
@ -541,7 +512,6 @@ class Protocol < ApplicationRecord
|
|||
reload
|
||||
self.record_timestamps = false
|
||||
self.updated_at = source.published_on
|
||||
self.parent_updated_at = source.published_on
|
||||
self.added_by = current_user
|
||||
self.last_modified_by = current_user
|
||||
self.parent = source
|
||||
|
@ -564,7 +534,6 @@ class Protocol < ApplicationRecord
|
|||
self.record_timestamps = false
|
||||
self.updated_at = source.published_on
|
||||
self.parent = source
|
||||
self.parent_updated_at = source.published_on
|
||||
self.added_by = current_user
|
||||
self.last_modified_by = current_user
|
||||
self.linked_at = Time.zone.now
|
||||
|
@ -622,7 +591,6 @@ class Protocol < ApplicationRecord
|
|||
if linked?
|
||||
clone.added_by = current_user
|
||||
clone.parent = parent
|
||||
clone.parent_updated_at = parent_updated_at
|
||||
end
|
||||
|
||||
deep_clone(clone, current_user)
|
||||
|
@ -691,8 +659,50 @@ class Protocol < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def child_version_protocols
|
||||
published_versions.or(Protocol.where(id: draft&.id))
|
||||
end
|
||||
|
||||
def sync_child_protocol_user_assignment(user_assignment, child_protocol_id = nil)
|
||||
# Copy user assignments to child protocol(s)
|
||||
|
||||
Protocol.transaction(requires_new: true) do
|
||||
# Reload to ensure a potential new draft is also included in child versions
|
||||
reload
|
||||
|
||||
(
|
||||
# all or single child version protocol
|
||||
child_protocol_id ? child_version_protocols.where(id: child_protocol_id) : child_version_protocols
|
||||
).find_each do |child_protocol|
|
||||
child_assignment = child_protocol.user_assignments.find_or_initialize_by(
|
||||
user: user_assignment.user
|
||||
)
|
||||
|
||||
if user_assignment.destroyed?
|
||||
child_assignment.destroy! if child_assignment.persisted?
|
||||
next
|
||||
end
|
||||
|
||||
child_assignment.update!(
|
||||
user_assignment.attributes.slice(
|
||||
'user_role_id',
|
||||
'assigned',
|
||||
'assigned_by_id',
|
||||
'team_id'
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def after_user_assignment_changed(user_assignment)
|
||||
return unless in_repository_published_original?
|
||||
|
||||
sync_child_protocol_user_assignment(user_assignment)
|
||||
end
|
||||
|
||||
def auto_assign_protocol_members
|
||||
UserAssignments::ProtocolGroupAssignmentJob.perform_now(
|
||||
team,
|
||||
|
@ -757,19 +767,13 @@ class Protocol < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def version_parent_type_constrain
|
||||
def parent_type_constraint
|
||||
unless parent.in_repository_published_original?
|
||||
errors.add(:base, I18n.t('activerecord.errors.models.protocol.wrong_parent_type'))
|
||||
end
|
||||
end
|
||||
|
||||
def draft_parent_type_constrain
|
||||
unless parent.in_repository_published_original?
|
||||
errors.add(:base, I18n.t('activerecord.errors.models.protocol.wrong_parent_type'))
|
||||
end
|
||||
end
|
||||
|
||||
def versions_same_name_constrain
|
||||
def versions_same_name_constraint
|
||||
if parent.present? && !parent.name.eql?(name)
|
||||
errors.add(:base, I18n.t('activerecord.errors.models.protocol.wrong_version_name'))
|
||||
end
|
||||
|
|
|
@ -7,6 +7,8 @@ class UserAssignment < ApplicationRecord
|
|||
after_create :assign_team_child_objects, if: -> { assignable.is_a?(Team) }
|
||||
after_update :update_team_children_assignments, if: -> { assignable.is_a?(Team) && saved_change_to_user_role_id? }
|
||||
before_destroy :unassign_team_child_objects, if: -> { assignable.is_a?(Team) }
|
||||
after_destroy :call_user_assignment_changed_hook
|
||||
after_save :call_user_assignment_changed_hook
|
||||
|
||||
belongs_to :assignable, polymorphic: true, touch: true
|
||||
belongs_to :user_role
|
||||
|
@ -24,6 +26,10 @@ class UserAssignment < ApplicationRecord
|
|||
|
||||
private
|
||||
|
||||
def call_user_assignment_changed_hook
|
||||
assignable.__send__(:after_user_assignment_changed, self)
|
||||
end
|
||||
|
||||
def assign_team_child_objects
|
||||
UserAssignments::CreateTeamUserAssignmentsService.new(self).call
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ class ProtocolSerializer < ActiveModel::Serializer
|
|||
|
||||
attributes :name, :id, :urls, :description, :description_view, :updated_at, :in_repository,
|
||||
:created_at_formatted, :updated_at_formatted, :added_by, :authors, :keywords, :version, :code,
|
||||
:published, :version_comment
|
||||
:published, :version_comment, :archived
|
||||
|
||||
def updated_at
|
||||
object.updated_at.to_i
|
||||
|
@ -75,7 +75,8 @@ class ProtocolSerializer < ActiveModel::Serializer
|
|||
delete_steps_url: delete_steps_url,
|
||||
publish_url: publish_url,
|
||||
save_as_draft_url: save_as_draft_url,
|
||||
versions_modal_url: versions_modal_url
|
||||
versions_modal_url: versions_modal_url,
|
||||
print_protocol_url: print_protocol_url
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -115,6 +116,12 @@ class ProtocolSerializer < ActiveModel::Serializer
|
|||
versions_modal_protocol_path(object.parent || object)
|
||||
end
|
||||
|
||||
def print_protocol_url
|
||||
return unless can_read_protocol_in_repository?(object)
|
||||
|
||||
print_protocol_path(object)
|
||||
end
|
||||
|
||||
def reorder_steps_url
|
||||
return unless can_manage_protocol_in_module?(object) || can_manage_protocol_in_repository?(object)
|
||||
|
||||
|
@ -140,8 +147,7 @@ class ProtocolSerializer < ActiveModel::Serializer
|
|||
end
|
||||
|
||||
def update_protocol_url
|
||||
return unless can_read_protocol_in_module?(object) && object.linked? &&
|
||||
(object.parent_newer? || object.parent_and_self_newer?)
|
||||
return unless can_read_protocol_in_module?(object) && object.linked? && object.parent_newer?
|
||||
|
||||
update_from_parent_modal_protocol_path(object, format: :json)
|
||||
end
|
||||
|
|
|
@ -27,7 +27,7 @@ module ProtocolsExporter
|
|||
envelope_xml = "<envelope xmlns=\"http://www.scinote.net\" " \
|
||||
"version=\"1.0\">\n"
|
||||
protocols.each do |protocol|
|
||||
protocol = protocol.latest_published_version || protocol
|
||||
protocol = protocol.latest_published_version_or_self
|
||||
protocol_name = get_protocol_name(protocol)
|
||||
envelope_xml << "<protocol id=\"#{protocol.id}\" " \
|
||||
"guid=\"#{get_guid(protocol.id)}\">#{protocol_name}" \
|
||||
|
|
|
@ -9,7 +9,7 @@ module ProtocolsExporterV2
|
|||
envelope_xml = "<envelope xmlns=\"http://www.scinote.net\" " \
|
||||
"version=\"1.1\">\n"
|
||||
protocols.each do |protocol|
|
||||
protocol = protocol.latest_published_version || protocol
|
||||
protocol = protocol.latest_published_version_or_self
|
||||
protocol_name = get_protocol_name(protocol)
|
||||
envelope_xml << "<protocol id=\"#{protocol.id}\" " \
|
||||
"guid=\"#{get_guid(protocol.id)}\">#{protocol_name}" \
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<% # frozen_string_literal: true %>
|
||||
|
||||
<div class="modal fade user-assignments-modal <%= assignable.class.name.parameterize(separator: '-') %>-assignments-modal" tabindex="-1" role="dialog" data-action="modal-close">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content" id="user_assignments_modal">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
|
@ -9,19 +9,19 @@
|
|||
</div>
|
||||
<div class="modal-body">
|
||||
<% manually_assigned_users.order(full_name: :asc).each do |user| %>
|
||||
<%= render('access_permissions/partials/member_field',
|
||||
user: user,
|
||||
assignable: assignable,
|
||||
update_path: public_send("access_permissions_#{assignable.class.name.underscore}_path", assignable),
|
||||
delete_path: public_send("access_permissions_#{assignable.class.name.underscore}_path", assignable, user_id: user.id)
|
||||
) %>
|
||||
<%
|
||||
options = { user: user, with_inherit: !assignable.top_level_assignable?, assignable: assignable, update_path: public_send("access_permissions_#{assignable.class.name.underscore}_path", assignable) }
|
||||
options.merge!(delete_path: public_send("access_permissions_#{assignable.class.name.underscore}_path", assignable, user_id: user.id)) if assignable == top_level_assignable
|
||||
%>
|
||||
|
||||
<%= render('access_permissions/partials/member_field', options) %>
|
||||
<% end %>
|
||||
<%= render('access_permissions/partials/default_public_user_role_form', assignable: top_level_assignable, editable: assignable == top_level_assignable) if top_level_assignable.respond_to?(:visible?) && top_level_assignable.visible? %>
|
||||
</div>
|
||||
|
||||
<% if assignable.top_level_assignable? %>
|
||||
<div class="modal-footer">
|
||||
<%= link_to new_assignment_path, class: 'btn btn-default pull-left', data: { action: 'swap-remote-container', target: '#user_assignments_modal' } do %>
|
||||
<%= link_to new_assignment_path, class: 'btn btn-default pull-right', data: { action: 'swap-remote-container', target: '#user_assignments_modal' } do %>
|
||||
<i class="fas fa-plus"></i>
|
||||
<%= t('access_permissions.grant_access') %>
|
||||
<% end %>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<% # frozen_string_literal: true %>
|
||||
|
||||
<div class="modal fade <%= assignable.class.name.parameterize(separator: '-') %>-assignments-modal" tabindex="-1" role="dialog" data-action="modal-close">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content" id="user_assignments_modal">
|
||||
<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 "access_permissions.#{assignable.class.name.pluralize.underscore}.modals.show_modal.title", assignable_name: assignable.name %></h4>
|
||||
<h4 class="modal-title"><%= t("access_permissions.#{assignable.class.name.pluralize.underscore}.modals.show_modal.title", resource_name: assignable.name) %></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<% manually_assigned_users.order(full_name: :asc).each do |user| %>
|
||||
|
|
|
@ -5,7 +5,7 @@ json.form controller.render_to_string(
|
|||
formats: [:html],
|
||||
locals: {
|
||||
user: @user_assignment.user,
|
||||
update_path: access_permissions_my_module_path(@my_module)
|
||||
update_path: access_permissions_my_module_path(@my_module),
|
||||
with_inherit: true,
|
||||
assignable: @my_module
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<% editable ||= false %>
|
||||
<% if assignable.visible? %>
|
||||
<%= form_with(model: assignable, url: [:update_default_public_user_role, :access_permissions, assignable], method: :put, remote: true, html: { class: 'row member-item', id: 'public_assignments', data: { action: 'replace-form autosave-form', object_type: assignable.class.name.underscore.to_sym } }) do |f| %>
|
||||
<%= form_with(model: assignable, url: [:update_default_public_user_role, :access_permissions, assignable], method: :put, remote: true, html: { class: 'member-item', id: 'public_assignments', data: { action: 'replace-form autosave-form', object_type: assignable.class.name.underscore.to_sym } }) do |f| %>
|
||||
<div class="user-assignment-info">
|
||||
<div class="global-avatar-container">
|
||||
<%= image_tag "icon/team.png", class: 'img-circle pull-left' %>
|
||||
|
@ -7,20 +8,43 @@
|
|||
<div>
|
||||
<%= t('access_permissions.everyone_else', team_name: assignable.team.name) %>
|
||||
<%= render 'access_permissions/partials/public_members_dropdown', team: assignable.team, assignable: assignable %>
|
||||
<br>
|
||||
<small class="text-muted">
|
||||
<%= assignable.default_public_user_role.name %>
|
||||
<span class="permission-object-tag" title="<%= t("access_permissions.partials.#{assignable.class.name.underscore}_tooltip") %>"">
|
||||
<%= t("access_permissions.partials.#{assignable.class.name.underscore}") %>
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-assignment-controls">
|
||||
<div class="user-assignment-role">
|
||||
<% if editable %>
|
||||
<%= f.select :default_public_user_role_id, options_for_select(user_roles_collection + [[t('access_permissions.remove_access'), nil]], selected: assignable.default_public_user_role_id), {}, class: 'form-control selectpicker', title: t("user_assignment.change_#{assignable.class.name.underscore}_role"), data: { 'selected-text-format' => 'static' } %>
|
||||
<% end %>
|
||||
<div class="user-assignment-controls">
|
||||
<div class="dropdown pull-right">
|
||||
<% if editable %>
|
||||
<button class="btn btn-light btn-role-select" type="button" id="defaultPublicUserRole" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<%= f.object.default_public_user_role.name %>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<%= f.hidden_field :default_public_user_role_id, value: f.object.default_public_user_role.id, class: "default-public-user-role-id" %>
|
||||
<ul class="dropdown-menu dropdown-menu-right user-assignment-dropdown" aria-labelledby="defaultPublicUserRole">
|
||||
<% user_roles_collection.each do |role| %>
|
||||
<li>
|
||||
<a href="#" data-turbolinks="false" class="user-role-selector" data-role-id="<%= role[1] %>">
|
||||
<%= role[0] %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if assignable.top_level_assignable? %>
|
||||
<li role="separator" class="divider" data-hook="support-dropdown-separator"></li>
|
||||
<li>
|
||||
<a href="#" data-turbolinks="false" class="user-role-selector" data-role-id="">
|
||||
<%= t('access_permissions.remove_access') %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<button class="btn btn-light btn-role-select disabled" type="button">
|
||||
<%= f.object.default_public_user_role.name %>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<% # frozen_string_literal: true %>
|
||||
|
||||
<%
|
||||
with_inherit ||= false
|
||||
assignment = assignable.user_assignments.find_by(user_id: user.id, team: current_team)
|
||||
item_id = dom_id(user, :assignment_member)
|
||||
%>
|
||||
|
@ -18,15 +19,15 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="user-assignment-controls">
|
||||
<% unless defined?(with_inherit) && current_user == user %>
|
||||
<% unless with_inherit && current_user == user %>
|
||||
<div class="dropdown pull-right">
|
||||
<button class="btn btn-light" type="button" id="userAccess_<%= user.id %>" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<button class="btn btn-light btn-role-select" type="button" id="userAccess_<%= user.id %>" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<%= f.object.user_role.name %>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<%= f.hidden_field :user_role_id, value: f.object.user_role.id %>
|
||||
<ul class="dropdown-menu dropdown-menu-right user-assignment-dropdown" aria-labelledby="userAccess_<%= user.id %>">
|
||||
<% user_roles_collection(with_inherit: defined? with_inherit).each do |role| %>
|
||||
<% user_roles_collection(with_inherit: with_inherit).each do |role| %>
|
||||
<li>
|
||||
<a href="#" data-turbolinks="false" class="user-role-selector" data-role-id="<%= role[1] %>">
|
||||
<%= role[0] %>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<% # frozen_string_literal: true %>
|
||||
|
||||
<div class="modal-content" id="user_assignments_modal" data-action="modal-close" data-target="<%= projects_path %>">
|
||||
<div class="modal-content" id="user_assignments_modal" data-action="modal-close" data-target="<%= public_send("#{assignable.class.name.pluralize.underscore}_path") %>">
|
||||
<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">
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
id = dom_id(user, :new_protocol_member)
|
||||
%>
|
||||
|
||||
<div class="row member-item new-member-item" data-filter-item="<%= user.full_name %>">
|
||||
<div class="member-item new-member-item" data-filter-item="<%= user.full_name %>">
|
||||
<%= user_form.hidden_field :user_id, value: user.id, name:"access_permissions_new_user_form[resource_members][#{user.id}][user_id]" %>
|
||||
<div class="user-assignment-info">
|
||||
<div class="sci-checkbox-container">
|
||||
|
|
|
@ -6,8 +6,7 @@ json.modal controller.render_to_string(
|
|||
locals: {
|
||||
assignable: @project,
|
||||
manually_assigned_users: @project.manually_assigned_users,
|
||||
top_level_assignable: @project,
|
||||
users: @project.manually_assigned_users
|
||||
top_level_assignable: @project
|
||||
},
|
||||
layout: false
|
||||
)
|
||||
|
|
|
@ -6,7 +6,7 @@ json.html controller.render_to_string(
|
|||
locals: {
|
||||
assignable: @protocol,
|
||||
form_object: @user_assignment,
|
||||
users: current_team.users.where.not(id: @protocol.assigned_users.select(:id)),
|
||||
users: current_team.users.where.not(id: @protocol.manually_assigned_users.select(:id)),
|
||||
create_path: access_permissions_protocols_path(id: @protocol.id),
|
||||
assignable_path: edit_access_permissions_protocol_path(@protocol)
|
||||
},
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
json.modal controller.render_to_string(
|
||||
partial: 'access_permissions/protocols/modals/show_modal',
|
||||
partial: 'access_permissions/modals/show_modal',
|
||||
formats: [:html],
|
||||
locals: {
|
||||
protocol: @protocol,
|
||||
users: @protocol.assigned_users,
|
||||
can_manage_resource: can_manage_protocol_users?(@protocol)
|
||||
assignable: @protocol,
|
||||
top_level_assignable: @protocol,
|
||||
manually_assigned_users: @protocol.manually_assigned_users
|
||||
},
|
||||
layout: false
|
||||
)
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</span>
|
||||
<% end %>
|
||||
<a href="#" id="my-module-protocol-info-button" class="status-info
|
||||
<%= 'parent-newer' if @protocol.parent_newer? || @protocol.parent_and_self_newer? %>
|
||||
<%= 'parent-newer' if @protocol.parent_newer? %>
|
||||
<%= 'protocol-newer' if @protocol.newer_than_parent? %>
|
||||
" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
|
@ -47,7 +47,7 @@
|
|||
<div class="value"><%= I18n.l(@protocol&.parent&.published_on, format: :full) %></div>
|
||||
</div>
|
||||
<div class="dropdown-footer">
|
||||
<% if @protocol.parent_newer? || @protocol.parent_and_self_newer? %>
|
||||
<% if @protocol.parent_newer? %>
|
||||
<div class="notification-line new-parent-version">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<div class="description"><%= t("my_modules.protocols.protocol_status_bar.messages.template_updated_html") %></div>
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
<%= f.hidden_field(:published_on, value: protocol.published_on) %>
|
||||
<%= f.hidden_field(:description, value: protocol.description) %>
|
||||
<%= f.hidden_field(:protocol_type, value: protocol.protocol_type) %>
|
||||
<%= f.hidden_field(:visibility) %>
|
||||
<%= f.hidden_field(:default_public_user_role_id) %>
|
||||
|
||||
<% end %>
|
||||
|
||||
|
|
|
@ -1,3 +1,25 @@
|
|||
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%=t "general.cancel" %></button>
|
||||
<button type="button" class="btn btn-primary" data-action="import_protocol" data-import_type="in_repository_draft"><%=t "protocols.import_export.import_modal.import_to_team_protocols_label" %></button>
|
||||
<button type="button" class="btn btn-primary" data-action="import_protocol" data-import_type="in_repository_draft"><%=t "protocols.import_export.import_modal.import_to_private_protocols_label" %></button>
|
||||
<div class="footer">
|
||||
<div class="left-section">
|
||||
<div class="default-role-container">
|
||||
<div class="sci-checkbox-container">
|
||||
<%= check_box_tag "visibility", "visible", false, class: "sci-checkbox" %>
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</div>
|
||||
<div class="default-role-description">
|
||||
<%= t("protocols.new_protocol_modal.access_label") %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden" id="roleSelectWrapper">
|
||||
<div class="sci-input-container">
|
||||
<%= label_tag :default_public_user_role_id, t("protocols.new_protocol_modal.role_label") %>
|
||||
<% default_role = UserRole.find_by(name: I18n.t('user_roles.predefined.viewer')).id %>
|
||||
<%= hidden_field_tag :default_public_user_role_id, value: default_role %>
|
||||
<%= select_tag :role_selector, options_for_select(user_roles_collection, default_role) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-section">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%=t "general.cancel" %></button>
|
||||
<button type="button" class="btn btn-primary" data-action="import_protocol" data-import_type="in_repository_draft"><%=t "protocols.import_export.import_modal.import_protocols_label" %></button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<div class="content-pane flexible protocols-index <%= @type %>">
|
||||
<div class="content-header sticky-header">
|
||||
<div class="title-row">
|
||||
<% if templates_view_mode_archived? %>
|
||||
<% if templates_view_mode_archived?(type: @type) %>
|
||||
<h1>
|
||||
<i class="fas fa-archive"></i>
|
||||
<%= t('protocols.index.head_title_archived') %>
|
||||
|
@ -43,6 +43,7 @@
|
|||
<%= render partial: "protocols/index/linked_children_modal.html.erb" %>
|
||||
<%= render partial: "protocols/index/protocolsio_modal.html.erb" %>
|
||||
<%= render partial: "protocols/index/new_protocol_modal.html.erb", locals: {type: 'new'} %>
|
||||
<%= render partial: "protocols/index/protocol_preview_modal.html.erb" %>
|
||||
|
||||
<%= render partial: "protocols/import_export/import_elements.html.erb" %>
|
||||
|
||||
|
|
|
@ -23,4 +23,7 @@
|
|||
<i class="fas fa-undo"></i>
|
||||
<span class="button-text"><%= t("protocols.index.action_toolbar.restore") %></span>
|
||||
</button>
|
||||
<div class="emptyPlaceholder hidden">
|
||||
<%= t("protocols.index.action_toolbar.empty_placeholder") %>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
data-placeholder="Select who has access">
|
||||
</select>
|
||||
</div>
|
||||
<div class="select-block folders">
|
||||
<div class="select-block has-draft">
|
||||
<span class="sci-checkbox-container">
|
||||
<%= check_box_tag :has_draft, 'accepted', false, {class: "sci-checkbox"} %>
|
||||
<span class="sci-checkbox-label"></span>
|
||||
|
|
|
@ -98,7 +98,5 @@
|
|||
<button type="button" class="btn btn-primary convert-protocol" disabled><%= t('protocols.index.protocolsio.convert') %></button>
|
||||
</div>
|
||||
|
||||
<%= render partial: "protocols/index/protocol_preview_modal.html.erb" %>
|
||||
|
||||
<%= javascript_include_tag "protocols/steps" %>
|
||||
<%= javascript_include_tag "protocols/protocolsio.js" %>
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
config: @inline_editable_title_config
|
||||
} %>
|
||||
<% else %>
|
||||
<% if @protocol.archived %>
|
||||
<i class="fas fa-archive"></i>
|
||||
<% end %>
|
||||
<div class="name-readonly-placeholder">
|
||||
<%= @protocol.name %>
|
||||
</div>
|
||||
|
|
|
@ -436,7 +436,8 @@ class Extends
|
|||
protocol_template_access_granted: 233,
|
||||
protocol_template_access_changed: 234,
|
||||
protocol_template_access_revoked: 235,
|
||||
task_protocol_save_to_template: 236
|
||||
task_protocol_save_to_template: 236,
|
||||
protocol_template_draft_created: 237
|
||||
}
|
||||
|
||||
ACTIVITY_GROUPS = {
|
||||
|
@ -453,7 +454,7 @@ class Extends
|
|||
78, 96, 107, 113, 114, *133..136, 180, 181, 182],
|
||||
protocol_repository: [80, 103, 89, 87, 79, 90, 91, 88, 85, 86, 84, 81, 82,
|
||||
83, 101, 112, 123, 125, 117, 119, 129, 131, 170, 173, 179, 187, 186,
|
||||
190, 191, *204..215, 220, 221, 223, 227, 228, 229, *230..235],
|
||||
190, 191, *204..215, 220, 221, 223, 227, 228, 229, *230..235, 237],
|
||||
team: [92, 94, 93, 97, 104],
|
||||
label_repository: [*216..219]
|
||||
}
|
||||
|
|
|
@ -156,6 +156,8 @@ en:
|
|||
attributes:
|
||||
step_order:
|
||||
invalid: "Invalid step order."
|
||||
name:
|
||||
taken: "This protocol template name has to be unique inside a team (this includes the archive)."
|
||||
step:
|
||||
attributes:
|
||||
step_orderable_element_order:
|
||||
|
@ -313,7 +315,7 @@ en:
|
|||
templates:
|
||||
sidebar_title: "TEMPLATES"
|
||||
protocol_templates: "Protocol templates"
|
||||
label_templates: "Label Templates"
|
||||
label_templates: "Label templates"
|
||||
|
||||
nav2:
|
||||
all_projects:
|
||||
|
@ -1085,8 +1087,6 @@ en:
|
|||
revert_title: "Revert protocol template"
|
||||
revert_message: "This will override any changes you made. We can’t recover them once you revert."
|
||||
revert_btn_text: "Yes, revert it"
|
||||
update_parent_title: "Update templates version"
|
||||
update_parent_message: "Are you sure you want to update the templates protocol with this version? This will override any other changes made in the templates version."
|
||||
update_self_title: "Update from protocol templates"
|
||||
update_self_message: "This will override any changes you made. We can’t recover them once you update this protocol with the new version."
|
||||
update_self_btn_text: "Yes, update it"
|
||||
|
@ -1099,7 +1099,7 @@ en:
|
|||
update_parent_flash: "Protocol in templates was successfully updated with the version from the task."
|
||||
update_parent_error: "Failed to update templates version of the protocol."
|
||||
update_parent_error_locked: "Failed to update templates version of the protocol. One or more files in the protocol are currently being edited."
|
||||
update_from_parent_flash: "Version in the templates was successfully updated."
|
||||
update_from_parent_flash: "Protocol was successfully updated."
|
||||
update_from_parent_error: "Failed to update the protocol with the version in the templates."
|
||||
update_from_parent_error_locked: "Failed to update the protocol with the version in the templates. One or more files in the protocol are currently being edited."
|
||||
load_from_repository_flash: "Successfully loaded the protocol from the templates."
|
||||
|
@ -1130,7 +1130,7 @@ en:
|
|||
link_label: "Link task to this protocol in templates"
|
||||
link_text: "<strong>Warning!</strong> This will unlink the currently linked protocol."
|
||||
error_400: "Due to unknown error, protocol could not be copied to templates."
|
||||
success_message: "New protocol template was successfully saved. You can see it in protocol templates."
|
||||
success_message: "Protocol was successfully saved as a new protocol template."
|
||||
confirm: "Save"
|
||||
load_from_file_flash: "Successfully loaded the protocol from the file."
|
||||
load_from_file_error: "Failed to load the protocol from file."
|
||||
|
@ -2641,6 +2641,7 @@ en:
|
|||
import: "Load"
|
||||
import_to_team_protocols_label: "Import to Team Protocols"
|
||||
import_to_private_protocols_label: "Import to My Protocols"
|
||||
import_protocols_label: "Import"
|
||||
export:
|
||||
export_results:
|
||||
title: "Export results"
|
||||
|
@ -2718,6 +2719,7 @@ en:
|
|||
export: "Export"
|
||||
archive: "Archive"
|
||||
restore: "Restore"
|
||||
empty_placeholder: "There is no action available"
|
||||
public_description: "Team protocols are visible and can be used by everyone from the team."
|
||||
private_description: "My protocols are only visible to you."
|
||||
create_new: "New protocol"
|
||||
|
@ -2850,7 +2852,7 @@ en:
|
|||
import:
|
||||
public: "Team protocols"
|
||||
private: "My protocols"
|
||||
success_flash: 'Protocol <strong>%{name}</strong> successfully imported to %{type}.'
|
||||
success_flash: 'Protocol <strong>%{name}</strong> successfully imported.'
|
||||
|
||||
steps:
|
||||
placeholder: 'Enter step name'
|
||||
|
@ -3216,9 +3218,9 @@ en:
|
|||
|
||||
access_permissions:
|
||||
everyone_else: "Everyone else at %{team_name}"
|
||||
reset: "The inherited role will be applied"
|
||||
reset: "Inherit role"
|
||||
remove_access: "Remove access"
|
||||
grant_access: "Grant access"
|
||||
grant_access: "Grant new access"
|
||||
create:
|
||||
success:
|
||||
one: "You have successfully granted access to %{count} member to the project."
|
||||
|
@ -3233,15 +3235,15 @@ en:
|
|||
new_resource_assignments: "Grant new access to %{resource}"
|
||||
new_assignments_form:
|
||||
title: "Select members"
|
||||
submit: "Grant access"
|
||||
submit_singular: "Grant access to 1 user"
|
||||
submit_plural: "Grant access to {num} users"
|
||||
submit: "Grant new access"
|
||||
submit_singular: "Grant access to 1 member"
|
||||
submit_plural: "Grant access to {num} members"
|
||||
find_people_html: "Find people"
|
||||
new_protocol_assignments_form:
|
||||
title: "Select members"
|
||||
submit: "Grant access"
|
||||
submit_singular: "Grant access to 1 user"
|
||||
submit_plural: "Grant access to {num} users"
|
||||
submit: "Grant new access"
|
||||
submit_singular: "Grant access to 1 member"
|
||||
submit_plural: "Grant access to {num} members"
|
||||
find_people_html: "Find people"
|
||||
experiment_member_field:
|
||||
reset: "Inherit role"
|
||||
|
|
|
@ -257,6 +257,7 @@ en:
|
|||
protocol_template_published_html: "%{user} published protocol template %{protocol} version %{version_number}"
|
||||
protocol_template_revision_notes_updated_html: "%{user} edited revision notes of %{protocol}"
|
||||
protocol_template_draft_deleted_html: "%{user} deleted draft of %{protocol}"
|
||||
protocol_template_draft_created_html: "%{user} created draft of %{protocol}"
|
||||
protocol_template_access_granted_html: "%{user} granted access to %{user_target} with user role %{role} to protocol template %{protocol}"
|
||||
protocol_template_access_changed_html: "%{user} changed %{user_target}’s role on protocol template %{protocol} to %{role}"
|
||||
protocol_template_access_revoked_html: "%{user} removed %{user_target} with user role %{role} from protocol template %{protocol}"
|
||||
|
@ -472,6 +473,7 @@ en:
|
|||
protocol_template_published: "Protocol template published"
|
||||
protocol_template_revision_notes_updated: "Revision notes edited"
|
||||
protocol_template_draft_deleted: "Deleting draft"
|
||||
protocol_template_draft_created: "Creating draft"
|
||||
protocol_template_access_granted: "User granted access to protocol template"
|
||||
protocol_template_access_changed: "User role changed on a protocol"
|
||||
protocol_template_access_revoked: "User removed from a protocol"
|
||||
|
|
|
@ -180,23 +180,6 @@ describe ProtocolsController, type: :controller do
|
|||
.to(change { Activity.count })
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST update_parent' do
|
||||
let(:action) { put :update_parent, params: params, format: :json }
|
||||
|
||||
it 'calls create activity for updating protocol in repository from task' do
|
||||
expect(Activities::CreateActivityService)
|
||||
.to(receive(:call)
|
||||
.with(hash_including(activity_type:
|
||||
:update_protocol_in_repository_from_task)))
|
||||
action
|
||||
end
|
||||
|
||||
it 'adds activity in DB' do
|
||||
expect { action }
|
||||
.to(change { Activity.count })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST load_from_repository' do
|
||||
|
|
|
@ -15,7 +15,6 @@ FactoryBot.define do
|
|||
protocol_type { :linked }
|
||||
parent { create :protocol }
|
||||
added_by { create :user }
|
||||
parent_updated_at { Time.now }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -187,9 +187,6 @@ describe TeamImporter do
|
|||
expect(db_protocol.restored_on).to be_nil
|
||||
expect(db_protocol.authors).to eq json_protocol['authors']
|
||||
expect(db_protocol.parent_id).to eq json_protocol['parent_id']
|
||||
expect(db_protocol.parent_updated_at).to eq(
|
||||
json_protocol['parent_updated_at']
|
||||
)
|
||||
expect(db_protocol.protocol_type).to eq(
|
||||
json_protocol['protocol_type']
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue