Merge branch 'features/protocol_versioning' into sb_SCI-8007

This commit is contained in:
sboursen-scinote 2023-03-09 16:35:36 +01:00
commit c4bdbdec9a
33 changed files with 322 additions and 164 deletions

View file

@ -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')) {

View file

@ -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();

View file

@ -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');
});
}());

View file

@ -22,7 +22,8 @@
}
.modal-body {
min-height: calc(100vh - 200px);
max-height: calc(100vh - 190px);
min-height: 370px;
overflow: auto;
padding-top: 0;
}

View file

@ -14,6 +14,10 @@
align-items: baseline;
display: flex;
a:first-of-type:focus {
outline: none;
}
.task-section-caret {
padding-right: .25em;
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -89,7 +89,7 @@
padding-left: 20px;
}
}
.details-container {
.protocol-details {
.protocol-metadata {
margin-bottom: 2em;

View file

@ -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|

View file

@ -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')

View file

@ -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"'
)

View file

@ -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

View file

@ -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>

View file

@ -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">

View file

@ -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

View file

@ -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|

View file

@ -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

View file

@ -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] %>

View file

@ -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] %>

View file

@ -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>

View file

@ -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>

View file

@ -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',

View file

@ -5,8 +5,10 @@
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</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>

View file

@ -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") %>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -19,6 +19,9 @@ module PermissionExtends
%w(
READ
READ_ARCHIVED
ARCHIVE
RESTORE
PUBLISH
MANAGE
USERS_MANAGE
MANAGE_DRAFT

View file

@ -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>&nbsp;&nbsp;Newest first"
old_html: "<i class=\"fas fa-sort-numeric-down\"></i>&nbsp;&nbsp;Oldest first"
atoz_html: "<i class=\"fas fa-sort-alpha-down\"></i>&nbsp;&nbsp;Name A to Z"
ztoa_html: "<i class=\"fas fa-sort-alpha-up\"></i>&nbsp;&nbsp;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"