mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-04 12:14:37 +08:00
Merge branch 'develop' into features/design-update
This commit is contained in:
commit
b82e8da538
137 changed files with 1398 additions and 865 deletions
9
Gemfile
9
Gemfile
|
@ -58,7 +58,7 @@ gem 'jbuilder' # JSON structures via a Builder-style DSL
|
|||
gem 'logging', '~> 2.0.0'
|
||||
gem 'mime-types', '~> 3.4'
|
||||
gem 'nested_form_fields'
|
||||
gem 'nokogiri', '~> 1.18.8' # HTML/XML parser
|
||||
gem 'nokogiri', '~> 1.18.9' # HTML/XML parser
|
||||
gem 'noticed'
|
||||
gem 'oj'
|
||||
gem 'rails_autolink', '~> 1.1', '>= 1.1.6'
|
||||
|
@ -70,7 +70,6 @@ gem 'rubyzip', '>= 2.3.0' # will load new rubyzip version
|
|||
gem 'scenic', '~> 1.4'
|
||||
gem 'sdoc', '~> 1.0', group: :doc
|
||||
gem 'silencer' # Silence certain Rails logs
|
||||
gem 'sneaky-save', git: 'https://github.com/einzige/sneaky-save'
|
||||
gem 'turbolinks', '~> 5.2.0'
|
||||
gem 'underscore-rails'
|
||||
gem 'wicked_pdf'
|
||||
|
@ -95,8 +94,12 @@ gem 'js-routes'
|
|||
gem 'tailwindcss-rails', '~> 2.4'
|
||||
|
||||
gem 'base62' # Used for smart annotations
|
||||
gem 'datadog'
|
||||
gem 'newrelic_rpm'
|
||||
gem 'opentelemetry-exporter-otlp'
|
||||
gem 'opentelemetry-instrumentation-pg'
|
||||
gem 'opentelemetry-instrumentation-rails'
|
||||
gem 'opentelemetry-propagator-xray'
|
||||
gem 'opentelemetry-sdk'
|
||||
|
||||
# Permission helper Gem
|
||||
gem 'canaid', git: 'https://github.com/scinote-eln/canaid'
|
||||
|
|
233
Gemfile.lock
233
Gemfile.lock
|
@ -1,10 +1,3 @@
|
|||
GIT
|
||||
remote: https://github.com/einzige/sneaky-save
|
||||
revision: ee71d0a00cd4ecdd575bd2a9aa8b8693915f4871
|
||||
specs:
|
||||
sneaky-save (0.1.3)
|
||||
activerecord (>= 3.2.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/scinote-eln/canaid
|
||||
revision: bba1b817d1c9b0c7e0440a83d0f62848aabc0a1b
|
||||
|
@ -43,29 +36,29 @@ GIT
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.2.2.1)
|
||||
actionpack (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
actioncable (7.2.2.2)
|
||||
actionpack (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (7.2.2.1)
|
||||
actionpack (= 7.2.2.1)
|
||||
activejob (= 7.2.2.1)
|
||||
activerecord (= 7.2.2.1)
|
||||
activestorage (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
actionmailbox (7.2.2.2)
|
||||
actionpack (= 7.2.2.2)
|
||||
activejob (= 7.2.2.2)
|
||||
activerecord (= 7.2.2.2)
|
||||
activestorage (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
mail (>= 2.8.0)
|
||||
actionmailer (7.2.2.1)
|
||||
actionpack (= 7.2.2.1)
|
||||
actionview (= 7.2.2.1)
|
||||
activejob (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
actionmailer (7.2.2.2)
|
||||
actionpack (= 7.2.2.2)
|
||||
actionview (= 7.2.2.2)
|
||||
activejob (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
mail (>= 2.8.0)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (7.2.2.1)
|
||||
actionview (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
actionpack (7.2.2.2)
|
||||
actionview (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
nokogiri (>= 1.8.5)
|
||||
racc
|
||||
rack (>= 2.2.4, < 3.2)
|
||||
|
@ -74,15 +67,15 @@ GEM
|
|||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
useragent (~> 0.16)
|
||||
actiontext (7.2.2.1)
|
||||
actionpack (= 7.2.2.1)
|
||||
activerecord (= 7.2.2.1)
|
||||
activestorage (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
actiontext (7.2.2.2)
|
||||
actionpack (= 7.2.2.2)
|
||||
activerecord (= 7.2.2.2)
|
||||
activestorage (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
actionview (7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
|
@ -92,14 +85,14 @@ GEM
|
|||
activemodel (>= 4.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
activejob (7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
activerecord (7.2.2.1)
|
||||
activemodel (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
activemodel (7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
activerecord (7.2.2.2)
|
||||
activemodel (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
timeout (>= 0.4.0)
|
||||
activerecord-import (2.2.0)
|
||||
activerecord (>= 4.2)
|
||||
|
@ -110,13 +103,13 @@ GEM
|
|||
multi_json (~> 1.11, >= 1.11.2)
|
||||
rack (>= 2.0.8, < 4)
|
||||
railties (>= 6.1)
|
||||
activestorage (7.2.2.1)
|
||||
actionpack (= 7.2.2.1)
|
||||
activejob (= 7.2.2.1)
|
||||
activerecord (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
activestorage (7.2.2.2)
|
||||
actionpack (= 7.2.2.2)
|
||||
activejob (= 7.2.2.2)
|
||||
activerecord (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
marcel (~> 1.0)
|
||||
activesupport (7.2.2.1)
|
||||
activesupport (7.2.2.2)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
|
@ -195,14 +188,14 @@ GEM
|
|||
aws-sigv4 (1.12.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
base62 (1.0.0)
|
||||
base64 (0.2.0)
|
||||
base64 (0.3.0)
|
||||
bcrypt (3.1.18)
|
||||
benchmark (0.4.0)
|
||||
benchmark (0.4.1)
|
||||
better_errors (2.10.1)
|
||||
erubi (>= 1.0.0)
|
||||
rack (>= 0.9.0)
|
||||
rouge (>= 1.0.0)
|
||||
bigdecimal (3.2.0)
|
||||
bigdecimal (3.2.2)
|
||||
bindata (2.5.0)
|
||||
binding_of_caller (1.0.0)
|
||||
debug_inspector (>= 0.0.1)
|
||||
|
@ -292,13 +285,6 @@ GEM
|
|||
activerecord (>= 5.a)
|
||||
database_cleaner-core (~> 2.0.0)
|
||||
database_cleaner-core (2.0.1)
|
||||
datadog (2.14.0)
|
||||
datadog-ruby_core_source (~> 3.4)
|
||||
libdatadog (~> 16.0.1.1.0)
|
||||
libddwaf (~> 1.21.0.0.1)
|
||||
logger
|
||||
msgpack
|
||||
datadog-ruby_core_source (3.4.0)
|
||||
date (3.4.1)
|
||||
debug_inspector (1.1.0)
|
||||
deface (1.9.0)
|
||||
|
@ -361,6 +347,14 @@ GEM
|
|||
raabro (~> 1.4)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
google-protobuf (4.31.1-arm64-darwin)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.31.1-x86_64-linux-gnu)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
googleapis-common-protos-types (1.20.0)
|
||||
google-protobuf (>= 3.18, < 5.a)
|
||||
graphviz (1.2.1)
|
||||
process-pipeline
|
||||
grover (1.2.3)
|
||||
|
@ -430,12 +424,6 @@ GEM
|
|||
activerecord
|
||||
kaminari-core (= 1.2.2)
|
||||
kaminari-core (1.2.2)
|
||||
libdatadog (16.0.1.1.0)
|
||||
libdatadog (16.0.1.1.0-x86_64-linux)
|
||||
libddwaf (1.21.0.0.1-arm64-darwin)
|
||||
ffi (~> 1.0)
|
||||
libddwaf (1.21.0.0.1-x86_64-linux)
|
||||
ffi (~> 1.0)
|
||||
listen (3.8.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
|
@ -486,9 +474,9 @@ GEM
|
|||
net-protocol
|
||||
newrelic_rpm (9.14.0)
|
||||
nio4r (2.7.4)
|
||||
nokogiri (1.18.8-arm64-darwin)
|
||||
nokogiri (1.18.9-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.8-x86_64-linux-gnu)
|
||||
nokogiri (1.18.9-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
noticed (1.6.3)
|
||||
http (>= 4.0.0)
|
||||
|
@ -536,6 +524,82 @@ GEM
|
|||
validate_email
|
||||
validate_url
|
||||
webfinger (~> 2.0)
|
||||
opentelemetry-api (1.5.0)
|
||||
opentelemetry-common (0.22.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-exporter-otlp (0.30.0)
|
||||
google-protobuf (>= 3.18)
|
||||
googleapis-common-protos-types (~> 1.3)
|
||||
opentelemetry-api (~> 1.1)
|
||||
opentelemetry-common (~> 0.20)
|
||||
opentelemetry-sdk (~> 1.2)
|
||||
opentelemetry-semantic_conventions
|
||||
opentelemetry-helpers-sql (0.1.1)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-helpers-sql-obfuscation (0.3.0)
|
||||
opentelemetry-common (~> 0.21)
|
||||
opentelemetry-instrumentation-action_mailer (0.4.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-active_support (~> 0.7)
|
||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||
opentelemetry-instrumentation-action_pack (0.12.3)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||
opentelemetry-instrumentation-rack (~> 0.21)
|
||||
opentelemetry-instrumentation-action_view (0.9.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-active_support (~> 0.7)
|
||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||
opentelemetry-instrumentation-active_job (0.8.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||
opentelemetry-instrumentation-active_record (0.9.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||
opentelemetry-instrumentation-active_storage (0.1.1)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-active_support (~> 0.7)
|
||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||
opentelemetry-instrumentation-active_support (0.8.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||
opentelemetry-instrumentation-base (0.23.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-common (~> 0.21)
|
||||
opentelemetry-registry (~> 0.1)
|
||||
opentelemetry-instrumentation-concurrent_ruby (0.22.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||
opentelemetry-instrumentation-pg (0.30.1)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-helpers-sql
|
||||
opentelemetry-helpers-sql-obfuscation
|
||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||
opentelemetry-instrumentation-rack (0.26.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||
opentelemetry-instrumentation-rails (0.36.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-action_mailer (~> 0.4.0)
|
||||
opentelemetry-instrumentation-action_pack (~> 0.12.0)
|
||||
opentelemetry-instrumentation-action_view (~> 0.9.0)
|
||||
opentelemetry-instrumentation-active_job (~> 0.8.0)
|
||||
opentelemetry-instrumentation-active_record (~> 0.9.0)
|
||||
opentelemetry-instrumentation-active_storage (~> 0.1.0)
|
||||
opentelemetry-instrumentation-active_support (~> 0.8.0)
|
||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||
opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0)
|
||||
opentelemetry-propagator-xray (0.24.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-registry (0.4.0)
|
||||
opentelemetry-api (~> 1.1)
|
||||
opentelemetry-sdk (1.8.0)
|
||||
opentelemetry-api (~> 1.1)
|
||||
opentelemetry-common (~> 0.20)
|
||||
opentelemetry-registry (~> 0.2)
|
||||
opentelemetry-semantic_conventions
|
||||
opentelemetry-semantic_conventions (1.11.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
orm_adapter (0.5.0)
|
||||
ostruct (0.6.0)
|
||||
overcommit (0.60.0)
|
||||
|
@ -597,20 +661,20 @@ GEM
|
|||
rackup (1.0.1)
|
||||
rack (< 3)
|
||||
webrick
|
||||
rails (7.2.2.1)
|
||||
actioncable (= 7.2.2.1)
|
||||
actionmailbox (= 7.2.2.1)
|
||||
actionmailer (= 7.2.2.1)
|
||||
actionpack (= 7.2.2.1)
|
||||
actiontext (= 7.2.2.1)
|
||||
actionview (= 7.2.2.1)
|
||||
activejob (= 7.2.2.1)
|
||||
activemodel (= 7.2.2.1)
|
||||
activerecord (= 7.2.2.1)
|
||||
activestorage (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
rails (7.2.2.2)
|
||||
actioncable (= 7.2.2.2)
|
||||
actionmailbox (= 7.2.2.2)
|
||||
actionmailer (= 7.2.2.2)
|
||||
actionpack (= 7.2.2.2)
|
||||
actiontext (= 7.2.2.2)
|
||||
actionview (= 7.2.2.2)
|
||||
activejob (= 7.2.2.2)
|
||||
activemodel (= 7.2.2.2)
|
||||
activerecord (= 7.2.2.2)
|
||||
activestorage (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.2.2.1)
|
||||
railties (= 7.2.2.2)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
|
@ -626,9 +690,9 @@ GEM
|
|||
actionview (> 3.1)
|
||||
activesupport (> 3.1)
|
||||
railties (> 3.1)
|
||||
railties (7.2.2.1)
|
||||
actionpack (= 7.2.2.1)
|
||||
activesupport (= 7.2.2.1)
|
||||
railties (7.2.2.2)
|
||||
actionpack (= 7.2.2.2)
|
||||
activesupport (= 7.2.2.2)
|
||||
irb (~> 1.13)
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
|
@ -698,7 +762,7 @@ GEM
|
|||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-saml (1.18.0)
|
||||
ruby-saml (1.18.1)
|
||||
nokogiri (>= 1.13.10)
|
||||
rexml
|
||||
ruby-vips (2.1.4)
|
||||
|
@ -750,7 +814,7 @@ GEM
|
|||
railties (>= 6.0.0)
|
||||
tailwindcss-rails (2.4.0-x86_64-linux)
|
||||
railties (>= 6.0.0)
|
||||
thor (1.3.2)
|
||||
thor (1.4.0)
|
||||
tilt (2.4.0)
|
||||
timecop (0.9.6)
|
||||
timeout (0.4.3)
|
||||
|
@ -836,7 +900,6 @@ DEPENDENCIES
|
|||
cssbundling-rails
|
||||
cucumber-rails
|
||||
database_cleaner
|
||||
datadog
|
||||
deface (~> 1.9)
|
||||
delayed_job_active_record
|
||||
devise (~> 4.9.4)
|
||||
|
@ -866,7 +929,7 @@ DEPENDENCIES
|
|||
mime-types (~> 3.4)
|
||||
nested_form_fields
|
||||
newrelic_rpm
|
||||
nokogiri (~> 1.18.8)
|
||||
nokogiri (~> 1.18.9)
|
||||
noticed
|
||||
oj
|
||||
omniauth (~> 2.1)
|
||||
|
@ -876,6 +939,11 @@ DEPENDENCIES
|
|||
omniauth-rails_csrf_protection (~> 1.0)
|
||||
omniauth-saml
|
||||
omniauth_openid_connect
|
||||
opentelemetry-exporter-otlp
|
||||
opentelemetry-instrumentation-pg
|
||||
opentelemetry-instrumentation-rails
|
||||
opentelemetry-propagator-xray
|
||||
opentelemetry-sdk
|
||||
overcommit
|
||||
pg (~> 1.5)
|
||||
pg_search
|
||||
|
@ -907,7 +975,6 @@ DEPENDENCIES
|
|||
shoulda-matchers
|
||||
silencer
|
||||
simplecov
|
||||
sneaky-save!
|
||||
sprockets-rails
|
||||
tailwindcss-rails (~> 2.4)
|
||||
timecop
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.43.0.1
|
||||
1.44.0
|
||||
|
|
|
@ -3,17 +3,6 @@
|
|||
const protocolModal = '#newProtocolModal';
|
||||
const submitButton = $('.create-protocol-button');
|
||||
|
||||
let roleSelector = `${protocolModal} #protocol_role_selector`;
|
||||
dropdownSelector.init(roleSelector, {
|
||||
noEmptyOption: true,
|
||||
singleSelect: true,
|
||||
closeOnSelect: true,
|
||||
selectAppearance: 'simple',
|
||||
onChange: function() {
|
||||
$('#protocol_default_public_user_role_id').val(dropdownSelector.getValues(roleSelector));
|
||||
}
|
||||
});
|
||||
|
||||
$(protocolModal)
|
||||
.on('input', '#protocol_name', function() {
|
||||
if ($(this).val().length >= GLOBAL_CONSTANTS.NAME_MIN_LENGTH) {
|
||||
|
@ -22,11 +11,6 @@
|
|||
submitButton.attr('disabled', 'disabled');
|
||||
}
|
||||
})
|
||||
.on('change', '#protocol_visibility', function() {
|
||||
let checked = $(this)[0].checked;
|
||||
$('#roleSelectWrapper').toggleClass('hidden', !checked);
|
||||
$('#protocol_default_public_user_role_id').prop('disabled', !checked);
|
||||
})
|
||||
.on('submit', function() {
|
||||
submitButton.attr('disabled', 'disabled');
|
||||
})
|
||||
|
|
|
@ -198,15 +198,6 @@ var protocolsIO = function() {
|
|||
e.stopPropagation();
|
||||
animateSpinner(modal, true);
|
||||
|
||||
const visibility = $('#protocol-preview-modal .modal-footer #visibility').prop('checked');
|
||||
const defaultPublicUserRoleId = $('#protocol-preview-modal .modal-footer #default_public_user_role_id')
|
||||
.prop('value');
|
||||
const visibilityField = $('#protocol-preview-modal #protocol_visibility');
|
||||
const defaultPublicUserRoleIdField = $('#protocol-preview-modal #protocol_default_public_user_role_id');
|
||||
|
||||
visibilityField.prop('value', visibility ? 'visible' : 'hidden');
|
||||
defaultPublicUserRoleIdField.prop('value', defaultPublicUserRoleId);
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
|
@ -291,24 +282,6 @@ var protocolsIO = function() {
|
|||
$('form.protocols-search-bar').submit();
|
||||
|
||||
function initProtocolModalPreview() {
|
||||
$('#protocol-preview-modal').on('change', '#visibility', function() {
|
||||
const checkbox = this;
|
||||
$('#protocol-preview-modal #roleSelectWrapper').toggleClass('hidden', !checkbox.checked);
|
||||
});
|
||||
|
||||
|
||||
const roleSelector = '#protocol-preview-modal #role_selector';
|
||||
|
||||
dropdownSelector.init(roleSelector, {
|
||||
noEmptyOption: true,
|
||||
singleSelect: true,
|
||||
closeOnSelect: true,
|
||||
selectAppearance: 'simple',
|
||||
onChange: function() {
|
||||
$('#protocol-preview-modal #default_public_user_role_id').val(dropdownSelector.getValues(roleSelector));
|
||||
}
|
||||
});
|
||||
|
||||
$('#protocol-preview-modal')
|
||||
.on('ajax:error', 'form', function(e, error) {
|
||||
let msg = error.responseJSON.error;
|
||||
|
|
|
@ -150,7 +150,11 @@
|
|||
if (data.status === 'done') {
|
||||
// Reload the whole table
|
||||
HelperModule.flashAlertMsg(jobData.success_message, 'success');
|
||||
usersDatatable.ajax.reload();
|
||||
if(jobData.redirect_url) {
|
||||
window.location.href = jobData.redirect_url;
|
||||
} else {
|
||||
usersDatatable.ajax.reload();
|
||||
}
|
||||
animateSpinner(null, false);
|
||||
$('#destroy-user-team-modal').modal('hide');
|
||||
clearInterval(jobStatusInterval);
|
||||
|
|
|
@ -75,6 +75,8 @@ module AccessPermissions
|
|||
end
|
||||
|
||||
propagate_job
|
||||
|
||||
render json: { user_role_id: @assignment.user_role_id }, status: :ok
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
render json: { flash: t('access_permissions.update.failure') }, status: :unprocessable_entity
|
||||
end
|
||||
|
@ -106,7 +108,7 @@ module AccessPermissions
|
|||
end
|
||||
|
||||
def show_user_group_assignments
|
||||
render json: @model.user_group_assignments.includes(:user_role, :user_group).order('user_groups.name ASC'),
|
||||
render json: @model.user_group_assignments.where(team: current_team).includes(:user_role, :user_group).order('user_groups.name ASC'),
|
||||
each_serializer: UserGroupAssignmentSerializer, user: current_user
|
||||
end
|
||||
|
||||
|
@ -122,7 +124,7 @@ module AccessPermissions
|
|||
private
|
||||
|
||||
def model_parameter
|
||||
@model.class.permission_class.name.parameterize.to_sym
|
||||
@model.class.permission_class.model_name.param_key
|
||||
end
|
||||
|
||||
def manage_permission_constant
|
||||
|
|
|
@ -7,7 +7,7 @@ module AccessPermissions
|
|||
def update
|
||||
if permitted_params[:user_role_id] == 'reset'
|
||||
parent_assignment = @project.public_send(:"#{assignment_type}_assignments").find_or_initialize_by(
|
||||
"#{assignment_type}_id": permitted_params[:"#{assignment_type}_id"],
|
||||
"#{assignment_type}_id": permitted_params[:"#{assignment_type}_id"] || current_team.id,
|
||||
team: current_team
|
||||
)
|
||||
|
||||
|
@ -24,16 +24,18 @@ module AccessPermissions
|
|||
)
|
||||
end
|
||||
|
||||
UserAssignments::PropagateAssignmentJob.perform_later(@assignment, destroy: permitted_params[:user_role_id] == 'reset')
|
||||
UserAssignments::PropagateAssignmentJob.perform_later(@assignment)
|
||||
|
||||
case assignment_type
|
||||
when :team
|
||||
log_activity(:experiment_access_changed_all_team_members, team: @assignment.team.id, role: @assignment.user_role.name)
|
||||
when :user_group
|
||||
log_activity(:experiment_access_changed_user_group, user_group: @assignment.user_group.id, role: @assignment.user_role.name)
|
||||
when :user
|
||||
log_activity(:change_user_role_on_experiment, user_target: @assignment.user.id, role: @assignment.user_role.name)
|
||||
end
|
||||
|
||||
render json: {}, status: :ok
|
||||
render json: { user_role_id: @assignment.user_role_id }, status: :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -8,7 +8,7 @@ module AccessPermissions
|
|||
def update
|
||||
if permitted_params[:user_role_id] == 'reset'
|
||||
parent_assignment = @experiment.public_send(:"#{assignment_type}_assignments").find_or_initialize_by(
|
||||
"#{assignment_type}_id": permitted_params[:"#{assignment_type}_id"],
|
||||
"#{assignment_type}_id": permitted_params[:"#{assignment_type}_id"] || current_team.id,
|
||||
team: current_team
|
||||
)
|
||||
|
||||
|
@ -26,11 +26,15 @@ module AccessPermissions
|
|||
end
|
||||
|
||||
case assignment_type
|
||||
when :team
|
||||
log_activity(:my_module_access_changed_all_team_members, team: @assignment.team.id, role: @assignment.user_role.name)
|
||||
when :user_group
|
||||
log_activity(:my_module_access_changed_user_group, user_group: @assignment.user_group.id, role: @assignment.user_role.name)
|
||||
when :user
|
||||
log_activity(:change_user_role_on_my_module, user_target: @assignment.user.id, role: @assignment.user_role.name)
|
||||
end
|
||||
|
||||
render json: { user_role_id: @assignment.user_role_id }, status: :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -15,7 +15,7 @@ module AccessPermissions
|
|||
end
|
||||
|
||||
def check_read_permissions
|
||||
render_403 unless can_read_repository?(@model) || can_manage_team?(@model.team)
|
||||
render_403 unless can_manage_repository_users?(@model) || can_read_repository?(@model)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -35,12 +35,7 @@ module Api
|
|||
|
||||
@user_assignment.update!(user_assignment_params.merge(assigned: :manually))
|
||||
|
||||
UserAssignments::PropagateAssignmentJob.perform_later(
|
||||
@experiment,
|
||||
@user_assignment.user_id,
|
||||
@user_assignment.user_role,
|
||||
current_user.id
|
||||
)
|
||||
UserAssignments::PropagateAssignmentJob.perform_later(@user_assignment)
|
||||
|
||||
log_change_activity
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ module Api
|
|||
def index
|
||||
inventories =
|
||||
timestamps_filter(@team.repositories).active
|
||||
.readable_by_user(current_user)
|
||||
.page(params.dig(:page, :number))
|
||||
.per(params.dig(:page, :size))
|
||||
|
||||
|
|
|
@ -110,10 +110,7 @@ module Api
|
|||
|
||||
def propagate_job(user_assignment, destroy: false)
|
||||
UserAssignments::PropagateAssignmentJob.perform_later(
|
||||
@project,
|
||||
user_assignment.user.id,
|
||||
user_assignment.user_role,
|
||||
current_user.id,
|
||||
user_assignment,
|
||||
destroy: destroy
|
||||
)
|
||||
end
|
||||
|
|
|
@ -32,32 +32,57 @@ module Api
|
|||
def create
|
||||
raise PermissionError.new(Project, :create) unless can_create_projects?(@team)
|
||||
|
||||
project = @team.projects.build(project_params.merge!(created_by: current_user))
|
||||
ActiveRecord::Base.transaction do
|
||||
project = @team.projects.build(project_params.merge!(created_by: current_user))
|
||||
|
||||
if project.visible? # set default viewer role for public projects
|
||||
project.default_public_user_role = UserRole.predefined.find_by(name: I18n.t('user_roles.predefined.viewer'))
|
||||
project.save!
|
||||
|
||||
if project_params[:visibility] == 'visible'
|
||||
project.team_assignments.create!(
|
||||
team: project.team,
|
||||
user_role: UserRole.find_predefined_viewer_role,
|
||||
assigned_by: current_user,
|
||||
assigned: :manually
|
||||
)
|
||||
end
|
||||
|
||||
render jsonapi: project, serializer: ProjectSerializer, scope: { metadata: params['with-metadata'] == 'true' }, status: :created
|
||||
end
|
||||
|
||||
project.save!
|
||||
|
||||
render jsonapi: project, serializer: ProjectSerializer, scope: { metadata: params['with-metadata'] == 'true' }, status: :created
|
||||
end
|
||||
|
||||
def update
|
||||
@project.assign_attributes(project_params)
|
||||
|
||||
return render body: nil, status: :no_content unless @project.changed?
|
||||
return render body: nil, status: :no_content if !@project.changed? && project_params[:visibility].blank?
|
||||
|
||||
if @project.archived_changed?
|
||||
if @project.archived?
|
||||
@project.archived_by = current_user
|
||||
else
|
||||
@project.restored_by = current_user
|
||||
ActiveRecord::Base.transaction do
|
||||
if @project.archived_changed?
|
||||
if @project.archived?
|
||||
@project.archived_by = current_user
|
||||
else
|
||||
@project.restored_by = current_user
|
||||
end
|
||||
end
|
||||
@project.last_modified_by = current_user
|
||||
@project.save!
|
||||
|
||||
if project_params[:visibility].present?
|
||||
team_assignment = @project.team_assignments.find_by(team: @team)
|
||||
|
||||
if project_params[:visibility] == 'hidden' && team_assignment.present?
|
||||
team_assignment.destroy!
|
||||
elsif project_params[:visibility] == 'visible' && team_assignment.blank?
|
||||
@project.team_assignments.create!(
|
||||
team: @project.team,
|
||||
user_role: UserRole.find_predefined_viewer_role,
|
||||
assigned_by: current_user,
|
||||
assigned: :manually
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
render jsonapi: @project, serializer: ProjectSerializer, scope: { metadata: params['with-metadata'] == 'true' }, status: :ok
|
||||
end
|
||||
@project.last_modified_by = current_user
|
||||
@project.save!
|
||||
render jsonapi: @project, serializer: ProjectSerializer, scope: { metadata: params['with-metadata'] == 'true' }, status: :ok
|
||||
end
|
||||
|
||||
def activities
|
||||
|
|
|
@ -7,16 +7,18 @@ module Api
|
|||
before_action :load_project
|
||||
before_action :load_experiment
|
||||
before_action :load_task
|
||||
before_action :load_my_module_repository_row, only: :update
|
||||
before_action :load_inventory_item, only: %i(show destroy update)
|
||||
before_action :load_task_inventory_item, only: %i(update destroy)
|
||||
before_action :check_repository_view_permissions, only: :show
|
||||
before_action :check_stock_consumption_update_permissions, only: :update
|
||||
before_action :check_task_assign_permissions, only: %i(create destroy)
|
||||
|
||||
def index
|
||||
items =
|
||||
timestamps_filter(@task.repository_rows).includes(repository_cells: :repository_column)
|
||||
.preload(repository_cells: :value)
|
||||
.page(params.dig(:page, :number))
|
||||
.per(params.dig(:page, :size))
|
||||
items = @task.repository_rows.where(repository_id: Repository.readable_by_user(current_user).select(:id))
|
||||
items = timestamps_filter(items).includes(repository_cells: :repository_column)
|
||||
.preload(repository_cells: :value)
|
||||
.page(params.dig(:page, :number))
|
||||
.per(params.dig(:page, :size))
|
||||
render jsonapi: items,
|
||||
each_serializer: TaskInventoryItemSerializer,
|
||||
show_repository: true,
|
||||
|
@ -25,7 +27,7 @@ module Api
|
|||
end
|
||||
|
||||
def show
|
||||
render jsonapi: @task.repository_rows.find(params.require(:id)),
|
||||
render jsonapi: @inventory_item,
|
||||
serializer: TaskInventoryItemSerializer,
|
||||
show_repository: true,
|
||||
my_module: @task,
|
||||
|
@ -39,21 +41,21 @@ module Api
|
|||
|
||||
@task.my_module_repository_rows.create!(repository_row: @inventory_item, assigned_by: current_user)
|
||||
|
||||
render jsonapi: @task.repository_rows,
|
||||
each_serializer: TaskInventoryItemSerializer,
|
||||
render jsonapi: @inventory_item,
|
||||
serializer: TaskInventoryItemSerializer,
|
||||
show_repository: true,
|
||||
my_module: @task,
|
||||
include: include_params
|
||||
end
|
||||
|
||||
def update
|
||||
@my_module_repository_row.consume_stock(
|
||||
@task_inventory_item.consume_stock(
|
||||
current_user,
|
||||
repository_row_params[:attributes][:stock_consumption],
|
||||
repository_row_params[:attributes][:stock_consumption_comment]
|
||||
)
|
||||
|
||||
render jsonapi: @my_module_repository_row.repository_row,
|
||||
render jsonapi: @inventory_item,
|
||||
serializer: TaskInventoryItemSerializer,
|
||||
show_repository: true,
|
||||
my_module: @task,
|
||||
|
@ -61,29 +63,27 @@ module Api
|
|||
end
|
||||
|
||||
def destroy
|
||||
@inventory_item = @task.repository_rows.find(params.require(:id))
|
||||
|
||||
raise PermissionError.new(Repository, :read) unless @inventory_item && can_read_repository?(@inventory_item.repository)
|
||||
|
||||
@task.my_module_repository_rows.find_by(repository_row: @inventory_item).destroy!
|
||||
@task_inventory_item.destroy!
|
||||
|
||||
render body: nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_my_module_repository_row
|
||||
@my_module_repository_row = @task.repository_rows
|
||||
.find(params.require(:id))
|
||||
.my_module_repository_rows
|
||||
.find_by(my_module: @task)
|
||||
def load_inventory_item
|
||||
@inventory_item = @task.repository_rows.find(params.require(:id))
|
||||
end
|
||||
|
||||
def load_task_inventory_item
|
||||
@task_inventory_item = @task.my_module_repository_rows.find_by!(repository_row: @inventory_item)
|
||||
end
|
||||
|
||||
def check_repository_view_permissions
|
||||
raise PermissionError.new(RepositoryRow, :read_repository) unless can_read_repository?(@inventory_item.repository)
|
||||
end
|
||||
|
||||
def check_stock_consumption_update_permissions
|
||||
unless can_update_my_module_stock_consumption?(@task) &&
|
||||
can_manage_repository_rows?(@my_module_repository_row.repository_row.repository)
|
||||
raise PermissionError.new(RepositoryRow, :update_stock_consumption)
|
||||
end
|
||||
raise PermissionError.new(RepositoryRow, :update_stock_consumption) if @inventory_item.archived? || !can_update_my_module_stock_consumption?(@task)
|
||||
end
|
||||
|
||||
def check_task_assign_permissions
|
||||
|
|
|
@ -9,13 +9,13 @@ module Dashboard
|
|||
date = params[:date].to_date
|
||||
start_date = date.beginning_of_month - 8.days
|
||||
end_date = date.end_of_month + 15.days
|
||||
due_dates = current_user.my_modules.readable_by_user(current_user).active.uncomplete
|
||||
.joins(experiment: :project)
|
||||
.where(experiments: { archived: false })
|
||||
.where(projects: { archived: false })
|
||||
.where(my_modules: { due_date: start_date..end_date })
|
||||
.joins(:protocols).where(protocols: { team_id: current_team.id })
|
||||
.pluck(:due_date)
|
||||
due_dates = MyModule.readable_by_user(current_user).active.uncomplete
|
||||
.joins(experiment: :project)
|
||||
.where(experiments: { archived: false })
|
||||
.where(projects: { archived: false })
|
||||
.where(my_modules: { due_date: start_date..end_date })
|
||||
.joins(:protocols).where(protocols: { team_id: current_team.id })
|
||||
.pluck(:due_date)
|
||||
render json: { events: due_dates.map { |i| { date: i.to_date } } }
|
||||
end
|
||||
|
||||
|
@ -23,12 +23,12 @@ module Dashboard
|
|||
date = params[:date].to_date
|
||||
start_date = date.beginning_of_day
|
||||
end_date = date.end_of_day
|
||||
my_modules = current_user.my_modules.readable_by_user(current_user).active.uncomplete
|
||||
.joins(experiment: :project)
|
||||
.where(experiments: { archived: false })
|
||||
.where(projects: { archived: false })
|
||||
.where(my_modules: { due_date: start_date..end_date })
|
||||
.where(projects: { team_id: current_team.id })
|
||||
my_modules = MyModule.readable_by_user(current_user).active.uncomplete
|
||||
.joins(experiment: :project)
|
||||
.where(experiments: { archived: false })
|
||||
.where(projects: { archived: false })
|
||||
.where(my_modules: { due_date: start_date..end_date })
|
||||
.where(projects: { team_id: current_team.id })
|
||||
render json: {
|
||||
html: render_to_string(partial: 'shared/my_modules_list_partial',
|
||||
locals: { my_modules: my_modules },
|
||||
|
|
|
@ -34,7 +34,7 @@ module Dashboard
|
|||
.search_by_name(current_user, current_team, params[:query]).select(:id, :name)
|
||||
|
||||
unless params[:mode] == 'team'
|
||||
projects = projects.where(id: current_user.my_modules.joins(:experiment)
|
||||
projects = projects.where(id: MyModule.readable_by_user(current_user).joins(:experiment)
|
||||
.group(:project_id).select(:project_id).pluck(:project_id))
|
||||
end
|
||||
render json: projects.map { |i| { value: i.id, label: escape_input(i.name) } }, status: :ok
|
||||
|
@ -51,7 +51,7 @@ module Dashboard
|
|||
.search_by_name(current_user, current_team, params[:query]).select(:id, :name)
|
||||
|
||||
unless params[:mode] == 'team'
|
||||
experiments = experiments.where(id: current_user.my_modules
|
||||
experiments = experiments.where(id: MyModule.readable_by_user(current_user)
|
||||
.group(:experiment_id).select(:experiment_id).pluck(:experiment_id))
|
||||
end
|
||||
render json: experiments.map { |i| { value: i.id, label: escape_input(i.name) } }, status: :ok
|
||||
|
|
|
@ -47,7 +47,7 @@ class ExperimentsController < ApplicationController
|
|||
end
|
||||
|
||||
def assigned_users
|
||||
render json: User.where(id: @experiment.user_assignments.select(:user_id)),
|
||||
render json: @experiment.users,
|
||||
each_serializer: UserSerializer,
|
||||
user: current_user
|
||||
end
|
||||
|
|
|
@ -118,7 +118,7 @@ class ExternalProtocolsController < ApplicationController
|
|||
def create_protocol_params
|
||||
params
|
||||
.require(:protocol)
|
||||
.permit(:name, :authors, :published_on, :protocol_type, :description, :visibility, :default_public_user_role_id)
|
||||
.permit(:name, :authors, :published_on, :protocol_type, :description)
|
||||
.except(:steps)
|
||||
end
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ class FormFieldValuesController < ApplicationController
|
|||
log_form_field_value_create_activity
|
||||
form_field_value_annotation if @form_field_value.is_a?(FormTextFieldValue)
|
||||
|
||||
render json: @form_field_value, serializer: FormFieldValueSerializer, user: current_user
|
||||
render json: @form_field_value, serializer: FormFieldValueSerializer, scope: { user: current_user }
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -52,7 +52,7 @@ class FormFieldValuesController < ApplicationController
|
|||
smart_annotation_notification(
|
||||
old_text: @form_field_value.text_previously_was,
|
||||
new_text: @form_field_value.text,
|
||||
subject: step.protocol,
|
||||
subject: step,
|
||||
title: t('notifications.form_field_value_title',
|
||||
user: current_user.full_name,
|
||||
field: @form_field_value.form_field.name,
|
||||
|
|
|
@ -5,7 +5,7 @@ class LabelTemplatesController < ApplicationController
|
|||
include TeamsHelper
|
||||
|
||||
before_action :check_feature_enabled, except: %i(index zpl_preview list)
|
||||
before_action :load_label_templates, only: %i(index datatable list)
|
||||
before_action :load_label_templates, only: %i(index list)
|
||||
before_action :load_label_template, only: %i(show set_default update template_tags)
|
||||
before_action :check_view_permissions, except: %i(create duplicate set_default delete update)
|
||||
before_action :check_manage_permissions, only: %i(create duplicate set_default delete update)
|
||||
|
|
|
@ -47,7 +47,7 @@ class MyModulesController < ApplicationController
|
|||
|
||||
def new
|
||||
@my_module = @experiment.my_modules.new
|
||||
assigned_users = User.where(id: @experiment.user_assignments.select(:user_id))
|
||||
assigned_users = @experiment.users
|
||||
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
|
|
|
@ -60,22 +60,16 @@ class NavigationsController < ApplicationController
|
|||
end
|
||||
|
||||
def settings_menu_links
|
||||
links = [
|
||||
{
|
||||
name: I18n.t('users.settings.sidebar.teams'), url: teams_path
|
||||
}, {
|
||||
name: I18n.t('users.settings.sidebar.account_nav.addons'), url: addons_path
|
||||
}
|
||||
]
|
||||
|
||||
if can_create_acitivity_filters?
|
||||
links.push({ name: I18n.t('users.settings.sidebar.webhooks'), url: users_settings_webhooks_path })
|
||||
end
|
||||
links = [{ name: I18n.t('users.settings.sidebar.teams'), url: teams_path }]
|
||||
links << { name: I18n.t('users.settings.sidebar.groups'), url: users_settings_team_user_groups_path(current_team) } if can_manage_team?(current_team)
|
||||
links << { name: I18n.t('users.settings.sidebar.account_nav.addons'), url: addons_path }
|
||||
|
||||
private_methods.select { |i| i.to_s[/^settings_menu_links_[a-z]*_extension$/] }.each do |method|
|
||||
links = __send__(method, links)
|
||||
end
|
||||
|
||||
links << { name: I18n.t('users.settings.sidebar.webhooks'), url: users_settings_webhooks_path } if can_create_acitivity_filters?
|
||||
|
||||
links
|
||||
end
|
||||
|
||||
|
|
|
@ -86,7 +86,6 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def update
|
||||
default_public_user_role_name_before_update = @project.default_public_user_role&.name
|
||||
old_status = @project.status
|
||||
@project.assign_attributes(project_update_params)
|
||||
return_error = false
|
||||
|
@ -105,14 +104,6 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
message_edited = @project.name_changed? || @project.description_changed?
|
||||
message_visibility = if !@project.visibility_changed?
|
||||
nil
|
||||
elsif @project.visible?
|
||||
t('projects.activity.visibility_visible')
|
||||
else
|
||||
t('projects.activity.visibility_hidden')
|
||||
end
|
||||
|
||||
message_archived = if !@project.archived_changed?
|
||||
nil
|
||||
elsif @project.archived?
|
||||
|
@ -124,41 +115,14 @@ class ProjectsController < ApplicationController
|
|||
start_date_changes = @project.changes[:start_date]
|
||||
due_date_changes = @project.changes[:due_date]
|
||||
|
||||
default_public_user_role_name = nil
|
||||
if !@project.visibility_changed? && @project.default_public_user_role_id_changed?
|
||||
@project.visibility_will_change! # triggers assignment sync
|
||||
default_public_user_role_name = UserRole.find(project_params[:default_public_user_role_id]).name
|
||||
end
|
||||
|
||||
@project.last_modified_by = current_user
|
||||
if !return_error && @project.save
|
||||
|
||||
# Add activities if needed
|
||||
if message_visibility.present? && @project.visible?
|
||||
log_activity(:project_grant_access_to_all_team_members,
|
||||
@project,
|
||||
{ visibility: message_visibility,
|
||||
role: @project.default_public_user_role.name,
|
||||
team: @project.team.id })
|
||||
end
|
||||
if message_visibility.present? && !@project.visible?
|
||||
log_activity(:project_remove_access_from_all_team_members,
|
||||
@project,
|
||||
{ visibility: message_visibility,
|
||||
role: default_public_user_role_name_before_update,
|
||||
team: @project.team.id })
|
||||
end
|
||||
|
||||
log_activity(:edit_project) if message_edited.present?
|
||||
log_activity(:archive_project) if message_archived == 'archive'
|
||||
log_activity(:restore_project) if message_archived == 'restore'
|
||||
|
||||
if default_public_user_role_name.present?
|
||||
log_activity(:project_access_changed_all_team_members,
|
||||
@project,
|
||||
{ team: @project.team.id, role: default_public_user_role_name })
|
||||
end
|
||||
|
||||
if supervised_by_id_changes.present?
|
||||
log_activity(:remove_head_of_project, @project, { user_target: supervised_by_id_changes[0] }) if supervised_by_id_changes[0].present? # remove head of project
|
||||
log_activity(:set_head_of_project, @project, { user_target: supervised_by_id_changes[1] }) if supervised_by_id_changes[1].present? # add head of project
|
||||
|
@ -320,9 +284,8 @@ class ProjectsController < ApplicationController
|
|||
def project_params
|
||||
params.require(:project)
|
||||
.permit(
|
||||
:name, :visibility,
|
||||
:name,
|
||||
:archived, :project_folder_id,
|
||||
:default_public_user_role_id,
|
||||
:due_date,
|
||||
:start_date,
|
||||
:description
|
||||
|
@ -331,7 +294,7 @@ class ProjectsController < ApplicationController
|
|||
|
||||
def project_update_params
|
||||
params.require(:project)
|
||||
.permit(:name, :visibility, :archived, :default_public_user_role_id, :due_date, :start_date, :description, :status, :supervised_by_id)
|
||||
.permit(:name, :archived, :due_date, :start_date, :description, :status, :supervised_by_id)
|
||||
end
|
||||
|
||||
def view_type_params
|
||||
|
|
|
@ -1092,7 +1092,7 @@ class ProtocolsController < ApplicationController
|
|||
end
|
||||
|
||||
def create_params
|
||||
params.require(:protocol).permit(:name, :default_public_user_role_id, :visibility)
|
||||
params.require(:protocol).permit(:name)
|
||||
end
|
||||
|
||||
def check_protocolsio_import_permissions
|
||||
|
|
|
@ -12,7 +12,7 @@ class RepositoriesController < ApplicationController
|
|||
before_action :switch_team_with_param, only: %i(index)
|
||||
before_action :load_repository, except: %i(index create create_modal archive restore actions_toolbar
|
||||
export_repositories list)
|
||||
before_action :load_repositories, only: %i(index list actions_toolbar)
|
||||
before_action :load_repositories, only: %i(index actions_toolbar)
|
||||
before_action :load_repositories_for_archiving, only: :archive
|
||||
before_action :load_repositories_for_restoring, only: :restore
|
||||
before_action :check_view_permissions, except: %i(index create_modal create update destroy parse_sheet
|
||||
|
@ -44,7 +44,14 @@ class RepositoriesController < ApplicationController
|
|||
end
|
||||
|
||||
def list
|
||||
results = @repositories.select(:id, :name, 'LOWER(repositories.name)')
|
||||
repositories = if params[:appendable] == 'true'
|
||||
Repository.appendable_by_user(current_user)
|
||||
elsif params[:manageable] == 'true'
|
||||
Repository.with_granted_permissions(current_user, RepositoryPermissions::ROWS_UPDATE, current_team)
|
||||
else
|
||||
Repository.readable_by_user(current_user, current_team)
|
||||
end
|
||||
results = repositories.select(:id, :name, 'LOWER(repositories.name)')
|
||||
results = results.name_like(params[:query]) if params[:query].present?
|
||||
results = results.joins(:repository_rows).distinct if params[:non_empty].present?
|
||||
results = results.active if params[:active].present?
|
||||
|
@ -482,9 +489,7 @@ class RepositoriesController < ApplicationController
|
|||
|
||||
def load_repositories
|
||||
@repositories =
|
||||
if params[:appendable] == 'true'
|
||||
Repository.appendable_by_user(current_user)
|
||||
elsif can_manage_team?(current_team)
|
||||
if can_manage_team?(current_team)
|
||||
# Team owners see all repositories in the team
|
||||
current_team.repositories.or(Repository.shared_with_team(current_team))
|
||||
else
|
||||
|
|
|
@ -145,19 +145,23 @@ class RepositoryRowConnectionsController < ApplicationController
|
|||
|
||||
repository_connections.map do |connection|
|
||||
repository_row = @relation_type == 'parent' ? connection.parent : connection.child
|
||||
|
||||
{
|
||||
name: repository_row.name_with_label,
|
||||
code: repository_row.code,
|
||||
path: repository_repository_row_path(repository_row.repository, repository_row),
|
||||
repository_name: repository_row.repository.name_with_label,
|
||||
repository_path: repository_path(repository_row.repository),
|
||||
unlink_path: repository_repository_row_repository_row_connection_path(
|
||||
repository_row.repository,
|
||||
repository_row,
|
||||
connection
|
||||
)
|
||||
}
|
||||
if can_read_repository?(repository_row.repository)
|
||||
{
|
||||
name: repository_row.name_with_label,
|
||||
code: repository_row.code,
|
||||
path: repository_repository_row_path(repository_row.repository, repository_row),
|
||||
repository_name: repository_row.repository.name_with_label,
|
||||
repository_path: repository_path(repository_row.repository),
|
||||
can_connect_rows: can_connect_repository_rows?(repository_row.repository),
|
||||
unlink_path: repository_repository_row_repository_row_connection_path(
|
||||
repository_row.repository,
|
||||
repository_row,
|
||||
connection
|
||||
)
|
||||
}
|
||||
else
|
||||
{ name: I18n.t('repositories.item_card.relationships.private_item_name') }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -213,6 +213,8 @@ class RepositoryRowsController < ApplicationController
|
|||
{ repository_row: @repository_row.id,
|
||||
repository_column: update_params['repository_cells']&.keys&.first ||
|
||||
I18n.t('repositories.table.row_name') })
|
||||
|
||||
record_annotation_notification(@repository_row, row_cell_update.cell) if row_cell_update.cell && row_cell_update.cell.value_type == 'RepositoryTextValue'
|
||||
end
|
||||
@reminders_present = @repository_row.repository_cells.with_active_reminder(@current_user).any?
|
||||
|
||||
|
|
|
@ -25,13 +25,9 @@ class TeamSharedObjectsController < ApplicationController
|
|||
|
||||
case global_permission_level
|
||||
when :shared_read
|
||||
UserAssignment.where(assignable: @model).where.not(team: @model.team).update!(user_role: UserRole.find_predefined_viewer_role)
|
||||
TeamAssignment.where(assignable: @model).where.not(team: @model.team).update!(user_role: UserRole.find_predefined_viewer_role)
|
||||
UserGroupAssignment.where(assignable: @model).where.not(team: @model.team).update!(user_role: UserRole.find_predefined_viewer_role)
|
||||
@model.demote_all_sharing_assignments_to_viewer!
|
||||
when :not_shared
|
||||
UserAssignment.where(assignable: @model).where.not(team: @model.team).destroy_all
|
||||
TeamAssignment.where(assignable: @model).where.not(team: @model.team).destroy_all
|
||||
UserGroupAssignment.where(assignable: @model).where.not(team: @model.team).destroy_all
|
||||
@model.destroy_all_sharing_assignments!
|
||||
end
|
||||
|
||||
case @model
|
||||
|
|
|
@ -5,9 +5,19 @@ class Users::PasswordsController < Devise::PasswordsController
|
|||
# end
|
||||
|
||||
# POST /resource/password
|
||||
# def create
|
||||
# super
|
||||
# end
|
||||
def create
|
||||
self.resource = resource_class.send_reset_password_instructions(resource_params)
|
||||
yield resource if block_given?
|
||||
|
||||
if resource.errors.added?(:email, :blank)
|
||||
flash.now[:alert] = I18n.t('devise.errors.email.empty')
|
||||
self.resource = resource_class.new
|
||||
render :new
|
||||
else
|
||||
set_flash_message!(:notice, :send_instructions)
|
||||
respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name))
|
||||
end
|
||||
end
|
||||
|
||||
# GET /resource/password/edit?reset_password_token=abcdef
|
||||
# def edit
|
||||
|
@ -25,7 +35,7 @@ class Users::PasswordsController < Devise::PasswordsController
|
|||
flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
|
||||
set_flash_message!(:notice, flash_message)
|
||||
resource.after_database_authentication if check_database_authentication?(resource)
|
||||
sign_in(resource_name, resource)
|
||||
sign_in(resource_name, resource, event: :authentication)
|
||||
else
|
||||
set_flash_message!(:notice, :updated_not_active)
|
||||
end
|
||||
|
|
|
@ -5,10 +5,10 @@ module Users
|
|||
class UserGroupsController < ApplicationController
|
||||
before_action :load_team
|
||||
before_action :set_breadcrumbs_items, only: %i(index show)
|
||||
before_action :check_user_groups_enabled
|
||||
before_action :check_user_groups_enabled, except: :users
|
||||
before_action :load_user_group, except: %i(index unassigned_users actions_toolbar create)
|
||||
before_action :check_read_permissions, only: %i(index show unassigned_users actions_toolbar users)
|
||||
before_action :check_manage_permissions, except: %i(index show unassigned_users actions_toolbar users)
|
||||
before_action :check_read_permissions, only: %i(users)
|
||||
before_action :check_manage_permissions, except: %i(users)
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
|
@ -58,7 +58,7 @@ module Users
|
|||
end
|
||||
render json: { message: t('user_groups.create.success') }, status: :created
|
||||
else
|
||||
render json: { errors: t('user_groups.create.error') }, status: :unprocessable_entity
|
||||
render json: { error: @user_group.errors.full_messages.join(", ") }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -109,11 +109,13 @@ module Users
|
|||
)
|
||||
end
|
||||
|
||||
redirect_url = teams_path if params[:leave]
|
||||
|
||||
generate_notification(current_user,
|
||||
@user_assignment.user,
|
||||
@user_assignment.assignable,
|
||||
false)
|
||||
render json: { status: :ok, job_id: job_id, success_message: success_message }
|
||||
render json: { status: :ok, job_id: job_id, success_message: success_message, redirect_url: redirect_url }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -162,7 +162,7 @@ module CommentHelper
|
|||
smart_annotation_notification(
|
||||
old_text: old_text,
|
||||
new_text: comment.message,
|
||||
subject: step.protocol,
|
||||
subject: step,
|
||||
title: t('notifications.step_comment_annotation_title',
|
||||
step: step.name,
|
||||
user: current_user.full_name),
|
||||
|
|
|
@ -7,7 +7,7 @@ module UserRolesHelper
|
|||
viewer_role = UserRole.find_predefined_viewer_role
|
||||
roles = [[viewer_role.name, viewer_role.id]]
|
||||
else
|
||||
permission_group = "#{object.class.name}Permissions".constantize
|
||||
permission_group = "#{object.class.permission_class}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)
|
||||
|
|
|
@ -171,6 +171,7 @@ export default {
|
|||
cellRendererParams: {
|
||||
statusesList: this.statusesList
|
||||
},
|
||||
notSelectable: true,
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
|
|
|
@ -39,9 +39,16 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
optionRenderer(option) {
|
||||
let color = 'bg-sn-grey-500';
|
||||
if (option[0] === 'in_progress') {
|
||||
color = 'bg-sn-science-blue';
|
||||
} else if (option[0] === 'done') {
|
||||
color = 'bg-sn-alert-green';
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="${this.statusColor(option[0])} w-3 h-3 rounded-full"></div>
|
||||
<div class="${color} w-3 h-3 rounded-full"></div>
|
||||
<span>${option[1]}</span>
|
||||
</div>`;
|
||||
},
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
<div class="modal-body flex flex-col gap-6" :class="{ '!pb-3': notification }">
|
||||
<RepositoryRowSelector
|
||||
:multiple="true"
|
||||
:manageableRepositoriesOnly="true"
|
||||
@change="selectedItemValues = $event"
|
||||
@repositoryChange="changeSelectedInventory"
|
||||
/>
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
<template>
|
||||
<div class="bg-white px-4 my-4 task-section">
|
||||
<div class="py-4 flex items-center gap-4">
|
||||
<i ref="openHandler" @click="toggleContainer" class="sn-icon sn-icon-right cursor-pointer"></i>
|
||||
<h2 class="my-0 flex items-center gap-1">
|
||||
<i ref="openHandler"
|
||||
@click="toggleContainer"
|
||||
class="sn-icon sn-icon-right cursor-pointer"
|
||||
data-e2e="e2e-IC-task-assignedItems-visibilityToggle">
|
||||
</i>
|
||||
<h2 class="my-0 flex items-center gap-1" data-e2e="e2e-TX-task-assignedItems-title">
|
||||
{{ i18n.t('my_modules.assigned_items.title') }}
|
||||
<span class="text-sn-grey-500 font-normal text-base">[{{ totalRows }}]</span>
|
||||
</h2>
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
<img :src="logoUrl" alt="SciNote" class="h-full block">
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="currentTeam" class="w-64" :data-e2e="'e2e-DD-topMenu-teams'">
|
||||
<div v-if="currentTeam" class="w-64">
|
||||
<SelectDropdown
|
||||
:value="currentTeam"
|
||||
:options="teams"
|
||||
@change="switchTeam"
|
||||
:e2eValue="'e2e-DD-topMenu-teams'"
|
||||
></SelectDropdown>
|
||||
</div>
|
||||
<QuickSearch
|
||||
|
@ -144,11 +145,16 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
settingsMenuItems() {
|
||||
return this.settingsMenu.map((item) => ({ text: item.name, url: item.url })).concat(
|
||||
return this.settingsMenu.map((item) => ({
|
||||
text: item.name,
|
||||
url: item.url,
|
||||
data_e2e: `e2e-DO-topMenu-settings-${item.name.replace(' ','').toLowerCase()}`
|
||||
})).concat(
|
||||
{
|
||||
text: this.i18n.t('left_menu_bar.support_links.core_version'),
|
||||
modalTarget: '#aboutModal',
|
||||
url: ''
|
||||
url: '',
|
||||
data_e2e: `e2e-DO-topMenu-settings-scinoteVersion`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -55,12 +55,12 @@
|
|||
@update="updateDescription"
|
||||
@close="descriptionModalObject = null"/>
|
||||
<ExportLimitExceededModal v-if="exportLimitExceded" :description="exportDescription" @close="exportLimitExceded = false"/>
|
||||
<ProjectFormModal v-if="editProject" :userRolesUrl="userRolesUrl"
|
||||
<ProjectFormModal v-if="editProject"
|
||||
:project="editProject" @close="editProject = null" @update="updateTable(); updateNavigator()" />
|
||||
<EditFolderModal v-if="editFolder" :folder="editFolder"
|
||||
@close="editFolder = null" @update="updateTable(); updateNavigator()" />
|
||||
<ProjectFormModal v-if="newProject" :createUrl="createUrl"
|
||||
:currentFolderId="currentFolderId" :userRolesUrl="userRolesUrl"
|
||||
:currentFolderId="currentFolderId"
|
||||
@close="newProject = false" @create="updateTable(); updateNavigator()" />
|
||||
<NewFolderModal v-if="newFolder" :createFolderUrl="createFolderUrl"
|
||||
:currentFolderId="currentFolderId" :viewMode="currentViewMode"
|
||||
|
|
|
@ -51,22 +51,6 @@
|
|||
:placeholder="i18n.t('projects.index.add_description')"
|
||||
></TinymceEditor>
|
||||
</div>
|
||||
<div class="flex gap-2 text-xs items-center">
|
||||
<div class="sci-checkbox-container">
|
||||
<input type="checkbox" class="sci-checkbox" v-model="visible" value="visible" data-e2e="e2e-CB-projects-newProjectModal-access"/>
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</div>
|
||||
<span v-html="i18n.t('projects.index.modal_new_project.visibility_html')"></span>
|
||||
</div>
|
||||
<div class="mt-6" :class="{'hidden': !visible}">
|
||||
<label class="sci-label">{{ i18n.t("user_assignment.select_default_user_role") }}</label>
|
||||
<SelectDropdown
|
||||
:options="userRoles"
|
||||
:value="defaultRole"
|
||||
@change="changeRole"
|
||||
:e2eValue="'e2e-DD-projects-newProjectModal-defaultRole'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
|
@ -80,7 +64,7 @@
|
|||
<button
|
||||
class="btn btn-primary"
|
||||
type="submit"
|
||||
:disabled="submitting || (visible && !defaultRole) || !validName"
|
||||
:disabled="submitting || !validName"
|
||||
data-e2e="e2e-BT-projects-newProjectModal-create"
|
||||
>
|
||||
{{ submitButtonLabel }}
|
||||
|
@ -94,7 +78,6 @@
|
|||
|
||||
<script>
|
||||
|
||||
import SelectDropdown from '../../shared/select_dropdown.vue';
|
||||
import DateTimePicker from '../../shared/date_time_picker.vue';
|
||||
import TinymceEditor from '../../shared/tinymce_editor.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
|
@ -104,25 +87,14 @@ export default {
|
|||
name: 'ProjectFormModal',
|
||||
props: {
|
||||
project: Object,
|
||||
userRolesUrl: String,
|
||||
currentFolderId: String,
|
||||
createUrl: String
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
components: {
|
||||
SelectDropdown,
|
||||
DateTimePicker,
|
||||
TinymceEditor
|
||||
},
|
||||
watch: {
|
||||
visible(newValue) {
|
||||
if (newValue) {
|
||||
[this.defaultRole] = this.userRoles.find((role) => role[1] === 'Viewer');
|
||||
} else {
|
||||
this.defaultRole = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
validName() {
|
||||
return this.name.length >= GLOBAL_CONSTANTS.NAME_MIN_LENGTH;
|
||||
|
@ -142,16 +114,10 @@ export default {
|
|||
return this.i18n.t('projects.index.modal_edit_project.submit');
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchUserRoles();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: this.project?.name || '',
|
||||
visible: this.project ? !this.project.hidden : false,
|
||||
defaultRole: this.project?.default_public_user_role_id,
|
||||
error: null,
|
||||
userRoles: [],
|
||||
submitting: false,
|
||||
startDate: null,
|
||||
dueDate: null,
|
||||
|
@ -175,9 +141,7 @@ export default {
|
|||
name: this.name,
|
||||
start_date: this.startDate,
|
||||
due_date: this.dueDate,
|
||||
description: this.description,
|
||||
visibility: (this.visible ? 'visible' : 'hidden'),
|
||||
default_public_user_role_id: this.defaultRole
|
||||
description: this.description
|
||||
};
|
||||
|
||||
if (this.createUrl) {
|
||||
|
@ -209,23 +173,12 @@ export default {
|
|||
});
|
||||
this.submitting = false;
|
||||
},
|
||||
changeRole(role) {
|
||||
this.defaultRole = role;
|
||||
},
|
||||
updateStartDate(startDate) {
|
||||
this.startDate = this.stripTime(startDate);
|
||||
},
|
||||
updateDueDate(dueDate) {
|
||||
this.dueDate = this.stripTime(dueDate);
|
||||
},
|
||||
fetchUserRoles() {
|
||||
if (this.userRolesUrl) {
|
||||
axios.get(this.userRolesUrl)
|
||||
.then((response) => {
|
||||
this.userRoles = response.data.data;
|
||||
});
|
||||
}
|
||||
},
|
||||
stripTime(date) {
|
||||
if (date) {
|
||||
return new Date(Date.UTC(
|
||||
|
|
|
@ -9,10 +9,18 @@
|
|||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a class="task-section-caret" tabindex="0" role="button" data-toggle="collapse" href="#protocol-content" aria-expanded="true" aria-controls="protocol-content">
|
||||
<a class="task-section-caret"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
data-toggle="collapse"
|
||||
href="#protocol-content"
|
||||
aria-expanded="true"
|
||||
aria-controls="protocol-content"
|
||||
data-e2e="e2e-IC-task-protocol-visibilityToggle"
|
||||
>
|
||||
<i class="sn-icon sn-icon-right"></i>
|
||||
<div class="task-section-title truncate">
|
||||
<h2>{{ i18n.t('Protocol') }}</h2>
|
||||
<h2 data-e2e="e2e-TX-task-protocol-sectionTitle">{{ i18n.t('Protocol') }}</h2>
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
|
@ -29,16 +37,30 @@
|
|||
:title="i18n.t('protocols.steps.new_step_title')"
|
||||
@keyup.enter="addStep(steps.length)"
|
||||
@click="addStep(steps.length)"
|
||||
tabindex="0">
|
||||
tabindex="0"
|
||||
data-e2e="e2e-BT-task-protocol-newStep">
|
||||
<span class="sn-icon sn-icon-new-task" aria-hidden="true"></span>
|
||||
<span class="tw-hidden xl:inline">{{ i18n.t("protocols.steps.new_step") }}</span>
|
||||
</a>
|
||||
<template v-if="steps.length > 0">
|
||||
<button :title="i18n.t('protocols.steps.collapse_label')" v-if="!stepCollapsed" class="btn btn-secondary icon-btn xl:!px-4" @click="collapseSteps" tabindex="0">
|
||||
<button
|
||||
:title="i18n.t('protocols.steps.collapse_label')"
|
||||
v-if="!stepCollapsed"
|
||||
class="btn btn-secondary icon-btn xl:!px-4"
|
||||
@click="collapseSteps"
|
||||
tabindex="0"
|
||||
data-e2e="e2e-BT-task-protocol-collapseAll"
|
||||
>
|
||||
<i class="sn-icon sn-icon-collapse-all"></i>
|
||||
<span class="tw-hidden xl:inline">{{ i18n.t("protocols.steps.collapse_label") }}</span>
|
||||
</button>
|
||||
<button v-else :title="i18n.t('protocols.steps.expand_label')" class="btn btn-secondary icon-btn xl:!px-4" @click="expandSteps" tabindex="0">
|
||||
<button v-else
|
||||
:title="i18n.t('protocols.steps.expand_label')"
|
||||
class="btn btn-secondary icon-btn xl:!px-4"
|
||||
@click="expandSteps"
|
||||
tabindex="0"
|
||||
data-e2e="e2e-BT-task-protocol-expandAll"
|
||||
>
|
||||
<i class="sn-icon sn-icon-expand-all"></i>
|
||||
<span class="tw-hidden xl:inline">{{ i18n.t("protocols.steps.expand_label") }}</span>
|
||||
</button>
|
||||
|
@ -51,7 +73,13 @@
|
|||
@protocol:add_protocol_steps="addSteps"
|
||||
:canDeleteSteps="steps.length > 0 && urls.delete_steps_url !== null"
|
||||
/>
|
||||
<button class="btn btn-light icon-btn" data-toggle="modal" data-target="#print-protocol-modal" tabindex="0">
|
||||
<button
|
||||
class="btn btn-light icon-btn"
|
||||
data-toggle="modal"
|
||||
data-target="#print-protocol-modal"
|
||||
tabindex="0"
|
||||
data-e2e="e2e-BT-task-protocol-print"
|
||||
>
|
||||
<span class="sn-icon sn-icon-printer" aria-hidden="true"></span>
|
||||
</button>
|
||||
<a v-if="steps.length > 0 && urls.reorder_steps_url"
|
||||
|
@ -60,13 +88,20 @@
|
|||
@click="startStepReorder"
|
||||
@keyup.enter="startStepReorder"
|
||||
:class="{'disabled': steps.length == 1}"
|
||||
tabindex="0" >
|
||||
tabindex="0"
|
||||
data-e2e="e2e-BT-task-protocol-reorderSteps"
|
||||
>
|
||||
<i class="sn-icon sn-icon-sort" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="protocol-content" class="protocol-content collapse in" aria-expanded="true">
|
||||
<div
|
||||
id="protocol-content"
|
||||
class="protocol-content collapse in"
|
||||
aria-expanded="true"
|
||||
data-e2e="e2e-CO-task-protocol-content"
|
||||
>
|
||||
<div class="sci-divider" v-if="!inRepository"></div>
|
||||
<div class="mb-4">
|
||||
<div class="protocol-name mt-4" v-if="!inRepository">
|
||||
|
@ -78,6 +113,7 @@
|
|||
:allowBlank="!inRepository"
|
||||
:attributeName="`${i18n.t('Protocol')} ${i18n.t('name')}`"
|
||||
@update="updateName"
|
||||
:dataE2e="'task-protocol-title'"
|
||||
/>
|
||||
<span v-else>
|
||||
{{ protocol.attributes.name }}
|
||||
|
|
|
@ -1,18 +1,26 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-dialog" role="document" data-e2e="e2e-MD-protocol-addProtocolSteps">
|
||||
<form @submit.prevent="submit">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
data-e2e="e2e-BT-protocol-addProtocolStepsModal-close"
|
||||
>
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" >
|
||||
<h4 class="modal-title truncate !block" data-e2e="e2e-TX-protocol-addProtocolStepsModal-title">
|
||||
{{ i18n.t('protocols.steps.modals.add_protocol_steps.title') }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="mb-6">{{ i18n.t('protocols.steps.modals.add_protocol_steps.description')}}</p>
|
||||
<p class="mb-6" data-e2e="e2e-TX-protocol-addProtocolStepsModal-description">
|
||||
{{ i18n.t('protocols.steps.modals.add_protocol_steps.description')}}
|
||||
</p>
|
||||
<div class="mb-6">
|
||||
<label class="sci-label">{{ i18n.t('protocols.steps.modals.add_protocol_steps.protocol_label') }}</label>
|
||||
<SelectDropdown
|
||||
|
@ -21,6 +29,7 @@
|
|||
:searchable="true"
|
||||
:value="selectedProtocol"
|
||||
@change="selectedProtocol = $event"
|
||||
:e2eValue="'e2e-DD-protocol-addProtocolStepsModal-selectProtocol'"
|
||||
></SelectDropdown>
|
||||
</div>
|
||||
<div class="relative">
|
||||
|
@ -36,12 +45,25 @@
|
|||
:searchable="true"
|
||||
:value="selectedSteps"
|
||||
@change="selectedSteps= $event"
|
||||
:e2eValue="'e2e-DD-protocol-addProtocolStepsModal-selectSteps'"
|
||||
></SelectDropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" :disabled="submitting || !validObject" type="submit">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
data-dismiss="modal"
|
||||
data-e2e="e2e-BT-protocol-addProtocolStepsModal-cancel"
|
||||
>
|
||||
{{ i18n.t('general.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
:disabled="submitting || !validObject"
|
||||
type="submit"
|
||||
data-e2e="e2e-BT-protocol-addProtocolStepsModal-addSteps"
|
||||
>
|
||||
{{ i18n.t('protocols.steps.modals.add_protocol_steps.confirm') }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -1,20 +1,44 @@
|
|||
<template>
|
||||
<div ref="modal" @keydown.esc="cancel" class="modal delete-steps-modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-content" data-e2e="e2e-MD-task-protocol-deleteAllSteps">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||
<h4 class="modal-title">
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
data-e2e="e2e-BT-task-protocol-deleteAllStepsModal-close"
|
||||
>
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title" data-e2e="e2e-TX-task-protocol-deleteAllStepsModal-title">
|
||||
{{ i18n.t('protocols.steps.modals.delete_steps.title')}}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{ i18n.t('protocols.steps.modals.delete_steps.description_1')}}</p>
|
||||
<p class="warning">{{ i18n.t('protocols.steps.modals.delete_steps.description_2')}}</p>
|
||||
<p data-e2e="e2e-TX-task-protocol-deleteAllStepsModal-description">
|
||||
{{ i18n.t('protocols.steps.modals.delete_steps.description_1')}}
|
||||
</p>
|
||||
<p class="warning" data-e2e="e2e-TX-task-protocol-deleteAllStepsModal-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>
|
||||
<button class="btn btn-danger" @click="confirm">{{ i18n.t('protocols.steps.modals.delete_steps.confirm')}}</button>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="cancel"
|
||||
data-e2e="e2e-BT-task-protocol-deleteAllStepsModal-cancel"
|
||||
>
|
||||
{{ i18n.t('general.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
@click="confirm"
|
||||
data-e2e="e2e-BT-task-protocol-deleteAllStepsModal-delete"
|
||||
>
|
||||
{{ i18n.t('protocols.steps.modals.delete_steps.confirm')}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
aria-haspopup="true"
|
||||
aria-expanded="true"
|
||||
tabindex="0"
|
||||
data-e2e="e2e-DD-task-protocol-actions"
|
||||
>
|
||||
<span>{{ i18n.t("my_modules.protocol.options_dropdown.title") }}</span>
|
||||
<span class="sn-icon sn-icon-down"></span>
|
||||
|
@ -22,6 +23,7 @@
|
|||
ref="loadProtocol"
|
||||
data-action="load-from-repository"
|
||||
@click="loadProtocol"
|
||||
data-e2e="e2e-DO-task-protocol-actions-loadFromRepository"
|
||||
>
|
||||
<span>{{ i18n.t("my_modules.protocol.options_dropdown.load_from_repo") }}</span>
|
||||
</a>
|
||||
|
@ -30,6 +32,7 @@
|
|||
<a class="!px-3 !py-2.5 hover:!bg-sn-super-light-blue !text-sn-blue"
|
||||
data-turbolinks="false"
|
||||
@click.prevent="openAddStepsModal()"
|
||||
data-e2e="e2e-DO-task-protocol-actions-addProtocolSteps"
|
||||
>
|
||||
<span>{{
|
||||
i18n.t("my_modules.protocol.options_dropdown.add_protocol_steps")
|
||||
|
@ -42,6 +45,7 @@
|
|||
data-target="#newProtocolModal"
|
||||
v-bind:data-protocol-name="protocol.attributes.name"
|
||||
:class="{ disabled: !protocol.attributes.urls.save_to_repo_url }"
|
||||
data-e2e="e2e-DO-task-protocol-actions-saveAsNewTemplate"
|
||||
>
|
||||
<span>{{
|
||||
i18n.t("my_modules.protocol.options_dropdown.save_to_repo")
|
||||
|
@ -53,6 +57,7 @@
|
|||
data-turbolinks="false"
|
||||
:href="protocol.attributes.urls.export_url"
|
||||
:class="{ disabled: !protocol.attributes.urls.export_url }"
|
||||
data-e2e="e2e-DO-task-protocol-actions-exportProtocol"
|
||||
>
|
||||
<span>{{
|
||||
i18n.t("my_modules.protocol.options_dropdown.export")
|
||||
|
@ -64,6 +69,7 @@
|
|||
ref="updateProtocol"
|
||||
data-action="update-self"
|
||||
@click="updateProtocol"
|
||||
data-e2e="e2e-DO-task-protocol-actions-updateProtocol"
|
||||
>
|
||||
<span>{{
|
||||
i18n.t("my_modules.protocol.options_dropdown.update_protocol")
|
||||
|
@ -75,6 +81,7 @@
|
|||
ref="unlinkProtocol"
|
||||
data-action="unlink"
|
||||
@click="unlinkProtocol"
|
||||
data-e2e="e2e-DO-task-protocol-actions-unlinkProtocol"
|
||||
>
|
||||
<span>{{
|
||||
i18n.t("my_modules.protocol.options_dropdown.unlink")
|
||||
|
@ -86,6 +93,7 @@
|
|||
ref="revertProtocol"
|
||||
data-action="revert"
|
||||
@click="revertProtocol"
|
||||
data-e2e="e2e-DO-task-protocol-actions-revertProtocol"
|
||||
>
|
||||
<span>{{
|
||||
i18n.t("my_modules.protocol.options_dropdown.revert_protocol")
|
||||
|
@ -96,6 +104,7 @@
|
|||
<a class="!px-3 !py-2.5 hover:!bg-sn-super-light-blue !text-sn-blue"
|
||||
data-turbolinks="false"
|
||||
@click.prevent="openStepsDeletingModal()"
|
||||
data-e2e="e2e-DO-task-protocol-actions-deleteAllSteps"
|
||||
>
|
||||
<span>{{
|
||||
i18n.t("my_modules.protocol.options_dropdown.delete_steps")
|
||||
|
|
|
@ -22,26 +22,10 @@
|
|||
:placeholder="i18n.t('protocols.new_protocol_modal.name_placeholder')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 text-xs items-center">
|
||||
<div class="sci-checkbox-container">
|
||||
<input type="checkbox" class="sci-checkbox" v-model="visible" value="visible" data-e2e="e2e-CB-newProtocolModal-grantAccess"/>
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</div>
|
||||
<span v-html="i18n.t('protocols.new_protocol_modal.access_label')" data-e2e="e2e-TX-newProtocolModal-grantAccess"></span>
|
||||
</div>
|
||||
<div class="mt-6" :class="{'hidden': !visible}">
|
||||
<label class="sci-label">{{ i18n.t("protocols.new_protocol_modal.role_label") }}</label>
|
||||
<SelectDropdown
|
||||
:options="userRoles"
|
||||
:value="defaultRole"
|
||||
:data-e2e="`e2e-DD-newProtocolModal-defaultUserRole`"
|
||||
@change="changeRole"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal" data-e2e="e2e-BT-newProtocolModal-cancel">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" type="submit" :disabled="submitting || (visible && !defaultRole) || !validName" data-e2e="e2e-BT-newProtocolModal-create">
|
||||
<button class="btn btn-primary" type="submit" :disabled="submitting || !validName" data-e2e="e2e-BT-newProtocolModal-create">
|
||||
{{ i18n.t('protocols.new_protocol_modal.create_new') }}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -54,32 +38,15 @@
|
|||
<script>
|
||||
/* global GLOBAL_CONSTANTS */
|
||||
|
||||
import SelectDropdown from '../../shared/select_dropdown.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
|
||||
export default {
|
||||
name: 'NewProtocolModal',
|
||||
props: {
|
||||
createUrl: String,
|
||||
userRolesUrl: String
|
||||
createUrl: String
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
components: {
|
||||
SelectDropdown
|
||||
},
|
||||
watch: {
|
||||
visible(newValue) {
|
||||
if (newValue) {
|
||||
[this.defaultRole] = this.userRoles.find((role) => role[1] === 'Viewer');
|
||||
} else {
|
||||
this.defaultRole = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchUserRoles();
|
||||
},
|
||||
computed: {
|
||||
validName() {
|
||||
return this.name.length >= GLOBAL_CONSTANTS.NAME_MIN_LENGTH;
|
||||
|
@ -88,9 +55,6 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
name: '',
|
||||
visible: false,
|
||||
defaultRole: null,
|
||||
userRoles: [],
|
||||
error: null,
|
||||
submitting: false
|
||||
};
|
||||
|
@ -101,9 +65,7 @@ export default {
|
|||
|
||||
axios.post(this.createUrl, {
|
||||
protocol: {
|
||||
name: this.name,
|
||||
visibility: (this.visible ? 'visible' : 'hidden'),
|
||||
default_public_user_role_id: this.defaultRole
|
||||
name: this.name
|
||||
}
|
||||
}).then(() => {
|
||||
this.error = null;
|
||||
|
@ -113,17 +75,6 @@ export default {
|
|||
this.submitting = false;
|
||||
this.error = error.response.data.error;
|
||||
});
|
||||
},
|
||||
changeRole(role) {
|
||||
this.defaultRole = role;
|
||||
},
|
||||
fetchUserRoles() {
|
||||
if (this.userRolesUrl) {
|
||||
axios.get(this.userRolesUrl)
|
||||
.then((response) => {
|
||||
this.userRoles = response.data.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
/>
|
||||
</div>
|
||||
<NewProtocolModal v-if="newProtocol" :createUrl="createUrl"
|
||||
:userRolesUrl="userRolesUrl"
|
||||
@close="newProtocol = false" @create="updateTable" />
|
||||
<AccessModal v-if="accessModalParams" :params="accessModalParams"
|
||||
@close="accessModalParams = null" @refresh="this.reloadingTable = true" />
|
||||
|
|
|
@ -108,7 +108,7 @@ export default {
|
|||
mounted() {
|
||||
document.addEventListener('mouseover', this.loadColumnsInfo);
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('mouseover', this.loadColumnsInfo);
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -94,6 +94,7 @@ import DataTable from '../shared/datatable/table.vue';
|
|||
import NameRenderer from './renderers/name.vue';
|
||||
import AccessModal from '../shared/access_modal/modal.vue';
|
||||
import UsersRenderer from '../projects/renderers/users.vue';
|
||||
import escapeHtml from '../shared/escape_html.js';
|
||||
|
||||
export default {
|
||||
name: 'RepositoriesTable',
|
||||
|
@ -287,7 +288,7 @@ export default {
|
|||
};
|
||||
this.deleteModal.title = this.i18n.t('repositories.index.modal_delete.title_html', { name: repository.name });
|
||||
this.deleteModal.description = `
|
||||
<p data-e2e="e2e-TX-deleteInventoryModal-info">${this.i18n.t('repositories.index.modal_delete.message_html', { name: repository.name })}</p>
|
||||
<p data-e2e="e2e-TX-deleteInventoryModal-info">${this.i18n.t('repositories.index.modal_delete.message_html', { name: escapeHtml(repository.name) })}</p>
|
||||
<div class="alert alert-danger" role="alert" data-e2e="e2e-TX-deleteInventoryModal-warning">
|
||||
<span class="fas fa-exclamation-triangle"></span>
|
||||
${this.i18n.t('repositories.index.modal_delete.alert_heading')}
|
||||
|
|
|
@ -23,7 +23,7 @@ export default {
|
|||
archivedUrl: { type: String, required: true },
|
||||
disabled: { type: String, default: 'false' }
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
delete window.initRepositoryStateMenu;
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -184,7 +184,7 @@
|
|||
</a>
|
||||
</div>
|
||||
<div v-if="parentsCount">
|
||||
<details v-for="(parent) in parents" @toggle="updateOpenState(parent.code, $event.target.open)" :key="parent.code" class="flex flex-col font-normal gap-4 group cursor-default">
|
||||
<details v-for="(parent) in parents" @toggle="updateOpenState(parent.code, $event.target.open)" :key="parent.code" class="flex flex-col font-normal group cursor-default">
|
||||
<summary class="flex flex-row gap-3 mb-4 relative group">
|
||||
<img :src="icons.delimiter_path" class="w-3 h-3 cursor-pointer flex-shrink-0 relative top-1"
|
||||
:class="{ 'rotate-90': relationshipDetailsState[parent.code] }" />
|
||||
|
@ -192,7 +192,7 @@
|
|||
<span>{{ i18n.t('repositories.item_card.relationships.item') }}</span>
|
||||
<a v-if="parent.path" :href="parent.path" class="record-info-link btn-text-link !text-sn-science-blue">{{ parent.name }}</a>
|
||||
<span v-else>{{ parent.name }}</span>
|
||||
<button v-if="permissions.can_connect_rows" @click="openUnlinkModal(parent)"
|
||||
<button v-if="permissions.can_connect_rows && parent.can_connect_rows" @click="openUnlinkModal(parent)"
|
||||
class=" ml-2 bg-transparent border-none opacity-0 group-hover:opacity-100 cursor-pointer">
|
||||
<img :src="icons.unlink_path" />
|
||||
</button>
|
||||
|
@ -234,7 +234,7 @@
|
|||
</div>
|
||||
<div v-if="childrenCount">
|
||||
<details v-for="(child) in children" :key="child.code" @toggle="updateOpenState(child.code, $event.target.open)"
|
||||
class="flex flex-col font-normal gap-4 group-last-of-type:[&>p:last-child]:mb-0">
|
||||
class="flex flex-col font-normal group-last-of-type:[&>p:last-child]:mb-0">
|
||||
<summary class="flex flex-row gap-3 mb-4 relative group"
|
||||
:class="{ 'group-last-of-type:mb-0': !relationshipDetailsState[child.code] }">
|
||||
<img :src="icons.delimiter_path" class="w-3 h-3 flex-shrink-0 cursor-pointer relative top-1"
|
||||
|
@ -243,7 +243,7 @@
|
|||
<span>{{ i18n.t('repositories.item_card.relationships.item') }}</span>
|
||||
<a v-if="child.path" :href="child.path" class="record-info-link btn-text-link !text-sn-science-blue">{{ child.name }}</a>
|
||||
<span v-else>{{ child.name }}</span>
|
||||
<button v-if="permissions.can_connect_rows" @click="openUnlinkModal(child)"
|
||||
<button v-if="permissions.can_connect_rows && child.can_connect_rows" @click="openUnlinkModal(child)"
|
||||
class="ml-2 bg-transparent border-none opacity-0 group-hover:opacity-100 cursor-pointer">
|
||||
<img :src="icons.unlink_path" />
|
||||
</button>
|
||||
|
|
|
@ -60,7 +60,7 @@ export default {
|
|||
}
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
this.removeScrollListener();
|
||||
},
|
||||
|
|
|
@ -222,7 +222,7 @@ export default {
|
|||
created() {
|
||||
window.manageStockModalComponent = this;
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
delete window.manageStockModalComponent;
|
||||
},
|
||||
mounted() {
|
||||
|
|
|
@ -1,21 +1,41 @@
|
|||
<template>
|
||||
<div ref="modal" @keydown.esc="cancel" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-sm" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-content" data-e2e="e2e-MD-task-result-deleteResult">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||
<h4 class="modal-title">
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
data-e2e="e2e-BT-task-result-deleteResultModal-close"
|
||||
>
|
||||
<i class="sn-icon sn-icon-close">
|
||||
</i></button>
|
||||
<h4 class="modal-title" data-e2e="e2e-TX-task-result-deleteResultModal-title">
|
||||
{{ i18n.t("my_modules.results.delete_modal.title") }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body" data-e2e="e2e-TX-task-result-deleteResultModal-description">
|
||||
<p>{{ i18n.t("my_modules.results.delete_modal.description_1") }}</p>
|
||||
<p><b>{{ i18n.t("my_modules.results.delete_modal.description_2") }}</b></p>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" @click="cancel">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-danger" @click="confirm">{{ i18n.t("my_modules.results.delete_modal.confirm") }}</button>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="cancel"
|
||||
data-e2e="e2e-BT-task-result-deleteResultModal-cancel"
|
||||
>
|
||||
{{ i18n.t('general.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
@click="confirm"
|
||||
data-e2e="e2e-BT-task-result-deleteResultModal-delete"
|
||||
>
|
||||
{{ i18n.t("my_modules.results.delete_modal.confirm") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
@dragover.prevent
|
||||
:data-id="result.id"
|
||||
:class="{ 'bg-sn-super-light-blue': dragingFile, 'bg-white': !dragingFile, 'locked': locked, 'pointer-events-none': addingContent }"
|
||||
:data-e2e="`e2e-CO-task-result${result.id}`"
|
||||
>
|
||||
<div class="text-xl items-center flex flex-col text-sn-blue h-full justify-center left-0 absolute top-0 w-full"
|
||||
v-if="dragingFile"
|
||||
|
@ -20,6 +21,7 @@
|
|||
:href="'#resultBody' + result.id"
|
||||
data-toggle="collapse"
|
||||
data-remote="true"
|
||||
:data-e2e="`e2e-BT-task-result${result.id}-visibilityToggle`"
|
||||
@click="toggleCollapsed">
|
||||
<span class="sn-icon sn-icon-right "></span>
|
||||
</a>
|
||||
|
@ -35,6 +37,7 @@
|
|||
:placeholder="i18n.t('my_modules.results.placeholder')"
|
||||
:defaultValue="i18n.t('my_modules.results.default_name')"
|
||||
:timestamp="i18n.t('protocols.steps.timestamp', {date: result.attributes.created_at, user: result.attributes.created_by })"
|
||||
:data-e2e="`task-result${result.id}`"
|
||||
@editingEnabled="editingName = true"
|
||||
@editingDisabled="editingName = false"
|
||||
:editOnload="result.newResult == true"
|
||||
|
@ -48,6 +51,7 @@
|
|||
:btnText="i18n.t('my_modules.results.insert.button')"
|
||||
:position="'right'"
|
||||
:caret="true"
|
||||
:data-e2e="`e2e-DD-task-result${result.id}-insertContent`"
|
||||
:disableOverflow="true"
|
||||
@create:custom_well_plate="openCustomWellPlateModal"
|
||||
@create:table="(...args) => this.createElement('table', ...args)"
|
||||
|
@ -66,14 +70,26 @@
|
|||
:data-object-type="result.attributes.type"
|
||||
tabindex="0"
|
||||
></span> <!-- Hidden element to support legacy code -->
|
||||
<tempplate v-if="result.attributes.steps.length == 0">
|
||||
<button v-if="urls.update_url" ref="linkButton" :title="i18n.t('my_modules.results.link_steps')" class="btn btn-light icon-btn" @click="this.openLinkStepsModal = true">
|
||||
<template v-if="result.attributes.steps.length == 0">
|
||||
<button
|
||||
v-if="urls.update_url"
|
||||
ref="linkButton"
|
||||
:title="i18n.t('my_modules.results.link_steps')"
|
||||
class="btn btn-light icon-btn"
|
||||
@click="this.openLinkStepsModal = true"
|
||||
:data-e2e="`e2e-BT-task-result${result.id}-linkStep`"
|
||||
>
|
||||
{{ i18n.t('my_modules.results.link_to_step') }}
|
||||
</button>
|
||||
</tempplate>
|
||||
</template>
|
||||
<GeneralDropdown v-else ref="linkedStepsDropdown" position="right">
|
||||
<template v-slot:field>
|
||||
<button ref="linkButton" class="btn btn-light icon-btn" :title="i18n.t('my_modules.results.linked_steps')">
|
||||
<button
|
||||
ref="linkButton"
|
||||
class="btn btn-light icon-btn"
|
||||
:title="i18n.t('my_modules.results.linked_steps')"
|
||||
:data-e2e="`e2e-DD-task-result${result.id}-linkStep-showLinked`"
|
||||
>
|
||||
<i class="sn-icon sn-icon-steps"></i>
|
||||
<span class="absolute top-1 -right-1 h-4 min-w-4 bg-sn-science-blue text-white flex items-center justify-center rounded-full text-[10px]">
|
||||
{{ result.attributes.steps.length }}
|
||||
|
@ -87,14 +103,18 @@
|
|||
:title="step.name"
|
||||
:href="protocolUrl(step.id)"
|
||||
class="py-2.5 px-3 hover:bg-sn-super-light-grey cursor-pointer block hover:no-underline text-sn-blue truncate"
|
||||
:data-e2e="`e2e-BT-task-result${result.id}-linkStep-step${step.id}`"
|
||||
>
|
||||
{{ step.name }}
|
||||
</a>
|
||||
</div>
|
||||
<template v-if="urls.update_url">
|
||||
<hr class="my-0">
|
||||
<div class="py-2.5 px-3 hover:bg-sn-super-light-grey cursor-pointer text-sn-blue"
|
||||
@click="this.openLinkStepsModal = true; $refs.linkedStepsDropdown.closeMenu()">
|
||||
<div
|
||||
class="py-2.5 px-3 hover:bg-sn-super-light-grey cursor-pointer text-sn-blue"
|
||||
@click="this.openLinkStepsModal = true; $refs.linkedStepsDropdown.closeMenu()"
|
||||
:data-e2e="`e2e-BT-task-result${result.id}-linkStep-manage`"
|
||||
>
|
||||
{{ i18n.t('protocols.steps.manage_links') }}
|
||||
</div>
|
||||
</template>
|
||||
|
@ -105,7 +125,9 @@
|
|||
class="open-comments-sidebar btn icon-btn btn-light"
|
||||
data-turbolinks="false"
|
||||
data-object-type="Result"
|
||||
:data-object-id="result.id">
|
||||
:data-object-id="result.id"
|
||||
:data-e2e="`e2e-BT-task-result${result.id}-comments`"
|
||||
>
|
||||
<i class="sn-icon sn-icon-comments"></i>
|
||||
<span class="comments-counter" :class="{ 'hidden': !result.attributes.comments_count }"
|
||||
:id="`comment-count-${result.id}`">
|
||||
|
@ -119,6 +141,7 @@
|
|||
:btnClasses="'btn btn-light icon-btn'"
|
||||
:position="'right'"
|
||||
:btnIcon="'sn-icon sn-icon-more-hori'"
|
||||
:data-e2e="`e2e-DD-task-result${result.id}-optionsMenu`"
|
||||
@reorder="openReorderModal"
|
||||
@duplicate="duplicateResult"
|
||||
@archive="archiveResult"
|
||||
|
@ -244,14 +267,46 @@ export default {
|
|||
customWellPlate: false,
|
||||
openLinkStepsModal: false,
|
||||
wellPlateOptions: [
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.custom'), emit: 'create:custom_well_plate'},
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.32_x_48'), emit: 'create:table', params: [32, 48] },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.16_x_24'), emit: 'create:table', params: [16, 24] },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.8_x_12'), emit: 'create:table', params: [8, 12] },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.6_x_8'), emit: 'create:table', params: [6, 8] },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.4_x_6'), emit: 'create:table', params: [4, 6] },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.3_x_4'), emit: 'create:table', params: [3, 4] },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.2_x_3'), emit: 'create:table', params: [2, 3] }
|
||||
{
|
||||
text: I18n.t('protocols.steps.insert.well_plate_options.custom'),
|
||||
emit: 'create:custom_well_plate',
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-insertMenu-wellPlate-custom`
|
||||
},
|
||||
{
|
||||
text: I18n.t('protocols.steps.insert.well_plate_options.32_x_48'), emit: 'create:table',
|
||||
params: [32, 48],
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-insertMenu-wellPlate-32`
|
||||
},
|
||||
{
|
||||
text: I18n.t('protocols.steps.insert.well_plate_options.16_x_24'), emit: 'create:table',
|
||||
params: [16, 24],
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-insertMenu-wellPlate-16`
|
||||
},
|
||||
{
|
||||
text: I18n.t('protocols.steps.insert.well_plate_options.8_x_12'), emit: 'create:table',
|
||||
params: [8, 12],
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-insertMenu-wellPlate-8`
|
||||
},
|
||||
{
|
||||
text: I18n.t('protocols.steps.insert.well_plate_options.6_x_8'), emit: 'create:table',
|
||||
params: [6, 8],
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-insertMenu-wellPlate-6`
|
||||
},
|
||||
{
|
||||
text: I18n.t('protocols.steps.insert.well_plate_options.4_x_6'), emit: 'create:table',
|
||||
params: [4, 6],
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-insertMenu-wellPlate-4`
|
||||
},
|
||||
{
|
||||
text: I18n.t('protocols.steps.insert.well_plate_options.3_x_4'), emit: 'create:table',
|
||||
params: [3, 4],
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-insertMenu-wellPlate-3`
|
||||
},
|
||||
{
|
||||
text: I18n.t('protocols.steps.insert.well_plate_options.2_x_3'), emit: 'create:table',
|
||||
params: [2, 3],
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-insertMenu-wellPlate-2`
|
||||
}
|
||||
],
|
||||
editingName: false,
|
||||
confirmingDelete: false,
|
||||
|
@ -329,25 +384,29 @@ export default {
|
|||
if (this.urls.upload_attachment_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('my_modules.results.insert.add_file'),
|
||||
emit: 'create:file'
|
||||
emit: 'create:file',
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-insertMenu-file-addFromPc`
|
||||
}]);
|
||||
}
|
||||
if (this.result.attributes.wopi_enabled) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('assets.create_wopi_file.button_text'),
|
||||
emit: 'create:wopi_file'
|
||||
emit: 'create:wopi_file',
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-insertMenu-file-wopi`
|
||||
}]);
|
||||
}
|
||||
if (this.result.attributes.open_vector_editor_context.new_sequence_asset_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('open_vector_editor.new_sequence_file'),
|
||||
emit: 'create:ove_file'
|
||||
emit: 'create:ove_file',
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-insertMenu-file-sequence`
|
||||
}]);
|
||||
}
|
||||
if (this.result.attributes.marvinjs_enabled) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('marvinjs.new_button'),
|
||||
emit: 'create:marvinjs_file'
|
||||
emit: 'create:marvinjs_file',
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-insertMenu-file-chemical`
|
||||
}]);
|
||||
}
|
||||
return menu;
|
||||
|
@ -358,21 +417,25 @@ export default {
|
|||
menu = menu.concat([{
|
||||
text: this.i18n.t('my_modules.results.insert.text'),
|
||||
icon: 'sn-icon sn-icon-result-text',
|
||||
emit: 'create:text'
|
||||
emit: 'create:text',
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-insertMenu-text`
|
||||
}, {
|
||||
text: this.i18n.t('my_modules.results.insert.attachment'),
|
||||
submenu: this.filesMenu,
|
||||
icon: 'sn-icon sn-icon-file',
|
||||
position: 'left'
|
||||
position: 'left',
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-insertMenu-file`
|
||||
}, {
|
||||
text: this.i18n.t('my_modules.results.insert.table'),
|
||||
icon: 'sn-icon sn-icon-tables',
|
||||
emit: 'create:table'
|
||||
emit: 'create:table',
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-insertMenu-table`
|
||||
}, {
|
||||
text: this.i18n.t('my_modules.results.insert.well_plate'),
|
||||
icon: 'sn-icon sn-icon-tables',
|
||||
submenu: this.wellPlateOptions,
|
||||
position: 'left'
|
||||
position: 'left',
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-insertMenu-wellPlate`
|
||||
}]);
|
||||
}
|
||||
|
||||
|
@ -383,31 +446,36 @@ export default {
|
|||
if (this.urls.reorder_elements_url && this.elements.length > 1) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('my_modules.results.actions.rearrange'),
|
||||
emit: 'reorder'
|
||||
emit: 'reorder',
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-optionsMenu-reorder`
|
||||
}]);
|
||||
}
|
||||
if (this.urls.duplicate_url && !this.result.attributes.archived) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('my_modules.results.actions.duplicate'),
|
||||
emit: 'duplicate'
|
||||
emit: 'duplicate',
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-optionsMenu-duplicate`
|
||||
}]);
|
||||
}
|
||||
if (this.urls.archive_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('my_modules.results.actions.archive'),
|
||||
emit: 'archive'
|
||||
emit: 'archive',
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-optionsMenu-archive`
|
||||
}]);
|
||||
}
|
||||
if (this.urls.restore_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('my_modules.results.actions.restore'),
|
||||
emit: 'restore'
|
||||
emit: 'restore',
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-optionsMenu-restore`
|
||||
}]);
|
||||
}
|
||||
if (this.urls.delete_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('my_modules.results.actions.delete'),
|
||||
emit: 'delete'
|
||||
emit: 'delete',
|
||||
data_e2e: `e2e-DO-task-result${this.result.id}-optionsMenu-delete`
|
||||
}]);
|
||||
}
|
||||
return menu;
|
||||
|
|
|
@ -8,7 +8,14 @@
|
|||
</div>
|
||||
|
||||
<div class="result-toolbar__left flex items-center">
|
||||
<button v-if="canCreate" :title="i18n.t('my_modules.results.add_title')" class="btn btn-secondary" :class="{'mr-3': headerSticked}" @click="$emit('newResult')">
|
||||
<button
|
||||
v-if="canCreate"
|
||||
:title="i18n.t('my_modules.results.add_title')"
|
||||
class="btn btn-secondary"
|
||||
:class="{'mr-3': headerSticked}"
|
||||
@click="$emit('newResult')"
|
||||
data-e2e="e2e-BT-task-results-createNew"
|
||||
>
|
||||
<i class="sn-icon sn-icon-new-task"></i>
|
||||
{{ i18n.t('my_modules.results.add_label') }}
|
||||
</button>
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<perfect-scrollbar class="max-h-80 relative">
|
||||
<div v-for="userGroup in filteredUserGroups" :key="userGroup.id" class="py-2 flex items-center w-full">
|
||||
<div>
|
||||
<img src="/images/icon/group.png" class="rounded-full w-8 h-8">
|
||||
<img src="/images/icon/group.svg" class="rounded-full w-8 h-8">
|
||||
</div>
|
||||
<div
|
||||
class="truncate ml-2"
|
||||
|
|
|
@ -50,11 +50,11 @@
|
|||
<div>
|
||||
<img
|
||||
class="rounded-full w-8 h-8"
|
||||
src="/images/icon/group.png"
|
||||
src="/images/icon/group.svg"
|
||||
>
|
||||
</div>
|
||||
<div class="truncate">
|
||||
<div class="flex flex-row gap-2">
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<div class="truncate"
|
||||
:title="userGroupAssignment.attributes.user_group.name"
|
||||
>{{ userGroupAssignment.attributes.user_group.name }}</div>
|
||||
|
@ -114,7 +114,7 @@
|
|||
>
|
||||
</div>
|
||||
<div class="truncate">
|
||||
<div class="flex flex-row gap-2">
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<div class="truncate"
|
||||
:title="userAssignment.attributes.user.name"
|
||||
:data-e2e="`e2e-TX-${dataE2e}-${userAssignment.attributes.user.name.replace(/\W/g, '')}-name`"
|
||||
|
@ -329,7 +329,7 @@ export default {
|
|||
if (!roleId) {
|
||||
this.$emit('changeVisibility', false, null);
|
||||
} else {
|
||||
this.$emit('changeVisibility', true, roleId);
|
||||
this.$emit('changeVisibility', true, response.data.user_role_id);
|
||||
}
|
||||
if (response.data.message) {
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
|
|
|
@ -94,7 +94,7 @@ export default {
|
|||
this.setContainerSize();
|
||||
window.addEventListener('resize', this.setContainerSize);
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
window.removeEventListener('resize', this.setContainerSize);
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -541,9 +541,10 @@ export default {
|
|||
|
||||
this.handleScroll();
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((e) => {
|
||||
this.dataLoading = false;
|
||||
this.$emit('tableReloaded', [], { filtered: this.searchValue.length > 0 });
|
||||
console.error(e);
|
||||
window.HelperModule.flashAlertMsg(this.i18n.t('general.error'), 'danger');
|
||||
});
|
||||
},
|
||||
|
@ -558,6 +559,9 @@ export default {
|
|||
this.rowData = newRows;
|
||||
if (this.gridApi) {
|
||||
const viewport = document.querySelector('.ag-body-viewport');
|
||||
|
||||
if (!viewport) return;
|
||||
|
||||
const { scrollTop } = viewport;
|
||||
this.gridApi.setRowData(this.rowData);
|
||||
this.$nextTick(() => {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<template v-if="isOpen">
|
||||
<teleport to="body">
|
||||
<div @click="closeOnClick && closeMenu()" ref="flyout"
|
||||
:id="randomId"
|
||||
class="sn-dropdown fixed z-[3000] bg-sn-white inline-block
|
||||
rounded p-2.5 sn-shadow-menu-sm"
|
||||
:class="{
|
||||
|
@ -42,7 +43,8 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
isOpen: false
|
||||
isOpen: false,
|
||||
randomId: `dropdown-${Math.random().toString(36).substring(2, 15)}`,
|
||||
};
|
||||
},
|
||||
directives: {
|
||||
|
@ -70,7 +72,7 @@ export default {
|
|||
}
|
||||
},
|
||||
closeMenu(e) {
|
||||
if (e && e.target.closest('.sn-dropdown, .sn-select-dropdown, .sn-menu-dropdown, .dp__instance_calendar')) return;
|
||||
if (e && e.target.closest(`.sn-dropdown#${this.randomId}, .sn-select-dropdown, .sn-menu-dropdown, .dp__instance_calendar`)) return;
|
||||
this.isOpen = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,6 +79,10 @@ export default {
|
|||
excludeRows: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
manageableRepositoriesOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
@ -97,7 +101,7 @@ export default {
|
|||
mounted() {
|
||||
document.addEventListener('mouseover', this.loadColumnsInfo);
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('mouseover', this.loadColumnsInfo);
|
||||
},
|
||||
watch: {
|
||||
|
@ -116,7 +120,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
repositoriesUrl() {
|
||||
return list_team_repositories_path(this.teamId, { non_empty: true, active: true });
|
||||
return list_team_repositories_path(this.teamId, { non_empty: true, active: true, manageable: this.manageableRepositoriesOnly });
|
||||
},
|
||||
rowsUrl() {
|
||||
if (!this.selectedRepository) {
|
||||
|
|
|
@ -60,8 +60,9 @@
|
|||
<template v-if="isOpen">
|
||||
<teleport to="body">
|
||||
<div ref="flyout"
|
||||
class="sn-select-dropdown bg-white inline-block sn-shadow-menu-sm rounded w-full
|
||||
fixed z-[3000]">
|
||||
class="sn-select-dropdown bg-white inline-block sn-shadow-menu-sm rounded w-full fixed z-[3000]"
|
||||
:data-e2e="`${e2eValue}-dropdownOptions`"
|
||||
>
|
||||
<div v-if="multiple && withCheckboxes" class="p-2.5 pb-0">
|
||||
<div @click="selectAll" :class="sizeClass"
|
||||
class="border border-x-0 !border-transparent border-solid !border-b-sn-light-grey
|
||||
|
@ -72,7 +73,7 @@
|
|||
{{ i18n.t('general.select_all') }}
|
||||
</div>
|
||||
</div>
|
||||
<perfect-scrollbar ref="scrollContainer" class="p-2.5 flex flex-col max-h-80 relative" :class="{ 'pt-0': withCheckboxes }">
|
||||
<div ref="scrollContainer" class="p-2.5 flex flex-col max-h-80 relative overflow-y-auto" :class="{ 'pt-0': withCheckboxes }">
|
||||
<template v-for="(option, i) in filteredOptions" :key="option[0]">
|
||||
<div
|
||||
@click.stop="setValue(option[0])"
|
||||
|
@ -98,7 +99,7 @@
|
|||
<div v-if="filteredOptions.length === 0" class="text-sn-grey text-center py-2.5">
|
||||
{{ noOptionsPlaceholder || this.i18n.t('general.select_dropdown.no_options_placeholder') }}
|
||||
</div>
|
||||
</perfect-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
@ -148,7 +149,8 @@ export default {
|
|||
fixedWidth: true,
|
||||
focusedOption: null,
|
||||
skipQueryCallback: false,
|
||||
nextPage: 1
|
||||
nextPage: 1,
|
||||
totalOptionsCount: this.options.length
|
||||
};
|
||||
},
|
||||
mixins: [FixedFlyoutMixin],
|
||||
|
@ -225,11 +227,11 @@ export default {
|
|||
if (this.newValue.length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (this.newValue.length === 1 && this.rawOptions.length > 1) {
|
||||
if (this.newValue.length === 1 && this.totalOptionsCount > 1) {
|
||||
this.selectAllState = 'indeterminate';
|
||||
return this.renderLabel(this.rawOptions.find((option) => option[0] === this.newValue[0]));
|
||||
}
|
||||
if (this.newValue.length === this.rawOptions.length) {
|
||||
if (this.newValue.length === this.totalOptionsCount) {
|
||||
this.selectAllState = 'checked';
|
||||
return this.allOptionsPlaceholder || this.i18n.t('general.select_dropdown.all_options_placeholder');
|
||||
}
|
||||
|
@ -263,7 +265,6 @@ export default {
|
|||
if (!this.newValue && this.multiple) {
|
||||
this.newValue = [];
|
||||
}
|
||||
this.fetchOptions();
|
||||
},
|
||||
watch: {
|
||||
value(newValue) {
|
||||
|
@ -275,7 +276,7 @@ export default {
|
|||
this.$nextTick(() => {
|
||||
this.setPosition();
|
||||
this.$refs.search?.focus();
|
||||
this.$refs.scrollContainer.$el.addEventListener('scroll', this.loadNextPage);
|
||||
this.$refs.scrollContainer.addEventListener('scroll', this.loadNextPage);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -295,7 +296,7 @@ export default {
|
|||
this.fetchOptions();
|
||||
},
|
||||
loadNextPage() {
|
||||
const container = this.$refs.scrollContainer.$el;
|
||||
const container = this.$refs.scrollContainer;
|
||||
if (this.nextPage && container.scrollTop + container.clientHeight >= container.scrollHeight) {
|
||||
this.fetchOptions();
|
||||
}
|
||||
|
@ -316,7 +317,15 @@ export default {
|
|||
return this.newValue === value;
|
||||
},
|
||||
open() {
|
||||
if (!this.disabled) this.isOpen = true;
|
||||
if (this.disabled || this.isOpen) return;
|
||||
|
||||
this.isOpen = true;
|
||||
|
||||
if (this.optionsUrl) {
|
||||
this.fetchedOptions = [];
|
||||
this.nextPage = 1;
|
||||
this.fetchOptions();
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
this.newValue = this.multiple ? [] : null;
|
||||
|
@ -390,6 +399,11 @@ export default {
|
|||
} else {
|
||||
this.fetchedOptions = response.data.data;
|
||||
}
|
||||
|
||||
if (this.fetchedOptions.length > this.totalOptionsCount) {
|
||||
this.totalOptionsCount = this.fetchedOptions.length;
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.setPosition();
|
||||
});
|
||||
|
|
|
@ -66,6 +66,7 @@ import ShareObjectModal from '../shared/share_modal.vue';
|
|||
import DescriptionRenderer from './renderers/description.vue';
|
||||
import NameRenderer from './renderers/storage_name_renderer.vue';
|
||||
import FindRowModal from './modals/find_row.vue';
|
||||
import escapeHtml from '../shared/escape_html.js';
|
||||
|
||||
export default {
|
||||
name: 'RepositoriesTable',
|
||||
|
@ -264,7 +265,7 @@ export default {
|
|||
const storageLocationType = rows[0].container ? this.i18n.t('storage_locations.container') : this.i18n.t('storage_locations.location');
|
||||
const description = `
|
||||
<p>${this.i18n.t('storage_locations.index.delete_modal.description_1_html',
|
||||
{ name: rows[0].name, type: storageLocationType, num_of_items: event.number_of_items })}</p>
|
||||
{ name: escapeHtml(rows[0].name), type: storageLocationType, num_of_items: event.number_of_items })}</p>
|
||||
<p>${this.i18n.t('storage_locations.index.delete_modal.description_2_html')}</p>`;
|
||||
|
||||
this.storageLocationDeleteDescription = description;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
:option-renderer="usersRenderer"
|
||||
:label-renderer="usersRenderer"
|
||||
:multiple="true"
|
||||
:searchable="true"
|
||||
:placeholder="i18n.t('user_groups.show.add_members_modal.select_members_placeholder')"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
:withCheckboxes="true"
|
||||
:option-renderer="usersRenderer"
|
||||
:label-renderer="usersRenderer"
|
||||
:searchable="true"
|
||||
:multiple="true"
|
||||
:placeholder="i18n.t('user_groups.index.create_modal.select_members_placeholder')"
|
||||
/>
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
</GeneralDropdown>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="flex items-center gap-1 cursor-pointer h-9">
|
||||
<div class="flex items-center gap-1 h-9">
|
||||
<div v-for="(user, i) in visibleUsers" :key="i" :title="user.full_name">
|
||||
<img :src="user.avatar" class="w-7 h-7 rounded-full" />
|
||||
</div>
|
||||
|
|
|
@ -88,7 +88,8 @@ module UserAssignments
|
|||
|
||||
new_assignment = parent_assignment.class.find_or_initialize_by(
|
||||
"#{type}_id": parent_assignment.public_send(type).id,
|
||||
assignable: resource
|
||||
assignable: resource,
|
||||
team_id: parent_assignment.team_id
|
||||
)
|
||||
|
||||
return if new_assignment.manually_assigned?
|
||||
|
|
|
@ -28,12 +28,7 @@ module UserAssignments
|
|||
next unless project.experiments.any?
|
||||
|
||||
# make sure all related experiments and my modules are assigned
|
||||
UserAssignments::PropagateAssignmentJob.perform_later(
|
||||
project,
|
||||
user.id,
|
||||
project.default_public_user_role || UserRole.find_predefined_viewer_role,
|
||||
@assigned_by&.id
|
||||
)
|
||||
UserAssignments::PropagateAssignmentJob.perform_later(user_assignment)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
module UserAssignments
|
||||
class PropagateAssignmentJob < ApplicationJob
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
|
||||
queue_as :high_priority
|
||||
|
||||
def perform(assignment, destroy: false, remove_from_team: false)
|
||||
|
@ -13,6 +15,7 @@ module UserAssignments
|
|||
|
||||
ActiveRecord::Base.transaction do
|
||||
@assignment.destroy! if destroy && !@assignment.destroyed?
|
||||
cleanup!(@assignment)
|
||||
sync_resource_user_associations(@resource)
|
||||
end
|
||||
end
|
||||
|
@ -39,7 +42,7 @@ module UserAssignments
|
|||
|
||||
child_associations.find_each do |child_association|
|
||||
if @destroy
|
||||
destroy_or_update_assignment(child_association)
|
||||
destroy_assignment(child_association)
|
||||
else
|
||||
create_or_update_assignment(child_association)
|
||||
end
|
||||
|
@ -47,7 +50,7 @@ module UserAssignments
|
|||
sync_resource_user_associations(child_association)
|
||||
end
|
||||
|
||||
destroy_or_update_assignment(resource) if resource.is_a?(Project) && @destroy
|
||||
destroy_assignment(resource) if resource.is_a?(Project) && @destroy
|
||||
end
|
||||
|
||||
def create_or_update_assignment(resource)
|
||||
|
@ -66,31 +69,24 @@ module UserAssignments
|
|||
)
|
||||
end
|
||||
|
||||
def destroy_or_update_assignment(resource)
|
||||
# also destroy user designations if it's a MyModule
|
||||
resource.user_my_modules.where(user: @user).destroy_all if resource.is_a?(MyModule)
|
||||
|
||||
def destroy_assignment(resource)
|
||||
assignment = resource.public_send(:"#{@type}_assignments").find_by(
|
||||
"#{@type}_id" => @assignment.public_send(@type).id
|
||||
)
|
||||
|
||||
return unless assignment
|
||||
|
||||
project = resource.is_a?(Project) ? resource : resource.project
|
||||
assignment.destroy!
|
||||
|
||||
if project.default_public_user_role_id && !@remove_from_team
|
||||
# if project is public, the assignment
|
||||
# will reset to the default public role
|
||||
cleanup!(assignment)
|
||||
end
|
||||
|
||||
assignment.update!(
|
||||
user_role_id: project.default_public_user_role_id,
|
||||
assigned: :automatically,
|
||||
assigned_by: @assignment.assigned_by
|
||||
)
|
||||
else
|
||||
resource.favorites.where(user: @user).destroy_all if resource.respond_to?(:favorites)
|
||||
assignment.destroy!
|
||||
end
|
||||
def cleanup!(assignment)
|
||||
# clean up designations and favorites if user is no longer assigned
|
||||
assigned_users = assignment.assignable.users
|
||||
|
||||
assignment.assignable.favorites.where.not(user: assigned_users).destroy_all if assignment.assignable.respond_to?(:favorites)
|
||||
assignment.assignable.user_my_modules.where.not(user: assigned_users).destroy_all if assignment.assignable.is_a?(MyModule)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,7 +24,7 @@ module Assignable
|
|||
class_name: 'TeamAssignment',
|
||||
inverse_of: :assignable
|
||||
|
||||
after_create :create_users_assignments
|
||||
after_create :create_user_assignments!, unless: -> { skip_user_assignments }
|
||||
|
||||
def users
|
||||
direct_user_ids = user_assignments.select(:user_id)
|
||||
|
@ -41,6 +41,30 @@ module Assignable
|
|||
User.where(id: direct_user_ids).or(User.where(id: group_user_ids)).or(User.where(id: team_user_ids))
|
||||
end
|
||||
|
||||
def users_with_permission(permission, teams = Team.all)
|
||||
permitted_individual_assignments = user_assignments.joins(:user_role).where(team: teams).where(
|
||||
'user_roles.permissions @> ARRAY[?]::varchar[]', [permission]
|
||||
)
|
||||
|
||||
disallowed_assignments = user_assignments.joins(:user_role).where(team: teams).where(
|
||||
'NOT(user_roles.permissions @> ARRAY[?]::varchar[])', [permission]
|
||||
)
|
||||
|
||||
permitted_user_group_assignments = user_group_assignments.joins(:user_role, user_group: { user_group_memberships: :user }).where(team: teams).where(
|
||||
'user_roles.permissions @> ARRAY[?]::varchar[]', [permission]
|
||||
)
|
||||
|
||||
permitted_team_assignments = team_assignments.joins(:user_role, team: { user_assignments: :user }).where(team: teams).where(
|
||||
'user_roles.permissions @> ARRAY[?]::varchar[]', [permission]
|
||||
)
|
||||
|
||||
User.where(id: permitted_individual_assignments.select(:user_id)).or(
|
||||
User.where(id: permitted_user_group_assignments.select('user_group_memberships.user_id')).or(
|
||||
User.where(id: permitted_team_assignments.select('user_assignments.user_id'))
|
||||
)
|
||||
).where.not(id: disallowed_assignments.select(:user_id))
|
||||
end
|
||||
|
||||
def default_public_user_role_id(current_team = nil)
|
||||
if team_assignments.loaded?
|
||||
team_assignments.find { |ta| ta.team_id == (current_team || team).id }&.user_role_id
|
||||
|
@ -53,12 +77,11 @@ module Assignable
|
|||
false
|
||||
end
|
||||
|
||||
def role_for_user(user, team)
|
||||
user_assignments.find_by(user: user, team: team)&.user_role ||
|
||||
user_group_assignments.joins(user_group: :user_group_memberships)
|
||||
.where(team: team, user_groups: { user_group_memberships: { user_id: user.id } })
|
||||
.last&.user_role ||
|
||||
team_assignments.find_by(team: team)&.user_role
|
||||
def reset_all_users_assignments!(assigned_by)
|
||||
user_assignments.destroy_all
|
||||
user_group_assignments.destroy_all
|
||||
team_assignments.destroy_all
|
||||
create_user_assignments!(assigned_by)
|
||||
end
|
||||
|
||||
def manually_assigned_users
|
||||
|
@ -86,7 +109,7 @@ module Assignable
|
|||
|
||||
team_assignment = team_assignments.find_by(team: team)
|
||||
if team_assignment
|
||||
User.where.not(id: user_assignments.select(:user_id)).where(id: team_assignment.team.users.select(:id)).find_each do |user|
|
||||
User.where.not(id: user_assignments.select(:user_id).where(team: team)).where(id: team_assignment.team.users.select(:id)).find_each do |user|
|
||||
users << {
|
||||
user: user,
|
||||
role: team_assignment.user_role,
|
||||
|
@ -114,23 +137,36 @@ module Assignable
|
|||
# Will be called when an assignment is changed (save/destroy) for the assignable model.
|
||||
end
|
||||
|
||||
def create_users_assignments
|
||||
return if skip_user_assignments
|
||||
def after_team_assignment_changed(team_assignment = nil)
|
||||
# Optional, redefine in the assignable model.
|
||||
# Will be called when an assignment is changed (save/destroy) for the assignable model.
|
||||
end
|
||||
|
||||
role = if top_level_assignable?
|
||||
UserRole.find_predefined_owner_role
|
||||
else
|
||||
permission_parent.role_for_user(created_by, team)
|
||||
end
|
||||
def create_user_assignments!(user = created_by)
|
||||
# First create initial assignments for the object's creator
|
||||
if top_level_assignable?
|
||||
user_assignments.create!(user: user, assigned: :manually, user_role: UserRole.find_predefined_owner_role)
|
||||
else
|
||||
parent_assignment = permission_parent.user_assignments.find_by(user: user, team: team)
|
||||
if parent_assignment.present?
|
||||
user_assignments.create!(user: user, user_role: parent_assignment.user_role)
|
||||
else
|
||||
parent_group_assignments = permission_parent.user_group_assignments
|
||||
.joins(user_group: :user_group_memberships)
|
||||
.where(team: team, user_groups: { user_group_memberships: { user_id: user.id } })
|
||||
if parent_group_assignments.present?
|
||||
parent_group_assignments.each do |parent_group_assignment|
|
||||
user_group_assignments.create!(user_group: parent_group_assignment.user_group, user_role: parent_group_assignment.user_role)
|
||||
end
|
||||
else
|
||||
parent_team_assignment = permission_parent.team_assignments.find_by(team: team)
|
||||
team_assignments.create!(team: team, user_role: parent_team_assignment.user_role) if parent_team_assignment.present?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
UserAssignment.create!(
|
||||
user: created_by,
|
||||
assignable: self,
|
||||
assigned: top_level_assignable? ? :manually : :automatically,
|
||||
user_role: role
|
||||
)
|
||||
|
||||
UserAssignments::GenerateUserAssignmentsJob.perform_later(self, created_by.id)
|
||||
# Generate assignments for the rest of users in the background
|
||||
UserAssignments::GenerateUserAssignmentsJob.perform_later(self, user.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -50,7 +50,7 @@ module PermissionCheckableModel
|
|||
end
|
||||
|
||||
user_roles = UserRole.left_outer_joins(:team_assignments, user_group_assignments: { user_group: :users })
|
||||
user_roles.where(user_group_assignments: { assignable: self, user_groups: { users: user } })
|
||||
user_roles.where(user_group_assignments: { assignable: self, user_groups: { users: user }, team: permission_team })
|
||||
.or(user_roles.where(team_assignments: { assignable: self, team: permission_team }))
|
||||
.exists?(['user_roles.permissions @> ARRAY[?]::varchar[]', [permission]])
|
||||
end
|
||||
|
|
|
@ -50,8 +50,8 @@ module Shareable
|
|||
end
|
||||
|
||||
def can_manage_shared?(user)
|
||||
globally_shared? ||
|
||||
(shared_with?(user.current_team) && user.current_team.permission_granted?(user, TeamPermissions::MANAGE))
|
||||
(globally_shared? || shared_with?(user.current_team)) &&
|
||||
user.current_team.permission_granted?(user, TeamPermissions::MANAGE)
|
||||
end
|
||||
|
||||
def shareable_write?
|
||||
|
@ -99,4 +99,26 @@ module Shareable
|
|||
|
||||
shared_read? || team_shared_objects.exists?(team: team, permission_level: :shared_read)
|
||||
end
|
||||
|
||||
def demote_all_sharing_assignments_to_viewer!(for_team: nil)
|
||||
# take into account special roles with no read permission, and do not upgrade them to viewer
|
||||
read_permission = "#{self.class.permission_class}Permissions".constantize::READ
|
||||
|
||||
teams = for_team ? Team.where(id: for_team.id).where.not(id: team.id) : Team.where.not(id: team.id)
|
||||
|
||||
[user_assignments, user_group_assignments, team_assignments].each do |assignments|
|
||||
assignments.joins(:user_role)
|
||||
.where(team_id: teams.select(:id))
|
||||
.where(['user_roles.permissions @> ARRAY[?]::varchar[]', [read_permission]])
|
||||
.update!(user_role: UserRole.find_predefined_viewer_role)
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_all_sharing_assignments!(for_team: nil)
|
||||
teams = for_team ? Team.where(id: for_team.id).where.not(id: team.id) : Team.where.not(id: team.id)
|
||||
|
||||
user_assignments.where(team_id: teams.select(:id)).destroy_all
|
||||
user_group_assignments.where(team_id: teams.select(:id)).destroy_all
|
||||
team_assignments.where(team_id: teams.select(:id)).destroy_all
|
||||
end
|
||||
end
|
||||
|
|
|
@ -348,8 +348,7 @@ class Experiment < ApplicationRecord
|
|||
next unless my_module.save
|
||||
|
||||
# regenerate user assignments
|
||||
my_module.user_assignments.destroy_all
|
||||
UserAssignments::GenerateUserAssignmentsJob.perform_later(my_module, current_user.id)
|
||||
my_module.reset_all_users_assignments!(current_user)
|
||||
|
||||
Activities::CreateActivityService.call(activity_type: :move_task,
|
||||
owner: current_user,
|
||||
|
@ -405,8 +404,7 @@ class Experiment < ApplicationRecord
|
|||
m.save!
|
||||
|
||||
# regenerate user assignments
|
||||
m.user_assignments.destroy_all
|
||||
UserAssignments::GenerateUserAssignmentsJob.new(m, current_user.id).perform_now
|
||||
m.reset_all_users_assignments!(current_user)
|
||||
|
||||
# Add activity
|
||||
Activities::CreateActivityService.call(
|
||||
|
|
|
@ -23,7 +23,7 @@ class LabelTemplate < ApplicationRecord
|
|||
scope :predefined, -> { where(predefined: true) }
|
||||
|
||||
def self.readable_by_user(user, teams)
|
||||
where(team: teams.with_granted_permissions(user, TeamPermissions::LABEL_TEMPLATES_READ, teams))
|
||||
where(team: Team.with_granted_permissions(user, TeamPermissions::LABEL_TEMPLATES_READ, teams))
|
||||
end
|
||||
|
||||
def self.enabled?
|
||||
|
|
|
@ -35,7 +35,7 @@ class ProjectFolder < ApplicationRecord
|
|||
scope :top_level, -> { where(parent_folder: nil) }
|
||||
|
||||
def self.readable_by_user(user, teams)
|
||||
joins(team: :users)
|
||||
joins(team: :user_assignments)
|
||||
.where(teams: { user_assignments: { user: user } })
|
||||
.where(team: teams)
|
||||
end
|
||||
|
|
|
@ -17,7 +17,7 @@ class Protocol < ApplicationRecord
|
|||
include PermissionCheckableModel
|
||||
include TinyMceImages
|
||||
|
||||
skip_callback :create, :after, :create_users_assignments, if: -> { in_module? }
|
||||
before_create -> { self.skip_user_assignments = true }, if: -> { in_module? }
|
||||
|
||||
enum visibility: { hidden: 0, visible: 1 }
|
||||
enum protocol_type: {
|
||||
|
@ -601,12 +601,17 @@ class Protocol < ApplicationRecord
|
|||
end
|
||||
|
||||
parent_protocol.user_assignments.each do |parent_user_assignment|
|
||||
parent_protocol.sync_child_protocol_user_assignment(parent_user_assignment, draft.id)
|
||||
parent_protocol.sync_child_protocol_assignment(parent_user_assignment, draft.id)
|
||||
end
|
||||
|
||||
parent_protocol.user_group_assignments.each do |parent_user_group_assignment|
|
||||
parent_protocol.sync_child_protocol_user_assignment(parent_user_group_assignment, draft.id)
|
||||
parent_protocol.sync_child_protocol_assignment(parent_user_group_assignment, draft.id)
|
||||
end
|
||||
|
||||
parent_protocol.team_assignments.each do |parent_team_assignment|
|
||||
parent_protocol.sync_child_protocol_assignment(parent_team_assignment, draft.id)
|
||||
end
|
||||
|
||||
draft
|
||||
end
|
||||
|
||||
|
@ -678,29 +683,30 @@ class Protocol < ApplicationRecord
|
|||
published_versions.or(Protocol.where(id: draft&.id))
|
||||
end
|
||||
|
||||
def sync_child_protocol_user_assignment(user_assignment, child_protocol_id = nil)
|
||||
def sync_child_protocol_assignment(assignment, child_protocol_id = nil)
|
||||
# Copy user assignments to child protocol(s)
|
||||
|
||||
Protocol.transaction(requires_new: true) do
|
||||
# Reload to ensure a potential new draft is also included in child versions
|
||||
reload
|
||||
assignment_type = user_assignment.respond_to?(:user_group) ? 'user_group' : 'user'
|
||||
assignment_key = "#{assignment_type}_id".to_sym
|
||||
assignment_key = assignment.model_name.param_key
|
||||
assignable_id_key = assignment_key.gsub('assignment', 'id')
|
||||
|
||||
(
|
||||
# all or single child version protocol
|
||||
child_protocol_id ? child_version_protocols.where(id: child_protocol_id) : child_version_protocols
|
||||
).find_each do |child_protocol|
|
||||
child_assignment = child_protocol.public_send("#{assignment_type}_assignments").find_or_initialize_by(
|
||||
assignment_key => user_assignment.public_send(assignment_key)
|
||||
child_assignment = child_protocol.public_send(assignment_key.pluralize).find_or_initialize_by(
|
||||
assignable_id_key => assignment.public_send(assignable_id_key)
|
||||
)
|
||||
|
||||
if user_assignment.destroyed?
|
||||
if assignment.destroyed?
|
||||
child_assignment.destroy! if child_assignment.persisted?
|
||||
next
|
||||
end
|
||||
|
||||
child_assignment.update!(
|
||||
user_assignment.attributes.slice(
|
||||
assignment.attributes.slice(
|
||||
'user_role_id',
|
||||
'assigned',
|
||||
'assigned_by_id',
|
||||
|
@ -714,15 +720,21 @@ class Protocol < ApplicationRecord
|
|||
private
|
||||
|
||||
def after_user_assignment_changed(user_assignment)
|
||||
return unless in_repository_published_original?
|
||||
return if skip_user_assignments || !in_repository_published_original?
|
||||
|
||||
sync_child_protocol_user_assignment(user_assignment)
|
||||
sync_child_protocol_assignment(user_assignment)
|
||||
end
|
||||
|
||||
def after_user_group_assignment_changed(user_group_assignment)
|
||||
return unless in_repository_published_original?
|
||||
return if skip_user_assignments || !in_repository_published_original?
|
||||
|
||||
sync_child_protocol_user_assignment(user_group_assignment)
|
||||
sync_child_protocol_assignment(user_group_assignment)
|
||||
end
|
||||
|
||||
def after_team_assignment_changed(user_group_assignment)
|
||||
return if skip_user_assignments || !in_repository_published_original?
|
||||
|
||||
sync_child_protocol_assignment(user_group_assignment)
|
||||
end
|
||||
|
||||
def deep_clone(clone, current_user, include_file_versions: false)
|
||||
|
|
|
@ -32,6 +32,8 @@ class Repository < RepositoryBase
|
|||
|
||||
before_save :sync_name_with_snapshots, if: :name_changed?
|
||||
before_destroy :refresh_report_references_on_destroy, prepend: true
|
||||
after_save :unassign_unshared_items, if: :saved_change_to_permission_level
|
||||
after_save :unlink_unshared_items, if: -> { saved_change_to_permission_level? && !globally_shared? }
|
||||
|
||||
validates :name,
|
||||
presence: true,
|
||||
|
@ -53,6 +55,10 @@ class Repository < RepositoryBase
|
|||
.where(team: teams)
|
||||
}
|
||||
|
||||
def self.permission_class
|
||||
Repository
|
||||
end
|
||||
|
||||
def top_level_assignable
|
||||
true
|
||||
end
|
||||
|
@ -172,6 +178,26 @@ class Repository < RepositoryBase
|
|||
.destroy_all
|
||||
end
|
||||
|
||||
def unlink_unshared_items
|
||||
repository_rows_ids = repository_rows.select(:id)
|
||||
rows_to_unlink = RepositoryRow.joins("LEFT JOIN repository_row_connections \
|
||||
ON repository_rows.id = repository_row_connections.parent_id \
|
||||
OR repository_rows.id = repository_row_connections.child_id")
|
||||
.where("repository_row_connections.parent_id IN (?) \
|
||||
OR repository_row_connections.child_id IN (?)",
|
||||
repository_rows_ids,
|
||||
repository_rows_ids)
|
||||
.joins(:repository)
|
||||
.where.not(repository: self)
|
||||
.where.not(repositories: { team: team })
|
||||
.distinct
|
||||
|
||||
RepositoryRowConnection.where(parent: repository_rows_ids, child: rows_to_unlink)
|
||||
.destroy_all
|
||||
RepositoryRowConnection.where(child: repository_rows_ids, parent: rows_to_unlink)
|
||||
.destroy_all
|
||||
end
|
||||
|
||||
def archived_branch?
|
||||
archived?
|
||||
end
|
||||
|
|
|
@ -27,10 +27,6 @@ class RepositoryBase < ApplicationRecord
|
|||
# Not discarded
|
||||
default_scope -> { kept }
|
||||
|
||||
def self.permission_class
|
||||
Repository
|
||||
end
|
||||
|
||||
def self.stock_management_enabled?
|
||||
ApplicationSettings.instance.values['stock_management_enabled']
|
||||
end
|
||||
|
|
|
@ -54,8 +54,7 @@ class Result < ApplicationRecord
|
|||
new_query = joins(:my_module)
|
||||
.where(
|
||||
my_modules: {
|
||||
id: MyModule.with_granted_permissions(user, MyModulePermissions::READ)
|
||||
.where(user_assignments: { team: teams }).select(:id)
|
||||
id: MyModule.with_granted_permissions(user, MyModulePermissions::READ, teams).select(:id)
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ class Team < ApplicationRecord
|
|||
has_many :shareable_links, inverse_of: :team, dependent: :destroy
|
||||
has_many :storage_locations, dependent: :destroy
|
||||
has_many :forms, dependent: :destroy
|
||||
has_many :team_assignments, dependent: :destroy
|
||||
|
||||
attr_accessor :without_templates
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ class TeamAssignment < ApplicationRecord
|
|||
belongs_to :team
|
||||
belongs_to :user_role
|
||||
belongs_to :assigned_by, class_name: 'User', optional: true
|
||||
has_many :users, through: :team
|
||||
delegate :users, to: :team
|
||||
|
||||
enum :assigned, { automatically: 0, manually: 1 }, suffix: true
|
||||
|
||||
|
@ -14,4 +14,11 @@ class TeamAssignment < ApplicationRecord
|
|||
scope :as_viewers, -> { where(user_role: UserRole.find_predefined_viewer_role) }
|
||||
|
||||
validates :team, uniqueness: { scope: :assignable }
|
||||
|
||||
after_destroy :call_team_assignment_changed_hook
|
||||
after_save :call_team_assignment_changed_hook
|
||||
|
||||
def call_team_assignment_changed_hook
|
||||
assignable.__send__(:after_team_assignment_changed, self)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,22 +19,57 @@ class TeamSharedObject < ApplicationRecord
|
|||
|
||||
# ifs needed for StorageLocations, which currently do not have assignments
|
||||
after_update :update_assignments, if: -> { shared_object.respond_to?(:user_assignments) }
|
||||
before_destroy :unassign_unshared_items, if: -> { shared_object.is_a?(Repository) }
|
||||
before_destroy :unlink_unshared_items, if: -> { shared_object.is_a?(Repository) }
|
||||
after_destroy :destroy_assignments, if: -> { shared_object.respond_to?(:user_assignments) }
|
||||
|
||||
private
|
||||
|
||||
def unassign_unshared_items
|
||||
return if shared_object.shared_read? || shared_object.shared_write?
|
||||
|
||||
MyModuleRepositoryRow.joins(my_module: { experiment: { project: :team } })
|
||||
.joins(repository_row: :repository)
|
||||
.where(my_module: { experiment: { projects: { team: team } } })
|
||||
.where(repository_rows: { repository: shared_object })
|
||||
.destroy_all
|
||||
end
|
||||
|
||||
def unlink_unshared_items
|
||||
# We keep all the other teams shared with and the repository's own team
|
||||
teams_ids = shared_object.teams_shared_with.where.not(id: team).pluck(:id)
|
||||
teams_ids << shared_object.team_id
|
||||
repository_rows_ids = shared_object.repository_rows.select(:id)
|
||||
rows_to_unlink = RepositoryRow.joins("LEFT JOIN repository_row_connections \
|
||||
ON repository_rows.id = repository_row_connections.parent_id \
|
||||
OR repository_rows.id = repository_row_connections.child_id")
|
||||
.where("repository_row_connections.parent_id IN (?) \
|
||||
OR repository_row_connections.child_id IN (?)",
|
||||
repository_rows_ids,
|
||||
repository_rows_ids)
|
||||
.joins(:repository)
|
||||
.where.not(repositories: { team: teams_ids })
|
||||
.select(:id)
|
||||
|
||||
RepositoryRowConnection.where("(repository_row_connections.parent_id IN (?) \
|
||||
AND repository_row_connections.child_id IN (?)) \
|
||||
OR (repository_row_connections.parent_id IN (?) \
|
||||
AND repository_row_connections.child_id IN (?))",
|
||||
repository_rows_ids,
|
||||
rows_to_unlink,
|
||||
rows_to_unlink,
|
||||
repository_rows_ids)
|
||||
.destroy_all
|
||||
end
|
||||
|
||||
def update_assignments
|
||||
return unless saved_change_to_permission_level? && permission_level == 'shared_read'
|
||||
|
||||
shared_object.user_assignments.where(team: team).update!(user_role: UserRole.find_predefined_viewer_role)
|
||||
shared_object.user_group_assignments.where(team: team).update!(user_role: UserRole.find_predefined_viewer_role)
|
||||
shared_object.team_assignments.where(team: team).update!(user_role: UserRole.find_predefined_viewer_role)
|
||||
shared_object.demote_all_sharing_assignments_to_viewer!(for_team: team)
|
||||
end
|
||||
|
||||
def destroy_assignments
|
||||
shared_object.user_assignments.where(team: team).destroy_all
|
||||
shared_object.user_group_assignments.where(team: team).destroy_all
|
||||
shared_object.team_assignments.where(team: team).destroy_all
|
||||
shared_object.destroy_all_sharing_assignments!(for_team: team)
|
||||
end
|
||||
|
||||
def team_cannot_be_the_same
|
||||
|
|
|
@ -62,9 +62,7 @@ class User < ApplicationRecord
|
|||
has_many :user_group_memberships, dependent: :destroy
|
||||
has_many :user_groups, through: :user_group_memberships
|
||||
has_many :teams, through: :user_assignments, source: :assignable, source_type: 'Team'
|
||||
has_many :projects, through: :user_assignments, source: :assignable, source_type: 'Project'
|
||||
has_many :user_my_modules, inverse_of: :user
|
||||
has_many :my_modules, through: :user_assignments, source: :assignable, source_type: 'MyModule'
|
||||
has_many :comments, inverse_of: :user
|
||||
has_many :activities, inverse_of: :owner, foreign_key: 'owner_id'
|
||||
has_many :results, inverse_of: :user
|
||||
|
@ -465,6 +463,7 @@ class User < ApplicationRecord
|
|||
# Returns a hash with user statistics
|
||||
def statistics
|
||||
statistics = {}
|
||||
projects = Project.readable_by_user(self, teams)
|
||||
statistics[:number_of_teams] = teams.count
|
||||
statistics[:number_of_projects] = projects.count
|
||||
number_of_experiments = 0
|
||||
|
|
|
@ -14,6 +14,7 @@ class UserGroup < ApplicationRecord
|
|||
belongs_to :last_modified_by, class_name: 'User', optional: true
|
||||
has_many :user_group_memberships, dependent: :destroy
|
||||
has_many :users, through: :user_group_memberships, dependent: :destroy
|
||||
has_many :user_group_assignments, dependent: :destroy
|
||||
|
||||
accepts_nested_attributes_for :user_group_memberships
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@ class UserGroupAssignment < ApplicationRecord
|
|||
belongs_to :team
|
||||
belongs_to :user_group
|
||||
belongs_to :user_role
|
||||
belongs_to :assigned_by, class_name: 'User'
|
||||
belongs_to :assigned_by, class_name: 'User', optional: true
|
||||
has_many :users, through: :user_group
|
||||
|
||||
enum :assigned, { automatically: 0, manually: 1 }, suffix: true
|
||||
|
||||
|
|
|
@ -14,10 +14,7 @@ module Recipients
|
|||
end
|
||||
return User.none unless record
|
||||
|
||||
User.where(id: record.user_assignments
|
||||
.joins(:user_role)
|
||||
.where('? = ANY(user_roles.permissions)', permission)
|
||||
.select(:user_id))
|
||||
record.users_with_permission(permission)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ class Recipients::RepositoryItemRecipients
|
|||
end
|
||||
|
||||
def recipients
|
||||
repository_row = RepositoryRow.find(@repository_row_id)
|
||||
repository_row.repository.team.users
|
||||
repository = RepositoryRow.find(@repository_row_id).repository
|
||||
repository.users_with_permission(RepositoryPermissions::READ, repository.team)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -113,8 +113,7 @@ Canaid::Permissions.register_for(Repository) do
|
|||
|
||||
# repository: create field
|
||||
can :create_repository_columns do |user, repository|
|
||||
!repository.shared_with?(user.current_team) &&
|
||||
repository.permission_granted?(user, RepositoryPermissions::COLUMNS_CREATE)
|
||||
repository.permission_granted?(user, RepositoryPermissions::COLUMNS_CREATE)
|
||||
end
|
||||
|
||||
can :manage_repository_columns do |user, repository|
|
||||
|
|
|
@ -12,6 +12,10 @@ module Api
|
|||
has_many :project_comments, key: :comments, serializer: CommentSerializer
|
||||
|
||||
include TimestampableModel
|
||||
|
||||
def visibility
|
||||
object.team_assignments.any? ? 'visible' : 'hidden'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ module AssignmentsHelper
|
|||
}
|
||||
end
|
||||
|
||||
user_groups = object.user_group_assignments.map do |ua|
|
||||
user_groups = object.user_group_assignments.where(team: current_user.current_team).map do |ua|
|
||||
{
|
||||
avatar: ActionController::Base.helpers.asset_path('icon/group.svg'),
|
||||
full_name: ua.user_group_name_with_role
|
||||
|
|
|
@ -68,7 +68,8 @@ module Lists
|
|||
provisioning_status: provisioning_status_my_module_url(object),
|
||||
favorite: favorite_my_module_url(object),
|
||||
unfavorite: unfavorite_my_module_url(object),
|
||||
user_roles: user_roles_access_permissions_my_module_path(object)
|
||||
user_roles: user_roles_access_permissions_my_module_path(object),
|
||||
user_group_members: users_users_settings_team_user_groups_path(team_id: object.team.id)
|
||||
}
|
||||
|
||||
if can_manage_project_users?(object.experiment.project)
|
||||
|
|
|
@ -58,7 +58,8 @@ module Lists
|
|||
show_access: access_permissions_repository_path(object),
|
||||
share: team_shared_objects_path(current_user.current_team, object_id: object.id, object_type: 'Repository'),
|
||||
user_roles: user_roles_access_permissions_repository_path(object),
|
||||
user_group_members: users_users_settings_team_user_groups_path(team_id: object.team.id)
|
||||
user_group_members: users_users_settings_team_user_groups_path(team_id: current_user.current_team_id),
|
||||
show_user_group_assignments_access: show_user_group_assignments_access_permissions_repository_path(object)
|
||||
}
|
||||
|
||||
urls[:show] = repository_path(object) if can_read?
|
||||
|
@ -68,7 +69,6 @@ module Lists
|
|||
urls[:new_access] = new_access_permissions_repository_path(id: object.id)
|
||||
urls[:create_access] = access_permissions_repositories_path(id: object.id)
|
||||
urls[:unassigned_user_groups] = unassigned_user_groups_access_permissions_repository_path(id: object.id)
|
||||
urls[:show_user_group_assignments_access] = show_user_group_assignments_access_permissions_repository_path(object)
|
||||
end
|
||||
|
||||
urls
|
||||
|
|
|
@ -22,13 +22,13 @@ class ActivitiesService
|
|||
|
||||
activities =
|
||||
if filters[:from_date].present? && filters[:to_date].present?
|
||||
activities.where('created_at <= :from AND created_at >= :to',
|
||||
activities.where('activities.created_at <= :from AND activities.created_at >= :to',
|
||||
from: Time.zone.parse(filters[:from_date]).end_of_day.utc,
|
||||
to: Time.zone.parse(filters[:to_date]).beginning_of_day.utc)
|
||||
elsif filters[:from_date].present? && filters[:to_date].blank?
|
||||
activities.where('created_at <= :from', from: Time.zone.parse(filters[:from_date]).end_of_day.utc)
|
||||
activities.where('activities.created_at <= :from', from: Time.zone.parse(filters[:from_date]).end_of_day.utc)
|
||||
elsif filters[:from_date].blank? && filters[:to_date].present?
|
||||
activities.where(created_at: Time.zone.parse(filters[:to_date]).beginning_of_day.utc..)
|
||||
activities.where('activities.created_at' => Time.zone.parse(filters[:to_date]).beginning_of_day.utc..)
|
||||
else
|
||||
activities
|
||||
end
|
||||
|
@ -36,23 +36,33 @@ class ActivitiesService
|
|||
visible_projects = Project.readable_by_user(user, teams)
|
||||
visible_my_modules = MyModule.readable_by_user(user, teams)
|
||||
visible_forms = Form.readable_by_user(user, teams)
|
||||
# Temporary solution until handling of deleted subjects is fully implemented
|
||||
visible_repository_teams = user.teams.with_granted_permissions(user, RepositoryPermissions::READ, teams)
|
||||
visible_repositories = Repository.readable_by_user(user, teams)
|
||||
|
||||
deleted_repository_activities =
|
||||
activities.where(subject_type: %w(RepositoryBase RepositoryRow))
|
||||
.joins("LEFT OUTER JOIN repositories ON (activities.subject_id = repositories.id AND activities.subject_type = 'RepositoryBase')")
|
||||
.joins("LEFT OUTER JOIN repository_rows ON (activities.subject_id = repository_rows.id AND activities.subject_type = 'RepositoryRow')")
|
||||
.where("(activities.subject_type = 'RepositoryBase' AND repositories.id IS NULL) OR
|
||||
(activities.subject_type = 'RepositoryRow' AND repository_rows.id IS NULL)")
|
||||
|
||||
activities = Activity.from(activities, 'activities')
|
||||
activities = activities.where(project: nil, team_id: teams).where.not(subject_type: %w(RepositoryBase RepositoryRow Protocol Form))
|
||||
.or(activities.where(subject_type: %w(RepositoryBase RepositoryRow), team_id: visible_repository_teams.select(:id)))
|
||||
.or(activities.where(id: deleted_repository_activities.select(:id)))
|
||||
.or(activities.where(subject_type: 'Protocol', subject_id: Protocol.readable_by_user(user, teams).select(:id)))
|
||||
.or(activities.where(project_id: visible_projects.select(:id)).where.not(subject_type: %w(Experiment MyModule Result Protocol)))
|
||||
.or(activities.where(subject_type: 'Experiment', subject_id: Experiment.readable_by_user(user, teams).select(:id)))
|
||||
.or(activities.where("subject_id IN (?) AND subject_type = 'MyModule' OR " \
|
||||
"subject_id IN (?) AND subject_type = 'Result' OR " \
|
||||
"subject_id IN (?) AND subject_type = 'Protocol' OR " \
|
||||
"subject_id IN (?) AND subject_type = 'Form'",
|
||||
"subject_id IN (?) AND subject_type = 'Form' OR " \
|
||||
"subject_id IN (?) AND subject_type = 'RepositoryBase' OR " \
|
||||
"subject_id IN (?) AND subject_type = 'RepositoryRow'",
|
||||
visible_my_modules.select(:id),
|
||||
Result.with_discarded.where(my_module: visible_my_modules).select(:id),
|
||||
Protocol.where(my_module: visible_my_modules).select(:id),
|
||||
visible_forms.select(:id)))
|
||||
visible_forms.select(:id),
|
||||
visible_repositories.select(:id),
|
||||
RepositoryRow.where(repository_id: visible_repositories).select(:id)))
|
||||
|
||||
activities.order(created_at: :desc)
|
||||
.page(filters[:page])
|
||||
|
|
|
@ -25,14 +25,14 @@ module Experiments
|
|||
|
||||
ActiveRecord::Base.transaction do
|
||||
@exp.project = @project
|
||||
sync_user_assignments(@exp)
|
||||
@exp.reset_all_users_assignments!(@user)
|
||||
|
||||
@exp.my_modules.each do |my_module|
|
||||
unless can_move_my_module?(@user, my_module)
|
||||
@errors[:main] = I18n.t('move_to_project_service.my_modules_permission_error')
|
||||
raise
|
||||
end
|
||||
sync_user_assignments(my_module)
|
||||
my_module.reset_all_users_assignments!(@user)
|
||||
clean_up_user_my_modules(my_module)
|
||||
move_tags!(my_module)
|
||||
end
|
||||
|
@ -107,20 +107,6 @@ module Experiments
|
|||
end
|
||||
end
|
||||
|
||||
def sync_user_assignments(object)
|
||||
# remove user assignments where the user are not present on the project
|
||||
object.user_assignments.destroy_all
|
||||
|
||||
UserAssignment.create!(
|
||||
user: @user,
|
||||
assignable: object,
|
||||
assigned: :automatically,
|
||||
user_role: @project.user_assignments.find_by(user: @user).user_role
|
||||
)
|
||||
|
||||
UserAssignments::GenerateUserAssignmentsJob.perform_later(object, @user.id)
|
||||
end
|
||||
|
||||
def clean_up_user_my_modules(my_module)
|
||||
my_module.user_my_modules.where.not(user_id: @project.users.select(:id)).destroy_all
|
||||
end
|
||||
|
|
|
@ -3,7 +3,13 @@
|
|||
module Lists
|
||||
class FormsService < BaseService
|
||||
def fetch_records
|
||||
@records = @raw_data.left_outer_joins(:user_assignments)
|
||||
user_assignments = @raw_data.joins(:user_assignments).select(:assignable_id, :user_id)
|
||||
user_group_assignments = @raw_data.joins(user_group_assignments: { user_group: :user_group_memberships })
|
||||
.select('user_group_assignments.assignable_id, user_group_memberships.user_id')
|
||||
team_assignments = @raw_data.joins(team_assignments: { team: :user_assignments }).select('team_assignments.assignable_id, user_assignments.user_id')
|
||||
|
||||
@records = @raw_data.joins("LEFT JOIN (#{user_assignments.to_sql} UNION #{team_assignments.to_sql} UNION #{user_group_assignments.to_sql})
|
||||
all_assigned_users ON all_assigned_users.assignable_id = forms.id")
|
||||
.left_outer_joins(:form_responses)
|
||||
.joins(
|
||||
'LEFT OUTER JOIN users AS publishers ' \
|
||||
|
@ -12,7 +18,7 @@ module Lists
|
|||
'forms.* AS forms',
|
||||
'publishers.full_name AS published_by_user',
|
||||
'COUNT(DISTINCT form_responses.id) AS used_in_protocols_count',
|
||||
'COUNT(DISTINCT user_assignments.id) AS user_assignment_count'
|
||||
'COUNT(DISTINCT all_assigned_users.user_id) AS user_assignment_count'
|
||||
).group('forms.id', 'publishers.full_name')
|
||||
|
||||
view_mode = @params[:view_mode] || 'active'
|
||||
|
|
|
@ -22,6 +22,11 @@ module Lists
|
|||
@records = Protocol.where('protocols.id IN (?) OR protocols.id IN (?) OR protocols.id IN (?)',
|
||||
original_without_versions, published_versions, new_drafts)
|
||||
|
||||
user_assignments = @records.joins(:user_assignments).select(:assignable_id, :user_id)
|
||||
user_group_assignments = @records.joins(user_group_assignments: { user_group: :user_group_memberships })
|
||||
.select('user_group_assignments.assignable_id, user_group_memberships.user_id')
|
||||
team_assignments = @records.joins(team_assignments: { team: :user_assignments }).select('team_assignments.assignable_id, user_assignments.user_id')
|
||||
|
||||
@records = @records.preload(:parent, :latest_published_version, :draft,
|
||||
:protocol_keywords, user_assignments: %i(user user_role))
|
||||
.joins("LEFT OUTER JOIN protocols protocol_versions " \
|
||||
|
@ -48,9 +53,8 @@ module Lists
|
|||
'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"')
|
||||
.joins("LEFT JOIN (#{user_assignments.to_sql} UNION #{team_assignments.to_sql} UNION #{user_group_assignments.to_sql})
|
||||
all_assigned_users ON all_assigned_users.assignable_id = protocols.id")
|
||||
.group('"protocols"."id"')
|
||||
.select(
|
||||
'"protocols".*',
|
||||
|
@ -61,7 +65,7 @@ module Lists
|
|||
"THEN 0 ELSE COUNT(DISTINCT(\"protocol_versions\".\"id\")) + 1 " \
|
||||
"END AS nr_of_versions",
|
||||
'COUNT(DISTINCT("linked_task_protocols"."id")) AS nr_of_linked_tasks',
|
||||
'COUNT(DISTINCT("all_user_assignments"."id")) AS "nr_of_assigned_users"',
|
||||
'COUNT(DISTINCT all_assigned_users.user_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"'
|
||||
)
|
||||
|
@ -107,9 +111,7 @@ module Lists
|
|||
@records = @records.where(protocols: { published_by_id: @filters[:published_by].values })
|
||||
end
|
||||
|
||||
if @filters[:members].present?
|
||||
@records = @records.where(all_user_assignments: { user_id: @filters[:members].values })
|
||||
end
|
||||
@records = @records.where(all_assigned_users: { user_id: @filters[:members].values }) if @filters[:members].present?
|
||||
|
||||
if @filters[:has_draft].present?
|
||||
@records =
|
||||
|
|
|
@ -5,15 +5,23 @@ module Lists
|
|||
private
|
||||
|
||||
def fetch_records
|
||||
user_assignments = @raw_data.joins(:user_assignments).select(:assignable_id, :user_id)
|
||||
user_group_assignments = @raw_data.joins(user_group_assignments: { user_group: :user_group_memberships })
|
||||
.select('user_group_assignments.assignable_id, user_group_memberships.user_id')
|
||||
team_assignments = @raw_data.joins(team_assignments: { team: :user_assignments }).select('team_assignments.assignable_id, user_assignments.user_id')
|
||||
|
||||
@records = @raw_data.joins('LEFT OUTER JOIN users AS creators ' \
|
||||
'ON repositories.created_by_id = creators.id')
|
||||
.joins('LEFT OUTER JOIN users AS archivers ' \
|
||||
'ON repositories.archived_by_id = archivers.id')
|
||||
.joins(:team)
|
||||
.joins("LEFT OUTER JOIN (#{user_assignments.to_sql} UNION #{team_assignments.to_sql} UNION #{user_group_assignments.to_sql})
|
||||
all_assigned_users ON all_assigned_users.assignable_id = repositories.id")
|
||||
.joins('INNER JOIN teams AS teams_repositories ON teams_repositories.id = repositories.team_id')
|
||||
.select('repositories.*')
|
||||
.select('MAX(teams.name) AS team_name')
|
||||
.select('MAX(teams_repositories.name) AS team_name')
|
||||
.select('MAX(creators.full_name) AS created_by_user')
|
||||
.select('MAX(archivers.full_name) AS archived_by_user')
|
||||
.select('COUNT(DISTINCT all_assigned_users.user_id) AS "assigned_users_count"')
|
||||
.select(shared_sql_select)
|
||||
.preload(:team_assignments, :user_group_assignments, user_assignments: %i(user user_role))
|
||||
.group('repositories.id')
|
||||
|
@ -30,7 +38,7 @@ module Lists
|
|||
@records = @records.where_attributes_like(
|
||||
[
|
||||
'repositories.name',
|
||||
'teams.name',
|
||||
'teams_repositories.name',
|
||||
'creators.full_name',
|
||||
'archivers.full_name'
|
||||
],
|
||||
|
@ -48,7 +56,8 @@ module Lists
|
|||
archived_by: 'archived_by_user',
|
||||
nr_of_rows: 'repository_rows_count',
|
||||
code: 'repositories.id',
|
||||
shared_label: 'shared'
|
||||
shared_label: 'shared',
|
||||
assigned_users: 'assigned_users_count'
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -5,14 +5,11 @@ module Lists
|
|||
private
|
||||
|
||||
def fetch_records
|
||||
@records = @raw_data.joins(
|
||||
'LEFT OUTER JOIN users AS creators ' \
|
||||
'ON user_groups.created_by_id = creators.id'
|
||||
).left_joins(:user_group_memberships).includes(:users)
|
||||
.select('user_groups.* as user_groups')
|
||||
.select('creators.full_name AS created_by_user')
|
||||
@records = @raw_data.left_joins(:created_by).left_joins(:user_group_memberships).includes(:users)
|
||||
.select('user_groups.*')
|
||||
.select('array_agg(users.full_name) AS created_by_user')
|
||||
.select('COUNT(user_groups.id) AS members_count')
|
||||
.group('user_groups.id, creators.full_name')
|
||||
.group('user_groups.id')
|
||||
end
|
||||
|
||||
def filter_records
|
||||
|
|
|
@ -47,6 +47,9 @@ module ModelExporters
|
|||
user_assignments: @experiment.user_assignments.map do |ua|
|
||||
user_assignment(ua)
|
||||
end,
|
||||
team_assignments: @experiment.team_assignments.map do |ta|
|
||||
team_assignment(ta)
|
||||
end,
|
||||
my_modules: my_modules.map { |m| my_module(m) },
|
||||
my_module_groups: my_module_groups
|
||||
}, @assets_to_copy
|
||||
|
@ -61,12 +64,24 @@ module ModelExporters
|
|||
}
|
||||
end
|
||||
|
||||
def team_assignment(team_assignment)
|
||||
{
|
||||
team_id: team_assignment.team_id,
|
||||
assigned_by_id: team_assignment.assigned_by_id,
|
||||
role_name: team_assignment.user_role.name,
|
||||
assigned: team_assignment.assigned
|
||||
}
|
||||
end
|
||||
|
||||
def my_module(my_module)
|
||||
{
|
||||
my_module: my_module,
|
||||
user_assignments: my_module.user_assignments.map do |ua|
|
||||
user_assignment(ua)
|
||||
end,
|
||||
team_assignments: my_module.team_assignments.map do |ta|
|
||||
team_assignment(ta)
|
||||
end,
|
||||
my_module_status_name: my_module.my_module_status&.name,
|
||||
outputs: my_module.outputs,
|
||||
my_module_tags: my_module.my_module_tags,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue