Add versions and access columns to protocols table, update protocol model [SCI-7516]

This commit is contained in:
Oleksii Kriuchykhin 2022-12-09 12:44:41 +01:00
parent 9094dc08b5
commit 5fea4722ac
12 changed files with 276 additions and 142 deletions

View file

@ -55,21 +55,24 @@ function initProtocolsTable() {
</div>`; </div>`;
} }
}, { }, {
targets: [ 1, 2, 3, 4, 5 ], targets: [1, 2, 3, 4, 5, 6, 7, 8, 9],
searchable: true, searchable: true,
orderable: true orderable: true
}], }],
columns: [ columns: [
{ data: "0" }, { data: '0' },
{ data: "1" }, { data: '1' },
{ data: "2" }, { data: '2' },
{ data: '3' },
{ data: '4' },
{ {
data: "3", data: '5',
visible: repositoryType != "archive" visible: repositoryType !== 'archived'
}, },
{ data: "4" }, { data: '6' },
{ data: "5" }, { data: '7' },
{ data: "6" } { data: '8' },
{ data: '9' }
], ],
oLanguage: { oLanguage: {
sSearch: I18n.t('general.filter') sSearch: I18n.t('general.filter')
@ -230,7 +233,7 @@ function initKeywordFiltering() {
function initProtocolPreviewModal() { function initProtocolPreviewModal() {
// Only do this if the repository is public/private // Only do this if the repository is public/private
if (repositoryType !== "archive") { if (repositoryType !== 'archived') {
// If you are in protocol repository // If you are in protocol repository
var protocolsEl = protocolsTableEl; var protocolsEl = protocolsTableEl;
// If you are in search results // If you are in search results
@ -269,7 +272,7 @@ function initProtocolPreviewModal() {
function initLinkedChildrenModal() { function initLinkedChildrenModal() {
// Only do this if the repository is public/private // Only do this if the repository is public/private
if (repositoryType !== "archive") { if (repositoryType !== "archived") {
protocolsTableEl.on("click", "a[data-action='load-linked-children']", function(e) { protocolsTableEl.on("click", "a[data-action='load-linked-children']", function(e) {
var link = $(this); var link = $(this);
$.ajax({ $.ajax({

View file

@ -60,6 +60,51 @@
border: 0; border: 0;
height: calc(100vh - 167px); height: calc(100vh - 167px);
} }
.protocol-users-link {
align-items: center;
color: $color-silver-chalice;
display: flex;
&:hover {
text-decoration: none;
}
}
.users-access-cell {
a:hover {
text-decoration: none;
}
.value {
display: flex;
flex-wrap: wrap;
}
.global-avatar-container {
height: 2em;
line-height: 2em;
margin-right: .25em;
width: 2em;
}
.more-users {
background: $color-volcano;
border-radius: 50%;
color: $color-white;
height: 2em;
line-height: 2em;
margin-right: .25em;
text-align: center;
text-decoration: none;
width: 2em;
}
.new-user {
background: $color-concrete;
text-align: center;
}
}
} }
.tab-pane.external_protocols { .tab-pane.external_protocols {

View file

@ -232,7 +232,7 @@ class ProtocolsController < ApplicationController
def create def create
@protocol = Protocol.new( @protocol = Protocol.new(
team: @current_team, team: @current_team,
protocol_type: Protocol.protocol_types[@type == :public ? :in_repository_public : :in_repository_private], protocol_type: Protocol.protocol_types[:in_repository_draft],
added_by: current_user, added_by: current_user,
name: t('protocols.index.default_name') name: t('protocols.index.default_name')
) )
@ -1102,7 +1102,7 @@ class ProtocolsController < ApplicationController
def load_team_and_type def load_team_and_type
@current_team = current_team @current_team = current_team
# :public, :private or :archive # :public, :private or :archive
@type = (params[:type] || 'public').to_sym @type = (params[:type] || 'active').to_sym
end end
def check_view_all_permissions def check_view_all_permissions

View file

@ -23,12 +23,15 @@ class ProtocolsDatatable < CustomDatatable
def sortable_columns def sortable_columns
@sortable_columns ||= [ @sortable_columns ||= [
"Protocol.name", 'Protocol.name',
"protocol_keywords_str", 'Protocol.id',
"Protocol.nr_of_linked_children", 'nr_of_versions',
"full_username_str", 'protocol_keywords_str',
'Protocol.nr_of_linked_children',
'nr_of_assigned_users',
'full_username_str',
timestamp_db_column, timestamp_db_column,
"Protocol.updated_at" 'Protocol.updated_at'
] ]
end end
@ -77,57 +80,55 @@ class ProtocolsDatatable < CustomDatatable
# Returns json of current protocols (already paginated) # Returns json of current protocols (already paginated)
def data def data
result_data = [] records.map do |record|
records.each do |record| {
protocol = Protocol.find(record.id) DT_RowId: record.id,
result_data << { DT_CanClone: can_clone_protocol_in_repository?(record),
'DT_RowId': record.id, DT_CloneUrl: if can_clone_protocol_in_repository?(record)
'DT_RowAttr': { clone_protocol_path(record, team: @team, type: @type)
'data-permissions-url': permissions_protocol_path(protocol) end,
}, '1': record.archived? ? escape_input(record.name) : name_html(record),
'1': if protocol.in_repository_archived? '2': record.code,
escape_input(record.name) '3': versions_html(record),
else '4': keywords_html(record),
name_html(record) '5': modules_html(record),
end, '6': access_html(record),
'2': keywords_html(record), '7': escape_input(record.full_username_str),
'3': modules_html(record), '8': timestamp_column_html(record),
'4': escape_input(record.full_username_str), '9': I18n.l(record.updated_at, format: :full)
'5': timestamp_column_html(record),
'6': I18n.l(record.updated_at, format: :full)
} }
end end
result_data
end end
def get_raw_records_base def get_raw_records_base
records = records =
Protocol Protocol
.where(team: @team) .where(team: @team)
.joins('LEFT OUTER JOIN "protocol_protocol_keywords" ON "protocol_protocol_keywords"."protocol_id" = "protocols"."id"') .where('protocols.protocol_type = ? OR protocols.protocol_type = ? AND protocols.parent_id IS NULL',
.joins('LEFT OUTER JOIN "protocol_keywords" ON "protocol_protocol_keywords"."protocol_keyword_id" = "protocol_keywords"."id"') Protocol.protocol_types[:in_repository_published_original],
Protocol.protocol_types[:in_repository_draft])
.joins('LEFT OUTER JOIN "user_assignments" "all_user_assignments" '\
'ON "all_user_assignments"."assignable_type" = \'Protocol\' '\
'AND "all_user_assignments"."assignable_id" = "protocols"."id"')
.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 protocols protocol_drafts "\
"ON protocol_drafts.protocol_type = #{Protocol.protocol_types[:in_repository_draft]} "\
"AND protocol_drafts.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"')
.with_granted_permissions(@user, ProtocolPermissions::READ)
.preload(user_assignments: %i(user user_role))
if @type == :public records =
records = if @type == :archived
records records.joins('LEFT OUTER JOIN "users" ON "users"."id" = "protocols"."archived_by_id"').archived
.joins('LEFT OUTER JOIN users ON users.id = protocols.added_by_id') else
.where('protocols.protocol_type = ?', records.joins('LEFT OUTER JOIN "users" ON "users"."id" = "protocols"."added_by_id"').active
Protocol.protocol_types[:in_repository_public]) end
elsif @type == :private
records =
records
.joins('LEFT OUTER JOIN users ON users.id = protocols.added_by_id')
.where('protocols.protocol_type = ?',
Protocol.protocol_types[:in_repository_private])
.where(added_by: @user)
else
records =
records
.joins('LEFT OUTER JOIN users ON users.id = protocols.archived_by_id')
.where('protocols.protocol_type = ?',
Protocol.protocol_types[:in_repository_archived])
.where(added_by: @user)
end
records.group('"protocols"."id"') records.group('"protocols"."id"')
end end
@ -135,30 +136,23 @@ class ProtocolsDatatable < CustomDatatable
# Query database for records (this will be later paginated and filtered) # Query database for records (this will be later paginated and filtered)
# after that "data" function will return json # after that "data" function will return json
def get_raw_records def get_raw_records
get_raw_records_base get_raw_records_base.select(
.select( '"protocols".*',
'"protocols"."id"', 'STRING_AGG("protocol_keywords"."name", \', \') AS "protocol_keywords_str"',
'"protocols"."name"', 'COUNT("protocol_versions"."id") + 1 AS "nr_of_versions"',
'"protocols"."protocol_type"', 'COUNT("protocol_drafts"."id") AS "nr_of_drafts"',
'string_agg("protocol_keywords"."name", \', \') AS "protocol_keywords_str"', 'COUNT("user_assignments"."id") AS "nr_of_assigned_users"',
'"protocols"."nr_of_linked_children"', 'MAX("users"."full_name") AS "full_username_str"' # "Hack" to get single username
'max("users"."full_name") AS "full_username_str"', # "Hack" to get single username
'"protocols"."created_at"',
'"protocols"."updated_at"',
'"protocols"."published_on"',
'"protocols"."archived_on"'
) )
end end
# Various helper methods # Various helper methods
def timestamp_db_column def timestamp_db_column
if @type == :public if @type == :archived
"Protocol.published_on" 'Protocol.archived_on'
elsif @type == :private
"Protocol.created_at"
else else
"Protocol.archived_on" 'Protocol.published_on'
end end
end end
@ -188,13 +182,20 @@ class ProtocolsDatatable < CustomDatatable
"</a>" "</a>"
end end
def versions_html(record)
@view.controller
.render_to_string(partial: 'protocols/index/protocol_versions.html.erb', locals: { protocol: record })
end
def access_html(record)
@view.controller.render_to_string(partial: 'protocols/index/protocol_access.html.erb', locals: { protocol: record })
end
def timestamp_column_html(record) def timestamp_column_html(record)
if @type == :public if @type == :archived
I18n.l(record.published_on, format: :full)
elsif @type == :private
I18n.l(record.created_at, format: :full)
else
I18n.l(record.archived_on, format: :full) I18n.l(record.archived_on, format: :full)
else
I18n.l(record.published_on || record.created_at, format: :full)
end end
end end
@ -206,27 +207,27 @@ class ProtocolsDatatable < CustomDatatable
# using HAVING keyword (which is the correct way to filter aggregated 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 # Another OR is then appended to the WHERE clause, checking if protocol is inside
# this list of IDs. # this list of IDs.
def build_conditions_for(query) # def build_conditions_for(query)
# Inner query to retrieve list of protocol IDs where concatenated # # Inner query to retrieve list of protocol IDs where concatenated
# protocol keywords string, or user's full_name contains searched query # # protocol keywords string, or user's full_name contains searched query
search_val = dt_params[:search][:value] # search_val = dt_params[:search][:value]
records_having = get_raw_records_base.having( # records_having = get_raw_records_base.having(
::Arel::Nodes::NamedFunction.new( # ::Arel::Nodes::NamedFunction.new(
'CAST', # 'CAST',
[::Arel::Nodes::SqlLiteral.new("string_agg(\"protocol_keywords\".\"name\", ' ') AS #{typecast}")] # [::Arel::Nodes::SqlLiteral.new("string_agg(\"protocol_keywords\".\"name\", ' ') AS #{typecast}")]
).matches("%#{sanitize_sql_like(search_val)}%").to_sql + # ).matches("%#{sanitize_sql_like(search_val)}%").to_sql +
" OR " + # " OR " +
::Arel::Nodes::NamedFunction.new( # ::Arel::Nodes::NamedFunction.new(
'CAST', # 'CAST',
[::Arel::Nodes::SqlLiteral.new("max(\"users\".\"full_name\") AS #{typecast}")] # [::Arel::Nodes::SqlLiteral.new("max(\"users\".\"full_name\") AS #{typecast}")]
).matches("%#{sanitize_sql_like(search_val)}%").to_sql # ).matches("%#{sanitize_sql_like(search_val)}%").to_sql
).select(:id) # ).select(:id)
# Call parent function # # Call parent function
criteria = super(query) # criteria = super(query)
# Aight, now append another or # # Aight, now append another or
criteria = criteria.or(Protocol.arel_table[:id].in(records_having.arel)) # criteria = criteria.or(Protocol.arel_table[:id].in(records_having.arel))
criteria # criteria
end # end
end end

View file

@ -1,7 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
class Protocol < ApplicationRecord class Protocol < ApplicationRecord
ID_PREFIX = 'PT'
include ArchivableModel include ArchivableModel
include PrefixedIdModel
include SearchableModel include SearchableModel
include RenamingUtil include RenamingUtil
include SearchableByNameModel include SearchableByNameModel
@ -18,8 +21,9 @@ class Protocol < ApplicationRecord
enum protocol_type: { enum protocol_type: {
unlinked: 0, unlinked: 0,
linked: 1, linked: 1,
in_repository_published: 2, in_repository_published_original: 2,
in_repository_draft: 3 in_repository_draft: 3,
in_repository_published_version: 4
} }
auto_strip_attributes :name, :description, nullify: false auto_strip_attributes :name, :description, nullify: false
@ -28,9 +32,9 @@ class Protocol < ApplicationRecord
validates :description, length: { maximum: Constants::RICH_TEXT_MAX_LENGTH } validates :description, length: { maximum: Constants::RICH_TEXT_MAX_LENGTH }
validates :team, presence: true validates :team, presence: true
validates :protocol_type, presence: true validates :protocol_type, presence: true
validate :prevent_update, on: :update, if: :in_repository_published? validate :prevent_update,
# Only one draft can exist for each protocol on: :update,
validates :previous_version_id, uniqueness: true, if: -> { in_repository_draft? && previous_version_id.present? } if: -> { in_repository_published? && !protocol_type_changed?(from: 'in_repository_draft') }
with_options if: :in_module? do with_options if: :in_module? do
validates :my_module, presence: true validates :my_module, presence: true
@ -38,20 +42,27 @@ class Protocol < ApplicationRecord
validates :archived_on, absence: true validates :archived_on, absence: true
end end
with_options if: :linked? do with_options if: :linked? do
validate :parent_type_constrain validate :linked_parent_type_constrain
validates :added_by, presence: true validates :added_by, presence: true
validates :parent, presence: true validates :parent, presence: true
validates :parent_updated_at, presence: true validates :parent_updated_at, presence: true
end end
with_options if: :in_repository? do with_options if: :in_repository? do
validates :name, presence: true validates :name, presence: true
validate :versions_same_name_constrain
validates :added_by, presence: true validates :added_by, presence: true
validates :my_module, absence: true validates :my_module, absence: true
validates :parent, absence: true
validates :parent_updated_at, absence: true validates :parent_updated_at, absence: true
end end
with_options if: -> { in_repository? && active? && !previous_version } do |protocol| with_options if: :in_repository_published_version? do
validates :parent, presence: true
validate :versions_same_name_constrain
end
with_options if: :in_repository_draft? do
# Only one draft can exist for each protocol
validates :parent_id, uniqueness: true, if: -> { parent_id.present? }
validate :versions_same_name_constrain
end
with_options if: -> { in_repository? && active? && !parent } do |protocol|
# Active protocol must have unique name inside its team # Active protocol must have unique name inside its team
protocol protocol
.validates_uniqueness_of :name, case_sensitive: false, .validates_uniqueness_of :name, case_sensitive: false,
@ -59,7 +70,7 @@ class Protocol < ApplicationRecord
conditions: lambda { conditions: lambda {
active.where( active.where(
protocol_type: [ protocol_type: [
Protocol.protocol_types[:in_repository_published], Protocol.protocol_types[:in_repository_published_original],
Protocol.protocol_types[:in_repository_draft] Protocol.protocol_types[:in_repository_draft]
] ]
) )
@ -73,7 +84,7 @@ class Protocol < ApplicationRecord
conditions: lambda { conditions: lambda {
archived.where( archived.where(
protocol_type: [ protocol_type: [
Protocol.protocol_types[:in_repository_published], Protocol.protocol_types[:in_repository_published_original],
Protocol.protocol_types[:in_repository_draft] Protocol.protocol_types[:in_repository_draft]
] ]
) )
@ -285,13 +296,17 @@ class Protocol < ApplicationRecord
end end
def in_repository_active? def in_repository_active?
in_repository && active? in_repository? && active?
end end
def in_repository? def in_repository?
in_repository_published? || in_repository_draft? in_repository_published? || in_repository_draft?
end end
def in_repository_published?
in_repository_published_original? || in_repository_published_version?
end
def in_module? def in_module?
unlinked? || linked? unlinked? || linked?
end end
@ -725,14 +740,26 @@ class Protocol < ApplicationRecord
errors.add(:base, I18n.t('activerecord.errors.models.protocol.unchangable')) errors.add(:base, I18n.t('activerecord.errors.models.protocol.unchangable'))
end end
def parent_type_constrain def linked_parent_type_constrain
unless parent.in_repository_published? unless parent.in_repository_published?
errors.add(:base, I18n.t('activerecord.errors.models.protocol.wrong_parent_type')) errors.add(:base, I18n.t('activerecord.errors.models.protocol.wrong_parent_type'))
end end
end end
def version_parent_type_constrain
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_constrain
if previous_version.present? && !previous_version.name.eql?(name) if parent.present? && !parent.name.eql?(name)
errors.add(:base, I18n.t('activerecord.errors.models.protocol.wrong_version_name')) errors.add(:base, I18n.t('activerecord.errors.models.protocol.wrong_version_name'))
end end
end end

View file

@ -70,20 +70,34 @@ Canaid::Permissions.register_for(ProjectFolder) do
end end
Canaid::Permissions.register_for(Protocol) do Canaid::Permissions.register_for(Protocol) do
%i(read_protocol_in_repository
manage_protocol_in_repository
manage_protocol_users)
.each do |perm|
can perm do |_, protocol|
protocol.active?
end
end
# protocol in repository: read, export, read step, read/download step asset # protocol in repository: read, export, read step, read/download step asset
can :read_protocol_in_repository do |user, protocol| can :read_protocol_in_repository do |user, protocol|
protocol.in_repository_active? && protocol.permission_granted?(user, ProtocolPermissions::READ) protocol.permission_granted?(user, ProtocolPermissions::READ)
end end
# protocol in repository: update, create/update/delete/reorder step, # protocol in repository: update, create/update/delete/reorder step,
# toggle private/public visibility, archive # toggle private/public visibility, archive
can :manage_protocol_in_repository do |user, protocol| can :manage_protocol_in_repository do |user, protocol|
protocol.in_repository_active? && protocol.permission_granted?(user, ProtocolPermissions::MANAGE) protocol.permission_granted?(user, ProtocolPermissions::MANAGE)
end
can :manage_protocol_users do |user, protocol|
protocol.permission_granted?(user, ProtocolPermissions::USERS_MANAGE) ||
protocol.team.permission_granted?(user, TeamPermissions::MANAGE)
end end
# protocol in repository: restore # protocol in repository: restore
can :restore_protocol_in_repository do |user, protocol| can :restore_protocol_in_repository do |user, protocol|
protocol.in_repository_archived? && protocol.permission_granted?(user, ProtocolPermissions::MANAGE) protocol.archived? && protocol.permission_granted?(user, ProtocolPermissions::MANAGE)
end end
# protocol in repository: copy # protocol in repository: copy

View file

@ -0,0 +1,14 @@
<div class="users-access-cell">
<% if can_manage_protocol_users?(protocol) %>
<%= link_to edit_access_permissions_protocol_path(protocol), class: 'protocol-users-link', data: { action: 'remote-modal' } do %>
<%= render partial: 'protocols/index/users_list.html.erb', locals: { protocol: protocol } %>
<span class="new-user global-avatar-container">
<i class="fas fa-plus"></i>
</span>
<% end %>
<% else %>
<%= link_to access_permissions_protocol_path(protocol), class: 'protocol-users-link', data: { action: 'remote-modal' } do %>
<%= render partial: 'protocols/index/users_list.html.erb', locals: { protocol: protocol } %>
<% end %>
<% end %>
</div>

View file

@ -0,0 +1,10 @@
<%= link_to versions_modal_protocol_path(protocol), remote: true do %>
<% if protocol.in_repository_published_original? %>
<%= protocol.nr_of_versions %>
<% if protocol.nr_of_drafts.positive? %>
/ <%= t("protocols.index.table.draft") %>
<% end %>
<% elsif protocol.in_repository_draft? %>
<%= t("protocols.index.table.draft") %>
<% end %>
<% end %>

View file

@ -9,20 +9,20 @@
<span class="sci-checkbox-label"></span> <span class="sci-checkbox-label"></span>
</div> </div>
</th> </th>
<th id="protocol-name"><%= t("protocols.index.thead_name") %></th> <th id="protocol-name"><%= t("protocols.index.thead.name") %></th>
<th id="protocol-keywords"><%= t("protocols.index.thead_keywords") %></th> <th id="protocol-id"><%= t("protocols.index.thead.id") %></th>
<th id="protocol-nr-of-linked-children"><%= t("protocols.index.thead_nr_of_linked_children") %></th> <th id="protocol-versions"><%= t("protocols.index.thead.versions") %></th>
<% if @type == :public %> <th id="protocol-keywords"><%= t("protocols.index.thead.keywords") %></th>
<th id="protocol-published-by"><%= t("protocols.index.thead_published_by") %></th> <th id="protocol-nr-of-linked-children"><%= t("protocols.index.thead.nr_of_linked_children") %></th>
<th id="protocol-published-on"><%= t("protocols.index.thead_published_on") %></th> <th id="protocol-access"><%= t("protocols.index.thead.access") %></th>
<% elsif @type == :private %> <% if @type == :archived %>
<th id="protocol-added-by"><%= t("protocols.index.thead_added_by") %></th> <th id="protocol-archived-by"><%= t("protocols.index.thead.archived_by") %></th>
<th id="protocol-created-at"><%= t("protocols.index.thead_created_at") %></th> <th id="protocol-archived-on"><%= t("protocols.index.thead.archived_on") %></th>
<% else %> <% else %>
<th id="protocol-archived-by"><%= t("protocols.index.thead_archived_by") %></th> <th id="protocol-published-by"><%= t("protocols.index.thead.published_by") %></th>
<th id="protocol-archived-on"><%= t("protocols.index.thead_archived_on") %></th> <th id="protocol-published-on"><%= t("protocols.index.thead.published_on") %></th>
<% end %> <% end %>
<th id="protocol-updated-at"><%= t("protocols.index.thead_updated_at") %></th> <th id="protocol-updated-at"><%= t("protocols.index.thead.updated_at") %></th>
</tr> </tr>
</thead> </thead>
<tbody></tbody> <tbody></tbody>

View file

@ -0,0 +1,12 @@
<% protocol.user_assignments[0..3].each do |user_assigment| %>
<span class="global-avatar-container">
<%= image_tag(avatar_path(user_assigment.user, :icon_small), title: user_name_with_role(user_assigment)) %>
</span>
<% end %>
<% more_users = protocol.user_assignments[4..-1].to_a %>
<% if more_users.any? %>
<span class="more-users" title="<%= user_names_with_roles(more_users) %>">
+<%= more_users.size %>
</span>
<% end %>

View file

@ -2581,16 +2581,22 @@ en:
make_private: "Move to My Protocols" make_private: "Move to My Protocols"
publish: "Move to Team protocols" publish: "Move to Team protocols"
archive_action: "Archive" archive_action: "Archive"
thead_name: "Name" thead:
thead_keywords: "Keywords" name: "Name"
thead_nr_of_linked_children: "No. of linked tasks" id: "ID"
thead_published_by: "Published by" keywords: "Keywords"
thead_added_by: "Added by" nr_of_linked_children: "No. of linked tasks"
thead_archived_by: "Archived by" versions: "Versions"
thead_published_on: "Published at" access: "Access"
thead_created_at: "Created at" published_by: "Published by"
thead_archived_on: "Archived at" added_by: "Added by"
thead_updated_at: "Last modified at" archived_by: "Archived by"
published_on: "Published on"
created_at: "Created at"
archived_on: "Archived at"
updated_at: "Modified on"
table:
draft: "Draft"
preview: preview:
title: "%{protocol} preview" title: "%{protocol} preview"
linked_children: linked_children:

View file

@ -286,6 +286,7 @@ Rails.application.routes.draw do
resources :my_modules, only: %i(show update edit) resources :my_modules, only: %i(show update edit)
end end
end end
resources :protocols, only: %i(show update edit)
end end
resources :projects, except: [:destroy] do resources :projects, except: [:destroy] do
@ -551,6 +552,7 @@ Rails.application.routes.draw do
get 'linked_children', to: 'protocols#linked_children' get 'linked_children', to: 'protocols#linked_children'
post 'linked_children_datatable', post 'linked_children_datatable',
to: 'protocols#linked_children_datatable' to: 'protocols#linked_children_datatable'
get 'versions_modal', to: 'protocols#versions_modal'
get 'preview', to: 'protocols#preview' get 'preview', to: 'protocols#preview'
patch 'description', to: 'protocols#update_description' patch 'description', to: 'protocols#update_description'
patch 'name', to: 'protocols#update_name' patch 'name', to: 'protocols#update_name'