mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-17 02:24:34 +08:00
Merge branch 'features/protocol_versioning' into sb_SCI-8007
This commit is contained in:
commit
c4bdbdec9a
33 changed files with 322 additions and 164 deletions
|
@ -38,19 +38,19 @@ function initEditProtocolDescription() {
|
|||
function initLinkUpdate() {
|
||||
var modal = $('#confirm-link-update-modal');
|
||||
var modalTitle = modal.find('.modal-title');
|
||||
var modalBody = modal.find('.modal-body');
|
||||
var modalMessage = modal.find('.modal-body .message');
|
||||
var updateBtn = modal.find(".modal-footer [data-action='submit']");
|
||||
$("[data-action='unlink'], [data-action='revert'], [data-action='update-parent'], [data-action='update-self']")
|
||||
.on('ajax:success', function(e, data) {
|
||||
modalTitle.html(data.title);
|
||||
modalBody.html(data.message);
|
||||
modalMessage.html(data.message);
|
||||
updateBtn.text(data.btn_text);
|
||||
modal.attr('data-url', data.url);
|
||||
modal.modal('show');
|
||||
});
|
||||
|
||||
modal.on('hidden.bs.modal', function() {
|
||||
modalBody.html('');
|
||||
modalMessage.html('');
|
||||
});
|
||||
|
||||
if (!$._data(updateBtn[0], 'events')) {
|
||||
|
|
|
@ -135,9 +135,30 @@ var ProtocolsIndex = (function() {
|
|||
});
|
||||
}
|
||||
|
||||
function initManageAccessButton() {
|
||||
$('.protocols-index').on('click', '#manageProtocolAccess', function() {
|
||||
$(`tr[data-row-id=${rowsSelected[0]}] .protocol-users-link`).click();
|
||||
function initManageAccess() {
|
||||
let protocolsContainer = '.protocols-container';
|
||||
let manageAccessModal = '.protocol-assignments-modal';
|
||||
|
||||
function loadManageAccessModal(href) {
|
||||
$.get(href, function(data) {
|
||||
$(protocolsContainer).append($.parseHTML(data.html));
|
||||
$(manageAccessModal).modal('show');
|
||||
|
||||
// Remove modal when it gets closed
|
||||
$(manageAccessModal).on('hidden.bs.modal', function() {
|
||||
$(manageAccessModal).remove();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protocolsTableEl.on('click', '.protocol-users-link', function(e) {
|
||||
loadManageAccessModal(this.href);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
$(protocolsContainer).on('click', '#manageProtocolAccess', function() {
|
||||
loadManageAccessModal($(`tr[data-row-id=${rowsSelected[0]}] .protocol-users-link`).attr('href'));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -180,8 +201,8 @@ var ProtocolsIndex = (function() {
|
|||
targets: 0,
|
||||
searchable: false,
|
||||
orderable: false,
|
||||
sWidth: "1%",
|
||||
render: function (data, type, full, meta) {
|
||||
sWidth: '1%',
|
||||
render: function() {
|
||||
return `<div class="sci-checkbox-container">
|
||||
<input type="checkbox" class="sci-checkbox">
|
||||
<span class="sci-checkbox-label"></span>
|
||||
|
@ -244,7 +265,7 @@ var ProtocolsIndex = (function() {
|
|||
fnInitComplete: function(e) {
|
||||
var dataTableWrapper = $(e.nTableWrapper);
|
||||
DataTableHelpers.initLengthAppearance(dataTableWrapper);
|
||||
DataTableHelpers.initSearchField(dataTableWrapper, 'Enter...');
|
||||
DataTableHelpers.initSearchField(dataTableWrapper, I18n.t('protocols.index.search_bar_placeholder'));
|
||||
dataTableWrapper.find('.main-actions, .pagination-row').removeClass('hidden');
|
||||
|
||||
let actionToolBar = $($('#protocolActionToolbar').html());
|
||||
|
@ -675,29 +696,18 @@ var ProtocolsIndex = (function() {
|
|||
}
|
||||
|
||||
function updateDataTableSelectAllCheckbox() {
|
||||
var table = protocolsDatatable.table().node();
|
||||
var checkboxesAll = $("tbody input[type='checkbox']", protocolsTableEl);
|
||||
var checkboxesChecked = $("tbody input[type='checkbox']:checked", protocolsTableEl);
|
||||
var checkboxSelectAll = $("thead input[name='select_all']", table).get(0);
|
||||
var table = $('.protocols-datatable');
|
||||
var checkboxes = table.find("tbody input[type='checkbox']");
|
||||
var selectedCheckboxes = table.find("tbody input[type='checkbox']:checked");
|
||||
var selectAllCheckbox = table.find("thead input[name='select_all']");
|
||||
|
||||
if (checkboxesChecked.length === 0) {
|
||||
// If none of the checkboxes are checked
|
||||
checkboxSelectAll.checked = false;
|
||||
if ('indeterminate' in checkboxSelectAll) {
|
||||
checkboxSelectAll.indeterminate = false;
|
||||
}
|
||||
} else if (checkboxesChecked.length === checkboxesAll.length) {
|
||||
// If all of the checkboxes are checked
|
||||
checkboxSelectAll.checked = true;
|
||||
if ('indeterminate' in checkboxSelectAll) {
|
||||
checkboxSelectAll.indeterminate = false;
|
||||
}
|
||||
selectAllCheckbox.prop('indeterminate', false);
|
||||
if (selectedCheckboxes.length === 0) {
|
||||
selectAllCheckbox.prop('checked', false);
|
||||
} else if (selectedCheckboxes.length === checkboxes.length) {
|
||||
selectAllCheckbox.prop('checked', true);
|
||||
} else {
|
||||
// If some of the checkboxes are checked
|
||||
checkboxSelectAll.checked = true;
|
||||
if ('indeterminate' in checkboxSelectAll) {
|
||||
checkboxSelectAll.indeterminate = true;
|
||||
}
|
||||
selectAllCheckbox.prop('indeterminate', true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1002,7 +1012,7 @@ var ProtocolsIndex = (function() {
|
|||
}
|
||||
|
||||
init();
|
||||
initManageAccessButton();
|
||||
initManageAccess();
|
||||
initArchiveProtocols();
|
||||
initRestoreProtocols();
|
||||
initExportProtocols();
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
/* global dropdownSelector HelperModule */
|
||||
(function() {
|
||||
$('#newProtocolModal').on('change', '#protocol_visibility', function() {
|
||||
$('#roleSelectWrapper').toggleClass('hidden', !$(this)[0].checked);
|
||||
});
|
||||
const protocolModal = '#newProtocolModal';
|
||||
$(protocolModal)
|
||||
.on('change', '#protocol_visibility', function() {
|
||||
$('#roleSelectWrapper').toggleClass('hidden', !$(this)[0].checked);
|
||||
})
|
||||
.on('show.bs.modal', function() {
|
||||
$(`${protocolModal} #protocol_name`).parent().removeClass('error');
|
||||
$(`${protocolModal} form[data-action="new"] #protocol_name`).val('');
|
||||
});
|
||||
|
||||
let roleSelector = '#newProtocolModal #protocol_role_selector';
|
||||
let roleSelector = `${protocolModal} #protocol_role_selector`;
|
||||
dropdownSelector.init(roleSelector, {
|
||||
noEmptyOption: true,
|
||||
singleSelect: true,
|
||||
|
@ -15,16 +21,16 @@
|
|||
}
|
||||
});
|
||||
|
||||
$('#newProtocolModal')
|
||||
$(protocolModal)
|
||||
.on('ajax:error', 'form', function(e, error) {
|
||||
let msg = error.responseJSON.error;
|
||||
$('#newProtocolModal #protocol_name').parent().addClass('error').attr('data-error-text', msg);
|
||||
$(`${protocolModal} #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');
|
||||
}
|
||||
$('#newProtocolModal #protocol_name').parent().removeClass('error');
|
||||
$('#newProtocolModal').modal('hide');
|
||||
$(`${protocolModal} #protocol_name`).parent().removeClass('error');
|
||||
$(protocolModal).modal('hide');
|
||||
});
|
||||
}());
|
||||
|
|
|
@ -22,7 +22,8 @@
|
|||
}
|
||||
|
||||
.modal-body {
|
||||
min-height: calc(100vh - 200px);
|
||||
max-height: calc(100vh - 190px);
|
||||
min-height: 370px;
|
||||
overflow: auto;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,10 @@
|
|||
align-items: baseline;
|
||||
display: flex;
|
||||
|
||||
a:first-of-type:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.task-section-caret {
|
||||
padding-right: .25em;
|
||||
}
|
||||
|
|
|
@ -428,9 +428,14 @@
|
|||
width: 506px;
|
||||
padding: 15px 0;
|
||||
|
||||
.dropdown-content {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.dropdown-header,
|
||||
.dropdown-body {
|
||||
padding: 10px 24px;
|
||||
.dropdown-body,
|
||||
.dropdown-footer {
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.dropdown-header {
|
||||
|
@ -508,7 +513,6 @@
|
|||
|
||||
.dropdown-footer {
|
||||
border-top: $border-tertiary;
|
||||
padding: 12px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -670,8 +674,14 @@
|
|||
margin-left: 4px;
|
||||
}
|
||||
|
||||
#confirm-link-update-modal {
|
||||
#confirm-link-update-modal,
|
||||
.delete-steps-modal {
|
||||
.modal-body p {
|
||||
margin: 1.2em 0;
|
||||
}
|
||||
|
||||
.warning {
|
||||
margin: 0 15px 15px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1.2em;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,18 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.table.dataTable .sorting {
|
||||
&::after {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
|
@ -61,6 +73,10 @@
|
|||
.dataTables_scrollBody {
|
||||
height: 100%;
|
||||
|
||||
td:first-child {
|
||||
padding-top: 13px;
|
||||
}
|
||||
|
||||
td:not(:first-child) {
|
||||
padding: 14px 8px;
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@
|
|||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
.details-container {
|
||||
.protocol-details {
|
||||
.protocol-metadata {
|
||||
margin-bottom: 2em;
|
||||
|
||||
|
|
|
@ -53,21 +53,36 @@ module AccessPermissions
|
|||
permitted_create_params[:resource_members].each do |_k, user_assignment_params|
|
||||
next unless user_assignment_params[:assign] == '1'
|
||||
|
||||
user_assignment = UserAssignment.find_or_initialize_by(
|
||||
assignable: @project,
|
||||
user_id: user_assignment_params[:user_id],
|
||||
team: current_team
|
||||
)
|
||||
if user_assignment_params[:user_id] == 'all'
|
||||
@project.update!(visibility: :visible, default_public_user_role_id: user_assignment_params[:user_role_id])
|
||||
Activities::CreateActivityService
|
||||
.call(activity_type: :change_project_visibility,
|
||||
owner: current_user,
|
||||
subject: @project,
|
||||
team: @project.team,
|
||||
project: @project,
|
||||
message_items: {
|
||||
project: @project.id,
|
||||
visibility: t('projects.activity.visibility_visible')
|
||||
})
|
||||
else
|
||||
|
||||
user_assignment.update!(
|
||||
user_role_id: user_assignment_params[:user_role_id],
|
||||
assigned_by: current_user,
|
||||
assigned: :manually
|
||||
)
|
||||
user_assignment = UserAssignment.find_or_initialize_by(
|
||||
assignable: @project,
|
||||
user_id: user_assignment_params[:user_id],
|
||||
team: current_team
|
||||
)
|
||||
|
||||
log_activity(:assign_user_to_project, user_assignment)
|
||||
created_count += 1
|
||||
propagate_job(user_assignment)
|
||||
user_assignment.update!(
|
||||
user_role_id: user_assignment_params[:user_role_id],
|
||||
assigned_by: current_user,
|
||||
assigned: :manually
|
||||
)
|
||||
|
||||
log_activity(:assign_user_to_project, user_assignment)
|
||||
created_count += 1
|
||||
propagate_job(user_assignment)
|
||||
end
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
|
|
|
@ -51,25 +51,31 @@ module AccessPermissions
|
|||
permitted_create_params[:resource_members].each do |_k, user_assignment_params|
|
||||
next unless user_assignment_params[:assign] == '1'
|
||||
|
||||
user_assignment = UserAssignment.find_or_initialize_by(
|
||||
assignable: @protocol,
|
||||
user_id: user_assignment_params[:user_id],
|
||||
team: current_team
|
||||
)
|
||||
if user_assignment_params[:user_id] == 'all'
|
||||
@protocol.update!(visibility: :visible, default_public_user_role_id: user_assignment_params[:user_role_id])
|
||||
else
|
||||
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
|
||||
)
|
||||
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)
|
||||
created_count += 1
|
||||
log_activity(:protocol_template_access_granted, user_assignment)
|
||||
end
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
@message = t('access_permissions.create.success', count: created_count)
|
||||
format.json { render :edit }
|
||||
end
|
||||
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
respond_to do |format|
|
||||
@message = t('access_permissions.create.failure')
|
||||
|
|
|
@ -127,10 +127,7 @@ class ProtocolsDatatable < CustomDatatable
|
|||
records = records.where(protocols: { published_by_id: params[:published_by] }) if params[:published_by].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] })
|
||||
records = records.where(all_user_assignments: { user_id: params[:members] })
|
||||
end
|
||||
|
||||
if params[:archived_on_from].present?
|
||||
|
@ -141,7 +138,13 @@ class ProtocolsDatatable < CustomDatatable
|
|||
records = records.where(protocols: { archived_by_id: params[:archived_by] }) if params[:archived_by].present?
|
||||
|
||||
if params[:has_draft].present?
|
||||
records = records.where(protocols: { protocol_type: Protocol.protocol_types[:in_repository_draft] })
|
||||
records =
|
||||
records
|
||||
.joins("LEFT OUTER JOIN protocols protocol_drafts " \
|
||||
"ON protocol_drafts.protocol_type = #{Protocol.protocol_types[:in_repository_draft]} " \
|
||||
"AND (protocol_drafts.parent_id = protocols.id OR protocol_drafts.parent_id = protocols.parent_id)")
|
||||
.where('protocols.protocol_type = ? OR protocol_drafts.id IS NOT NULL',
|
||||
Protocol.protocol_types[:in_repository_draft])
|
||||
end
|
||||
|
||||
records
|
||||
|
@ -199,6 +202,9 @@ class ProtocolsDatatable < CustomDatatable
|
|||
'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"')
|
||||
.joins('LEFT OUTER JOIN "user_assignments" "all_user_assignments" '\
|
||||
'ON "all_user_assignments"."assignable_type" = \'Protocol\' '\
|
||||
'AND "all_user_assignments"."assignable_id" = "protocols"."id"')
|
||||
.group('"protocols"."id"')
|
||||
|
||||
records = filter_protocols_records(records)
|
||||
|
@ -212,7 +218,7 @@ class ProtocolsDatatable < CustomDatatable
|
|||
'(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"',
|
||||
'COUNT(DISTINCT("all_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"'
|
||||
)
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module UserRolesHelper
|
||||
def user_roles_collection(with_inherit: false)
|
||||
roles = UserRole.order(id: :asc).pluck(:name, :id)
|
||||
def user_roles_collection(object, with_inherit: false)
|
||||
permission_group = "#{object.class.name}Permissions".constantize
|
||||
permissions = permission_group.constants.map { |const| permission_group.const_get(const) }
|
||||
|
||||
roles = user_roles_subset_by_permissions(permissions).order(id: :asc).pluck(:name, :id)
|
||||
roles = [[t('access_permissions.reset'), 'reset']] + roles if with_inherit
|
||||
roles
|
||||
end
|
||||
|
@ -12,9 +15,10 @@ module UserRolesHelper
|
|||
PermissionExtends::TeamPermissions.constants.map { |const| TeamPermissions.const_get(const) } +
|
||||
ProtocolPermissions.constants.map { |const| ProtocolPermissions.const_get(const) } +
|
||||
RepositoryPermissions.constants.map { |const| RepositoryPermissions.const_get(const) }
|
||||
UserRole.where('permissions && ARRAY[?]::varchar[]', team_permissions)
|
||||
.sort_by { |user_role| (user_role.permissions & team_permissions).length }
|
||||
.reverse!
|
||||
|
||||
user_roles_subset_by_permissions(team_permissions)
|
||||
.sort_by { |user_role| (user_role.permissions & team_permissions).length }
|
||||
.reverse!
|
||||
end
|
||||
|
||||
def team_user_roles_for_select
|
||||
|
@ -22,6 +26,12 @@ module UserRolesHelper
|
|||
end
|
||||
|
||||
def managing_team_user_roles_collection
|
||||
UserRole.where('permissions && ARRAY[?]::varchar[]', [TeamPermissions::USERS_MANAGE])
|
||||
user_roles_subset_by_permissions([TeamPermissions::USERS_MANAGE])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_roles_subset_by_permissions(permissions)
|
||||
UserRole.where('permissions && ARRAY[?]::varchar[]', permissions)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{ i18n.t('protocols.steps.modals.delete_steps.description_1')}}</p>
|
||||
<p><b>{{ i18n.t('protocols.steps.modals.delete_steps.description_2')}}</b></p>
|
||||
|
||||
<p class="warning">{{ i18n.t('protocols.steps.modals.delete_steps.description_2')}}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" @click="cancel">{{ i18n.t('general.cancel') }}</button>
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
<button class="btn btn-light" @click="openVersionsModal">{{ i18n.t("protocols.header.versions") }}</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>
|
||||
<button v-bind:disabled="protocol.attributes.disabled_drafting" v-if="protocol.attributes.disabled_drafting" @click="saveAsdraft" class="btn btn-secondary">{{ i18n.t("protocols.header.save_as_draft") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="details-container" class="protocol-details collapse in">
|
||||
|
|
|
@ -21,6 +21,7 @@ class Protocol < ApplicationRecord
|
|||
after_save :update_user_assignments, if: -> { saved_change_to_visibility? && in_repository? }
|
||||
after_save :update_linked_children
|
||||
skip_callback :create, :after, :create_users_assignments, if: -> { in_module? }
|
||||
before_update :sync_protocol_assignments, if: :visibility_changed?
|
||||
|
||||
enum visibility: { hidden: 0, visible: 1 }
|
||||
enum protocol_type: {
|
||||
|
@ -230,6 +231,11 @@ class Protocol < ApplicationRecord
|
|||
teams.blank? ? self : where(team: teams)
|
||||
end
|
||||
|
||||
def original_code
|
||||
# returns linked protocol code, or code of the original version of the linked protocol
|
||||
parent&.parent&.code || parent&.code || code
|
||||
end
|
||||
|
||||
def insert_step(step, position)
|
||||
ActiveRecord::Base.transaction do
|
||||
steps.where('position >= ?', position).desc_order.each do |s|
|
||||
|
@ -793,4 +799,12 @@ class Protocol < ApplicationRecord
|
|||
errors.add(:base, I18n.t('activerecord.errors.models.protocol.wrong_parent_draft_number'))
|
||||
end
|
||||
end
|
||||
|
||||
def sync_protocol_assignments
|
||||
if visible?
|
||||
auto_assign_protocol_members
|
||||
else
|
||||
user_assignments.where(assigned: :automatically).destroy_all
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -105,11 +105,11 @@ Canaid::Permissions.register_for(Protocol) do
|
|||
|
||||
# protocol in repository: restore
|
||||
can :restore_protocol_in_repository do |user, protocol|
|
||||
protocol.archived? && protocol.permission_granted?(user, ProtocolPermissions::MANAGE)
|
||||
protocol.archived? && protocol.permission_granted?(user, ProtocolPermissions::RESTORE)
|
||||
end
|
||||
|
||||
can :archive_protocol_in_repository do |user, protocol|
|
||||
protocol.active? && protocol.permission_granted?(user, ProtocolPermissions::MANAGE)
|
||||
protocol.active? && protocol.permission_granted?(user, ProtocolPermissions::ARCHIVE)
|
||||
end
|
||||
|
||||
# protocol in repository: copy
|
||||
|
@ -119,7 +119,7 @@ Canaid::Permissions.register_for(Protocol) do
|
|||
|
||||
can :publish_protocol_in_repository do |user, protocol|
|
||||
protocol.in_repository_draft? &&
|
||||
protocol.permission_granted?(user, ProtocolPermissions::MANAGE)
|
||||
protocol.permission_granted?(user, ProtocolPermissions::PUBLISH)
|
||||
end
|
||||
|
||||
can :delete_protocol_draft_in_repository do |user, protocol|
|
||||
|
|
|
@ -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, :archived
|
||||
:published, :version_comment, :archived, :disabled_drafting
|
||||
|
||||
def updated_at
|
||||
object.updated_at.to_i
|
||||
|
@ -84,6 +84,14 @@ class ProtocolSerializer < ActiveModel::Serializer
|
|||
!object.in_module?
|
||||
end
|
||||
|
||||
def disabled_drafting
|
||||
protocol_types = Protocol.where(name: object.name).pluck(:protocol_type)
|
||||
object.protocol_type != 'in_repository_draft' &&
|
||||
!object.archived &&
|
||||
protocol_types.length > 1 &&
|
||||
protocol_types.include?('in_repository_draft')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_from_repo_url
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
</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| %>
|
||||
<% user_roles_collection(assignable).each do |role| %>
|
||||
<li>
|
||||
<a href="#" data-turbolinks="false" class="user-role-selector" data-role-id="<%= role[1] %>">
|
||||
<%= role[0] %>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
</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: with_inherit).each do |role| %>
|
||||
<% user_roles_collection(assignable, with_inherit: with_inherit).each do |role| %>
|
||||
<li>
|
||||
<a href="#" data-turbolinks="false" class="user-role-selector" data-role-id="<%= role[1] %>">
|
||||
<%= role[0] %>
|
||||
|
|
|
@ -16,9 +16,14 @@
|
|||
<%= text_field_tag :search_users, '', placeholder: t('.find_people_html'), class: 'sci-input-field', data: { action: 'filter-list', target: 'new-user-assignment-to-project-form' } %>
|
||||
<i class="fas fa-search"></i>
|
||||
</div>
|
||||
<% if assignable.visibility && assignable.visibility == 'hidden' %>
|
||||
<%= f.fields_for :users, UserAssignment.new do |user_form| %>
|
||||
<%= render 'access_permissions/partials/public_assignment_field.html.erb', user_form: user_form, assignable: assignable %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% users.each do |user| %>
|
||||
<%= f.fields_for :users, UserAssignment.new(user: user) do |user_form| %>
|
||||
<%= render 'access_permissions/partials/user_assignment_field.html.erb', user_form: user_form %>
|
||||
<%= render 'access_permissions/partials/user_assignment_field.html.erb', user_form: user_form, assignable: assignable %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<% # frozen_string_literal: true %>
|
||||
|
||||
<div class="member-item new-member-item">
|
||||
<%= user_form.hidden_field :user_id, value: :all, name:"access_permissions_new_user_form[resource_members][0][user_id]" %>
|
||||
<div class="user-assignment-info">
|
||||
<div class="sci-checkbox-container">
|
||||
<%= user_form.check_box :assign,
|
||||
name: "access_permissions_new_user_form[resource_members][0][assign]",
|
||||
data: { action: 'toggle-visibility', target: 'usersAll' },
|
||||
class: "sci-checkbox"
|
||||
%>
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</div>
|
||||
<div class="global-avatar-container">
|
||||
<%= image_tag "icon/team.png", class: 'img-circle pull-left' %>
|
||||
</div>
|
||||
<div>
|
||||
<%= t('user_assignment.assign_all_team_members') %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-assignment-controls">
|
||||
<div class="user-assignment-role hidden" id="usersAll">
|
||||
<%= user_form.select :user_role_id,
|
||||
options_for_select(user_roles_collection(assignable)),
|
||||
{},
|
||||
name: "access_permissions_new_user_form[resource_members][0][user_role_id]",
|
||||
class: 'form-control selectpicker pull-right',
|
||||
title: t('user_assignment.select_role') %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -26,7 +26,7 @@
|
|||
<div class="user-assignment-controls">
|
||||
<div class="user-assignment-role hidden" id="<%= id %>">
|
||||
<%= user_form.select :user_role_id,
|
||||
options_for_select(user_roles_collection),
|
||||
options_for_select(user_roles_collection(assignable)),
|
||||
{},
|
||||
name: "access_permissions_new_user_form[resource_members][#{user.id}][user_role_id]",
|
||||
class: 'form-control selectpicker pull-right',
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="confirm-link-update-modal-label"></h4>
|
||||
</div>
|
||||
<div class="modal-body"></div>
|
||||
<h3 class="warning"><%= t('my_modules.protocols.confirm_link_update_modal.warning') %></h3>
|
||||
<div class="modal-body">
|
||||
<p class="message"></p>
|
||||
<p class="warning"><%= t('my_modules.protocols.confirm_link_update_modal.warning') %></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><%= t("general.cancel") %></button>
|
||||
<button type="button" class="btn btn-success" data-action="submit"></button>
|
||||
|
|
|
@ -15,53 +15,57 @@
|
|||
<i class="fas fa-info-circle"></i>
|
||||
</a>
|
||||
<div class="dropdown-menu status-info-dropdown" aria-labelledby="my-module-protocol-info-button">
|
||||
<div class="dropdown-header">
|
||||
<h2 class="protocol-name">
|
||||
<%= @protocol.parent&.name || @protocol.name %>
|
||||
</h2>
|
||||
<% if @protocol.linked? %>
|
||||
<div class="protocol-header-info">
|
||||
<span><%= t('my_modules.protocols.protocol_status_bar.protocol_id_label') %> <%= @protocol.parent&.code %></span>
|
||||
<span><%= t('my_modules.protocols.protocol_status_bar.protocol_version_label') %> <%= @protocol.parent&.version_number %></span>
|
||||
<div class="dropdown-content">
|
||||
<div class="dropdown-header">
|
||||
<h2 class="protocol-name">
|
||||
<%= @protocol.parent&.name || @protocol.name %>
|
||||
</h2>
|
||||
<% if @protocol.linked? %>
|
||||
<div class="protocol-header-info">
|
||||
<span><%= t('my_modules.protocols.protocol_status_bar.protocol_id_label') %> <%= @protocol.original_code %></span>
|
||||
<span><%= t('my_modules.protocols.protocol_status_bar.protocol_version_label') %> <%= @protocol.parent&.version_number %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="dropdown-body">
|
||||
<% if @protocol.unlinked? %>
|
||||
<div class="info-line">
|
||||
<div class="description"><%= t("my_modules.protocols.protocol_status_bar.protocol_created") %></div>
|
||||
<div class="value"><%= I18n.l(@protocol.created_at, format: :full) %></div>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="info-line">
|
||||
<div class="description"><%= t("my_modules.protocols.protocol_status_bar.protocol_updated") %></div>
|
||||
<div class="value"><%= I18n.l(@protocol.updated_at, format: :full) %></div>
|
||||
</div>
|
||||
<% if @protocol.linked?%>
|
||||
<div class="info-line">
|
||||
<div class="description"><%= t("my_modules.protocols.protocol_status_bar.protocol_loaded") %></div>
|
||||
<div class="value"><%= @protocol.linked_at ? I18n.l(@protocol.linked_at, format: :full) : '' %></div>
|
||||
</div>
|
||||
<div class="info-line">
|
||||
<div class="description"><%= t("my_modules.protocols.protocol_status_bar.protocol_published") %></div>
|
||||
<div class="value"><%= I18n.l(@protocol&.parent&.published_on, format: :full) %></div>
|
||||
<div class="dropdown-body">
|
||||
<% if @protocol.unlinked? %>
|
||||
<div class="info-line">
|
||||
<div class="description"><%= t("my_modules.protocols.protocol_status_bar.protocol_created") %></div>
|
||||
<div class="value"><%= I18n.l(@protocol.created_at, format: :full) %></div>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="info-line">
|
||||
<div class="description"><%= t("my_modules.protocols.protocol_status_bar.protocol_updated") %></div>
|
||||
<div class="value"><%= I18n.l(@protocol.updated_at, format: :full) %></div>
|
||||
</div>
|
||||
<% if @protocol.linked?%>
|
||||
<div class="info-line">
|
||||
<div class="description"><%= t("my_modules.protocols.protocol_status_bar.protocol_loaded") %></div>
|
||||
<div class="value"><%= @protocol.linked_at ? I18n.l(@protocol.linked_at, format: :full) : '' %></div>
|
||||
</div>
|
||||
<div class="info-line">
|
||||
<div class="description"><%= t("my_modules.protocols.protocol_status_bar.protocol_published") %></div>
|
||||
<div class="value"><%= I18n.l(@protocol&.parent&.published_on, format: :full) %></div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="dropdown-footer">
|
||||
<% 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>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @protocol.newer_than_parent? %>
|
||||
<div class="notification-line new-protocol-version">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<div class="description"><%= t("my_modules.protocols.protocol_status_bar.messages.protocol_updated") %></div>
|
||||
</div>
|
||||
<% if @protocol.linked? %>
|
||||
<% 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>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @protocol.newer_than_parent? %>
|
||||
<div class="notification-line new-protocol-version">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<div class="description"><%= t("my_modules.protocols.protocol_status_bar.messages.protocol_updated") %></div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%= javascript_include_tag("my_modules/protocols/protocol_status_bar") %>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<div class="row <%= f.object.hidden? ? 'hidden' : '' %>" id="role_select_wrapper">
|
||||
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
|
||||
<%= f.select :default_public_user_role_id,
|
||||
options_for_select(user_roles_collection, selected: f.object.default_public_user_role_id),
|
||||
options_for_select(user_roles_collection(@project), selected: f.object.default_public_user_role_id),
|
||||
{ label: t('user_assignment.select_default_user_role') },
|
||||
class: 'form-control selectpicker'%>
|
||||
</div>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<div class="row hidden" id="role_select_wrapper">
|
||||
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
|
||||
<%= f.select :default_public_user_role_id,
|
||||
options_for_select(user_roles_collection, UserRole.find_by(name: I18n.t('user_roles.predefined.viewer')).id),
|
||||
options_for_select(user_roles_collection(@project), UserRole.find_by(name: I18n.t('user_roles.predefined.viewer')).id),
|
||||
class: 'form-control selectpicker'%>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<%= 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) %>
|
||||
<%= select_tag :role_selector, options_for_select(user_roles_collection(@protocol), default_role) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
<template id="protocolGeneralToolbar">
|
||||
<div class="left-general-toolbar">
|
||||
<button data-toggle="modal"
|
||||
data-target="#newProtocolModal"
|
||||
<%= 'disabled' if !can_create_protocols_in_repository?(@current_team) %>
|
||||
class="btn btn-primary only-active"
|
||||
>
|
||||
<span class="fas fa-plus"></span>
|
||||
<span class="hidden-xs"><%= t("protocols.index.create_new") %></span>
|
||||
</button>
|
||||
<% if can_create_protocols_in_repository?(@current_team) %>
|
||||
|
||||
<div id="protocol-import-group" class="sci-btn-group only-active" role="group">
|
||||
<button class="btn btn-light btn-open-file <%= 'disabled' unless can_create_protocols_in_repository?(@current_team) %>"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<span class="fas fa-upload"></span><span class="hidden-xs"><%= t("protocols.index.import") %></span>
|
||||
<button data-toggle="modal"
|
||||
data-target="#newProtocolModal"
|
||||
class="btn btn-primary only-active"
|
||||
>
|
||||
<span class="fas fa-plus"></span>
|
||||
<span class="hidden-xs"><%= t("protocols.index.create_new") %></span>
|
||||
</button>
|
||||
<% if can_create_protocols_in_repository?(@current_team) %>
|
||||
|
||||
<div id="protocol-import-group" class="sci-btn-group only-active" role="group">
|
||||
<button class="btn btn-light btn-open-file"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<span class="fas fa-upload"></span><span class="hidden-xs"><%= t("protocols.index.import") %></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class="btn-link-alt btn-default-link btn-open-file" <%= can_create_protocols_in_repository?(@current_team) ? 'data-action="import"' : 'disabled="disabled"' %>>
|
||||
<a class="btn-link-alt btn-default-link btn-open-file" data-action="import">
|
||||
<span><%= t("protocols.index.import_alt") %></span>
|
||||
<input type="file" value="" accept=".eln" data-role="import-file-input"
|
||||
data-team-id="<%= @current_team.id %>" data-import-url="<%= import_protocols_path %>"
|
||||
|
@ -30,7 +30,7 @@
|
|||
<%= link_to t("protocols.index.import_protocols_io"), '', data: { target: '#protocolsioModal', toggle: 'modal' } %>
|
||||
</li>
|
||||
</ul>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="modal" id="newProtocolModal" tabindex="-1" role="dialog">
|
||||
<%= form_with model: @protocol || Protocol.new, url: type == 'new' ? protocols_path : copy_to_repository_protocol_path(@protocol), method: :post do |f| %>
|
||||
<%= form_with model: @protocol || Protocol.new, url: type == 'new' ? protocols_path : copy_to_repository_protocol_path(@protocol), method: :post, data: { action: type } do |f| %>
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
|
@ -32,7 +32,7 @@
|
|||
<%= f.label :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 %>
|
||||
<%= f.hidden_field :default_public_user_role_id, value: default_role %>
|
||||
<%= f.select :role_selector, options_for_select(user_roles_collection, default_role) %>
|
||||
<%= f.select :role_selector, options_for_select(team_user_roles_for_select, default_role) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
>
|
||||
<div class="protocol-comment-message">
|
||||
<div class="view-mode" data-placeholder="<%= t('protocols.index.versions.comment_placeholder') %>"><%= draft.version_comment %></div>
|
||||
<%= text_area_tag 'version_comment', draft.version_comment, disabled: true, class: 'smart-text-area hidden' %>
|
||||
<%= text_area_tag 'version_comment', draft.version_comment, disabled: can_publish_protocol_in_repository?(@protocol), class: 'smart-text-area hidden' %>
|
||||
</div>
|
||||
<div class="edit-buttons">
|
||||
<span class="cancel-button btn btn-secondary"><%= t('general.cancel') %></span>
|
||||
|
|
|
@ -31,10 +31,10 @@
|
|||
<span><i class="fas fa-sort-amount-down"></i></span>
|
||||
</button>
|
||||
<ul id="sortMenuDropdown" class="dropdown-menu sort-projects-menu dropdown-menu-right" aria-labelledby="sortMenu">
|
||||
<% {new: :newest, old: :oldest, atoz: :alpha_asc, ztoa: :alpha_desc}.each do |name, value| %>
|
||||
<% {atoz: :alpha_asc, ztoa: :alpha_desc, new: :newest, old: :oldest}.each do |name, value| %>
|
||||
<li>
|
||||
<input type='radio' name='sort_by' value='<%= value %>' <%= name == :new ? 'checked' : '' %> />
|
||||
<label for="<%= value %>"><%= t("general.sort.#{name.to_s}_html") %></label>
|
||||
<label for="<%= value %>"><%= t("protocols.index.protocolsio.sort.#{name.to_s}_html") %></label>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
|
|
@ -19,6 +19,9 @@ module PermissionExtends
|
|||
%w(
|
||||
READ
|
||||
READ_ARCHIVED
|
||||
ARCHIVE
|
||||
RESTORE
|
||||
PUBLISH
|
||||
MANAGE
|
||||
USERS_MANAGE
|
||||
MANAGE_DRAFT
|
||||
|
|
|
@ -2707,6 +2707,7 @@ en:
|
|||
head_title_archived: "Archived protocol templates"
|
||||
default_name: 'New protocol'
|
||||
back_to_active_protocols: 'Back to active protocols'
|
||||
search_bar_placeholder: 'Filter protocols'
|
||||
navigation:
|
||||
public: "Team protocols"
|
||||
private: "My protocols"
|
||||
|
@ -2740,7 +2741,7 @@ en:
|
|||
name: "Name"
|
||||
id: "ID"
|
||||
keywords: "Keywords"
|
||||
nr_of_linked_children: "No. of linked tasks"
|
||||
nr_of_linked_children: "Linked tasks"
|
||||
versions: "Versions"
|
||||
access: "Access"
|
||||
published_by: "Published by"
|
||||
|
@ -2853,6 +2854,11 @@ en:
|
|||
public: "Team protocols"
|
||||
private: "My protocols"
|
||||
success_flash: 'Protocol <strong>%{name}</strong> successfully imported.'
|
||||
sort:
|
||||
new_html: "<i class=\"fas fa-sort-numeric-up\"></i> Newest first"
|
||||
old_html: "<i class=\"fas fa-sort-numeric-down\"></i> Oldest first"
|
||||
atoz_html: "<i class=\"fas fa-sort-alpha-down\"></i> Name A to Z"
|
||||
ztoa_html: "<i class=\"fas fa-sort-alpha-up\"></i> Name Z to A"
|
||||
|
||||
steps:
|
||||
placeholder: 'Enter step name'
|
||||
|
@ -3207,6 +3213,7 @@ en:
|
|||
change_experiment_role: "Change experiment role"
|
||||
change_my_module_role: "Change task role"
|
||||
select_role: "Select role"
|
||||
assign_all_team_members: "Grant access to all team members"
|
||||
from_project: "%{user_role} [from project]"
|
||||
from_experiment: "%{user_role} [from experiment]"
|
||||
experiment_select_role: "Change experiment role"
|
||||
|
|
Loading…
Add table
Reference in a new issue