diff --git a/Gemfile b/Gemfile index 69ed0c7e9..34ca6913d 100644 --- a/Gemfile +++ b/Gemfile @@ -38,7 +38,7 @@ gem 'rack-cors' gem 'uglifier', '>= 1.3.0' -gem 'activerecord-import' +gem 'activerecord-import', '~> 2.2.0' gem 'acts_as_list' gem 'ajax-datatables-rails', '~> 0.3.1' gem 'aspector' # Aspect-oriented programming for Rails @@ -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' diff --git a/Gemfile.lock b/Gemfile.lock index 99f845814..7d2ec2351 100644 --- a/Gemfile.lock +++ b/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,16 +85,16 @@ 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 (1.4.1) + activerecord-import (2.2.0) activerecord (>= 4.2) activerecord-session_store (2.1.0) actionpack (>= 6.1) @@ -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) @@ -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) @@ -810,7 +874,7 @@ PLATFORMS DEPENDENCIES active_model_serializers (~> 0.10.15) - activerecord-import + activerecord-import (~> 2.2.0) activerecord-session_store acts_as_list ajax-datatables-rails (~> 0.3.1) @@ -836,7 +900,6 @@ DEPENDENCIES cssbundling-rails cucumber-rails database_cleaner - datadog deface (~> 1.9) delayed_job_active_record devise (~> 4.9.4) @@ -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 diff --git a/VERSION b/VERSION index 758e98242..372cf402c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.43.0.2 +1.44.0 diff --git a/app/assets/images/icon/group.svg b/app/assets/images/icon/group.svg new file mode 100644 index 000000000..e7057628d --- /dev/null +++ b/app/assets/images/icon/group.svg @@ -0,0 +1,5 @@ + diff --git a/app/assets/images/user_groups/promo.svg b/app/assets/images/user_groups/promo.svg new file mode 100644 index 000000000..d8cba3029 --- /dev/null +++ b/app/assets/images/user_groups/promo.svg @@ -0,0 +1,100 @@ + diff --git a/app/assets/javascripts/my_modules/archived.js b/app/assets/javascripts/my_modules/archived.js index 19c2f4691..4096bea4b 100644 --- a/app/assets/javascripts/my_modules/archived.js +++ b/app/assets/javascripts/my_modules/archived.js @@ -82,13 +82,9 @@ id: data.data.id, type: data.data.type }; - const { rolesUrl } = container.dataset; - const params = { - object, - roles_path: rolesUrl - }; + const modal = $('#accessModalComponent').data('accessModal'); - modal.params = params; + modal.params = { object }; modal.open(); }); }); diff --git a/app/assets/javascripts/protocols/new_protocol.js b/app/assets/javascripts/protocols/new_protocol.js index 1b8735365..70493cdf7 100644 --- a/app/assets/javascripts/protocols/new_protocol.js +++ b/app/assets/javascripts/protocols/new_protocol.js @@ -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'); }) diff --git a/app/assets/javascripts/protocols/protocolsio.js b/app/assets/javascripts/protocols/protocolsio.js index 65dde90dd..ad2c1cda9 100644 --- a/app/assets/javascripts/protocols/protocolsio.js +++ b/app/assets/javascripts/protocols/protocolsio.js @@ -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; diff --git a/app/assets/javascripts/shared/inline_editing.js b/app/assets/javascripts/shared/inline_editing.js index 0d5241d1c..add20c34f 100644 --- a/app/assets/javascripts/shared/inline_editing.js +++ b/app/assets/javascripts/shared/inline_editing.js @@ -12,7 +12,9 @@ let inlineEditing = (function() { } if ($(container).data('params-group') === 'protocol' && $(container).hasClass('inline-editing-container')) { - $('.view-mode').text(I18n.t('protocols.draft_name', { name: $('.view-mode').text() })); + container.find('.view-mode').text(I18n.t('protocols.draft_name', { name: container.find('.view-mode').text() })); + } else if ($(container).data('params-group') === 'user_group' && $(container).hasClass('inline-editing-container')) { + container.find('.view-mode').text(`${I18n.t('user_groups.show.title')} ${container.find('.view-mode').text()}`); } } diff --git a/app/assets/javascripts/users/settings/teams/show.js b/app/assets/javascripts/users/settings/teams/show.js index d9a887b2a..cd213e4b6 100644 --- a/app/assets/javascripts/users/settings/teams/show.js +++ b/app/assets/javascripts/users/settings/teams/show.js @@ -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); diff --git a/app/assets/stylesheets/shared/content_pane.scss b/app/assets/stylesheets/shared/content_pane.scss index b60b61ae0..7ce218414 100644 --- a/app/assets/stylesheets/shared/content_pane.scss +++ b/app/assets/stylesheets/shared/content_pane.scss @@ -17,6 +17,14 @@ .fixed-content-body { height: calc(100vh - var(--content-header-size) - var(--navbar-height)); width: 100%; + + &.user-groups-table-container { + height: calc(100vh - var(--content-header-size) - var(--navbar-height) - 72px); + } + + &.user-group-table-container { + height: calc(100vh - var(--content-header-size) - var(--navbar-height) - 104px); + } } .content-header { diff --git a/app/components/reports/repositories_input_component.rb b/app/components/reports/repositories_input_component.rb index f168cc396..f6dc1dc17 100644 --- a/app/components/reports/repositories_input_component.rb +++ b/app/components/reports/repositories_input_component.rb @@ -4,10 +4,10 @@ module Reports class RepositoriesInputComponent < TemplateValueComponent def initialize(report:, name:, label:, placeholder: nil, editing: true, displayed_field: :name, user: nil) super(report: report, name: name, label: label, placeholder: placeholder, editing: editing) - live_repositories = Repository.viewable_by_user(user, report.team).sort_by { |r| r.name.downcase } + live_repositories = Repository.readable_by_user(user, report.team).sort_by { |r| r.name.downcase } snapshots_of_deleted = RepositorySnapshot.left_outer_joins(:original_repository) .where(team: report.team) - .where.not(original_repository: live_repositories) + .where.not(original_repository: report.team.repositories) .select('DISTINCT ON ("repositories"."parent_id") "repositories".*') .sort_by { |r| r.name.downcase } @repositories = live_repositories + snapshots_of_deleted diff --git a/app/controllers/access_permissions/base_controller.rb b/app/controllers/access_permissions/base_controller.rb new file mode 100644 index 000000000..4b493fbee --- /dev/null +++ b/app/controllers/access_permissions/base_controller.rb @@ -0,0 +1,200 @@ +# frozen_string_literal: true + +module AccessPermissions + class BaseController < ApplicationController + include InputSanitizeHelper + include UserRolesHelper + + before_action :set_model + before_action :set_assignment, only: %i(create update destroy) + before_action :check_read_permissions, only: %i(show show_user_group_assignments user_roles) + before_action :check_manage_permissions, except: %i(show show_user_group_assignments user_roles) + before_action :load_available_users, only: %i(new create) + + def show + render json: @model.user_assignments.where(team: current_team).includes(:user_role, :user).order('users.full_name ASC'), + each_serializer: UserAssignmentSerializer, user: current_user + end + + def new + render json: @available_users, each_serializer: UserSerializer, user: current_user + end + + def edit; end + + def create + ActiveRecord::Base.transaction do + @assignment.update!( + user_role_id: permitted_params[:user_role_id], + assigned_by: current_user, + assigned: :manually + ) + + case assignment_type + when :team + log_activity(:"#{model_parameter}_access_granted_all_team_members", team: @assignment.team.id, role: @assignment.user_role.name) + when :user + log_activity(:"#{model_parameter}_access_granted", user_target: @assignment.user.id, role: @assignment.user_role.name) + when :user_group + log_activity(:"#{model_parameter}_access_granted_user_group", user_group: @assignment.user_group.id, role: @assignment.user_role.name) + end + + propagate_job + + @message = if assignment_type == :team + t('access_permissions.create.success', member_name: t('access_permissions.all_team')) + else + t('access_permissions.create.success', member_name: escape_input(assignment_type == :user_group ? @assignment.user_group.name : @assignment.user.name)) + end + render json: { message: @message } + rescue ActiveRecord::RecordInvalid => e + Rails.logger.error e.message + errors = @model.errors.present? ? @model.errors&.map(&:message)&.join(',') : e.message + render json: { flash: errors }, status: :unprocessable_entity + raise ActiveRecord::Rollback + end + end + + def update + # prevent role change if it would result in no manually assigned users having the user management permission + new_user_role = UserRole.find(permitted_params[:user_role_id]) + if permitted_params[:user_id].present? && permitted_params[:user_id] != 'all' && !new_user_role.has_permission?(manage_permission_constant) && + @assignment.last_with_permission?(manage_permission_constant, assigned: :manually) + raise ActiveRecord::RecordInvalid + end + + @assignment.update!(user_role_id: permitted_params[:user_role_id], assigned_by: current_user) + + case assignment_type + when :team + log_activity(:"#{model_parameter}_access_changed_all_team_members", team: @assignment.team.id, role: @assignment.user_role.name) + when :user + log_activity(:"#{model_parameter}_access_changed", user_target: @assignment.user.id, role: @assignment.user_role.name) + when :user_group + log_activity(:"#{model_parameter}_access_changed_user_group", user_group: @assignment.user_group.id, role: @assignment.user_role.name) + 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 + + def destroy + # prevent deletion of last manually assigned user that can manage users + if params[:user_id].present? && params[:user_id] != 'all' && @assignment.last_with_permission?(manage_permission_constant, assigned: :manually) + raise ActiveRecord::RecordInvalid + end + + UserAssignments::PropagateAssignmentJob.perform_now(@assignment, destroy: true) + + case assignment_type + when :team + @assigned_name = @assignment.team.name + log_activity(:"#{model_parameter}_access_revoked_all_team_members", team: @assignment.team.id, role: @assignment.user_role.name) + when :user_group + @assigned_name = @assignment.user_group.name + log_activity(:"#{model_parameter}_access_revoked_user_group", user_group: @assignment.user_group.id, role: @assignment.user_role.name) + when :user + @assigned_name = @assignment.user.full_name + log_activity(:"#{model_parameter}_access_revoked", user_target: @assignment.user.id, role: @assignment.user_role.name) + end + + render json: { message: t('access_permissions.destroy.success', member_name: escape_input(@assigned_name)) } + rescue ActiveRecord::RecordInvalid + render json: { message: t('access_permissions.destroy.failure') }, + status: :unprocessable_entity + end + + def show_user_group_assignments + 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 + + def unassigned_user_groups + render json: current_team.user_groups.where.not(id: @model.user_group_assignments.select(:user_group_id)), + each_serializer: UserGroupSerializer, user: current_user + end + + def user_roles + render json: { data: user_roles_collection(@model).map(&:reverse) } + end + + private + + def model_parameter + @model.class.permission_class.model_name.param_key + end + + def manage_permission_constant + "#{@model.class.permission_class.name}Permissions::USERS_MANAGE".constantize + end + + def permitted_default_public_user_role_params + params.require(:object).permit(:default_public_user_role_id) + end + + def permitted_params + params.require(:user_assignment) + .permit(%i(user_role_id user_id user_group_id team_id)) + end + + def load_available_users + @available_users = current_team.users.where.not(id: @model.user_assignments.where(team: current_team).select(:user_id)).order(users: { full_name: :asc }) + end + + def propagate_job(destroy: false) + return unless @model.has_permission_children? + + UserAssignments::PropagateAssignmentJob.perform_later( + @assignment, + destroy: destroy + ) + end + + def check_manage_permissions + raise NotImplementedError + end + + def check_read_permissions + raise NotImplementedError + end + + def log_activity(type_of, message_items = {}) + message_items = { model_parameter => @model.id }.merge(message_items) + + Activities::CreateActivityService + .call(activity_type: type_of, + owner: current_user, + subject: @model, + team: @model.team, + project: @project, + message_items: message_items) + end + + def set_assignment + case assignment_type + when :user, :user_group + @assignment = @model.public_send(:"#{assignment_type}_assignments").find_or_initialize_by( + "#{assignment_type}_id": permitted_params[:"#{assignment_type}_id"], + team: current_team + ) + when :team + @assignment = + @model.team_assignments + .find_or_initialize_by(team: current_team, assignable: @model) + end + end + + def assignment_type + if permitted_params[:team_id].present? || permitted_params[:user_id] == 'all' + :team + elsif permitted_params[:user_group_id].present? + :user_group + elsif permitted_params[:user_id].present? + :user + end + end + end +end diff --git a/app/controllers/access_permissions/experiments_controller.rb b/app/controllers/access_permissions/experiments_controller.rb index 0da893b75..0f661007e 100644 --- a/app/controllers/access_permissions/experiments_controller.rb +++ b/app/controllers/access_permissions/experiments_controller.rb @@ -1,90 +1,61 @@ # frozen_string_literal: true module AccessPermissions - class ExperimentsController < ApplicationController - before_action :set_experiment + class ExperimentsController < BaseController before_action :set_project - before_action :check_read_permissions, only: %i(show) - before_action :check_manage_permissions, only: %i(edit update) - - def show - render json: @experiment.user_assignments.includes(:user_role, :user).order('users.full_name ASC'), - each_serializer: UserAssignmentSerializer, user: current_user - end - - def new - render json: @available_users, each_serializer: UserSerializer, user: current_user - end - - - def edit; end def update - user_id = permitted_update_params[:user_id] - @user_assignment = @experiment.user_assignments.find_by(user_id: user_id, team: current_team) + 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"] || current_team.id, + team: current_team + ) - if permitted_update_params[:user_role_id] == 'reset' - @user_assignment.update!( - user_role_id: @project.user_assignments.find_by(user_id: user_id, team: current_team).user_role_id, + @assignment.update!( + user_role_id: parent_assignment.user_role_id, + assigned_by: current_user, assigned: :automatically ) else - @user_assignment.update!( - user_role_id: permitted_update_params[:user_role_id], + @assignment.update!( + user_role_id: permitted_params[:user_role_id], + assigned_by: current_user, assigned: :manually ) end - UserAssignments::PropagateAssignmentJob.perform_later( - @experiment, - @user_assignment.user.id, - @user_assignment.user_role, - current_user.id - ) + UserAssignments::PropagateAssignmentJob.perform_later(@assignment) - log_change_activity + 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 - def permitted_update_params - params.require(:user_assignment) - .permit(%i(user_role_id user_id)) - end - def set_project - @project = @experiment.project + @project = @model.project end - def set_experiment - @experiment = Experiment.includes(user_assignments: %i(user user_role)).find_by(id: params[:id]) + def set_model + @model = Experiment.includes(user_assignments: %i(user user_role)).find_by(id: params[:id]) - render_404 unless @experiment + render_404 unless @model end def check_manage_permissions - render_403 unless can_manage_experiment_users?(@experiment) + render_403 unless can_manage_experiment_users?(@model) end def check_read_permissions - render_403 unless can_read_experiment?(@experiment) - end - - def log_change_activity - Activities::CreateActivityService.call( - activity_type: :change_user_role_on_experiment, - owner: current_user, - subject: @experiment, - team: @project.team, - project: @project, - message_items: { - experiment: @experiment.id, - user_target: @user_assignment.user_id, - role: @user_assignment.user_role.name - } - ) + render_403 unless can_read_experiment?(@model) end end end diff --git a/app/controllers/access_permissions/forms_controller.rb b/app/controllers/access_permissions/forms_controller.rb index b530fc444..ca30852e0 100644 --- a/app/controllers/access_permissions/forms_controller.rb +++ b/app/controllers/access_permissions/forms_controller.rb @@ -1,186 +1,21 @@ # frozen_string_literal: true module AccessPermissions - class FormsController < ApplicationController - include InputSanitizeHelper - - before_action :set_form - before_action :check_read_permissions, only: %i(show) - before_action :check_manage_permissions, except: %i(show) - before_action :available_users, only: %i(new create) - - def show - render json: @form.user_assignments.includes(:user_role, :user).order('users.full_name ASC'), - each_serializer: UserAssignmentSerializer, user: current_user - end - - def new - render json: @available_users, each_serializer: UserSerializer, user: current_user - end - - def edit; end - - def create - ActiveRecord::Base.transaction do - created_count = 0 - if permitted_create_params[:user_id] == 'all' - @form.update!(visibility: :visible, default_public_user_role_id: permitted_create_params[:user_role_id]) - log_activity(:form_access_granted_all_team_members, - { team: @form.team.id, role: @form.default_public_user_role&.name }) - else - user_assignment = UserAssignment.find_or_initialize_by( - assignable: @form, - user_id: permitted_create_params[:user_id], - team: current_team - ) - - user_assignment.update!( - user_role_id: permitted_create_params[:user_role_id], - assigned_by: current_user, - assigned: :manually - ) - - log_activity(:form_access_granted, { user_target: user_assignment.user.id, - role: user_assignment.user_role.name }) - created_count += 1 - end - - @message = if created_count.zero? - t('access_permissions.create.success', member_name: t('access_permissions.all_team')) - else - t('access_permissions.create.success', member_name: escape_input(user_assignment.user.name)) - end - render json: { message: @message } - rescue ActiveRecord::RecordInvalid => e - Rails.logger.error e.message - errors = @form.errors.present? ? @form.errors&.map(&:message)&.join(',') : e.message - render json: { flash: errors }, status: :unprocessable_entity - raise ActiveRecord::Rollback - end - end - - def update - @user_assignment = @form.user_assignments.find_by( - user_id: permitted_update_params[:user_id], - team: current_team - ) - - # prevent role change if it would result in no manually assigned users having the user management permission - new_user_role = UserRole.find(permitted_update_params[:user_role_id]) - if !new_user_role.has_permission?(FormPermissions::USERS_MANAGE) && - @user_assignment.last_with_permission?(FormPermissions::USERS_MANAGE, assigned: :manually) - raise ActiveRecord::RecordInvalid - end - - @user_assignment.update!(permitted_update_params) - log_activity(:form_access_changed, { user_target: @user_assignment.user.id, - role: @user_assignment.user_role.name }) - - render :form_member - rescue ActiveRecord::RecordInvalid - render json: { flash: t('access_permissions.update.failure') }, status: :unprocessable_entity - end - - def destroy - user = @form.assigned_users.find(params[:user_id]) - user_assignment = @form.user_assignments.find_by(user: user, team: current_team) - - # prevent deletion of last manually assigned user that can manage users - raise ActiveRecord::RecordInvalid if user_assignment.last_with_permission?(FormPermissions::USERS_MANAGE, assigned: :manually) - - Protocol.transaction do - if @form.visible? - user_assignment.update!( - user_role: @form.default_public_user_role, - assigned: :automatically - ) - else - user_assignment.destroy! - end - log_activity(:form_access_revoked, { user_target: user_assignment.user.id, - role: user_assignment.user_role.name }) - end - - render json: { message: t('access_permissions.destroy.success', member_name: user.full_name) } - rescue ActiveRecord::RecordInvalid => e - Rails.logger.error e.message - render json: { message: t('access_permissions.destroy.failure') }, status: :unprocessable_entity - raise ActiveRecord::Rollback - end - - def update_default_public_user_role - ActiveRecord::Base.transaction do - current_role = @form.default_public_user_role.name - @form.update!(permitted_default_public_user_role_params) - - # revoke all team members access - if permitted_default_public_user_role_params[:default_public_user_role_id].blank? - log_activity(:form_access_revoked_all_team_members, - { team: @form.team.id, role: current_role }) - render json: { flash: t('access_permissions.update.revoke_all_team_members') }, status: :ok - else - # update all team members access - log_activity(:form_access_changed_all_team_members, - { team: @form.team.id, role: @form.default_public_user_role&.name }) - end - rescue ActiveRecord::RecordInvalid => e - Rails.logger.error e.message - render json: { flash: @form.errors&.map(&:message)&.join(',') }, status: :unprocessable_entity - raise ActiveRecord::Rollback - end - end - + class FormsController < BaseController private - def permitted_default_public_user_role_params - params.require(:object).permit(:default_public_user_role_id) - end + def set_model + @model = current_team.forms.includes(user_assignments: %i(user user_role)).find_by(id: params[:id]) - def permitted_update_params - params.require(:user_assignment) - .permit(%i(user_role_id user_id)) - end - - def permitted_create_params - params.require(:user_assignment) - .permit(%i(user_id user_role_id)) - end - - def available_users - # automatically assigned or not assigned to project - @available_users = current_team.users.where( - id: @form.user_assignments.automatically_assigned.select(:user_id) - ).or( - current_team.users.where.not(id: @form.users.select(:id)) - ).order('users.full_name ASC') - end - - def set_form - @form = current_team.forms.includes(user_assignments: %i(user user_role)).find_by(id: params[:id]) - - return render_404 unless @form - - @form = @form.parent if @form.parent_id + render_404 unless @model end def check_manage_permissions - render_403 unless can_manage_form_users?(@form) + render_403 unless can_manage_form_users?(@model) end def check_read_permissions - render_403 unless can_read_form?(@form) || can_manage_team?(@form.team) - end - - def log_activity(type_of, message_items = {}) - message_items = { form: @form.id }.merge(message_items) - - Activities::CreateActivityService - .call(activity_type: type_of, - owner: current_user, - subject: @form, - team: @form.team, - project: nil, - message_items: message_items) + render_403 unless can_read_form?(@model) || can_manage_team?(@model.team) end end end diff --git a/app/controllers/access_permissions/my_modules_controller.rb b/app/controllers/access_permissions/my_modules_controller.rb index 0e501fbaa..62cc95a0c 100644 --- a/app/controllers/access_permissions/my_modules_controller.rb +++ b/app/controllers/access_permissions/my_modules_controller.rb @@ -1,86 +1,64 @@ # frozen_string_literal: true module AccessPermissions - class MyModulesController < ApplicationController - before_action :set_my_module + class MyModulesController < BaseController before_action :set_experiment before_action :set_project - before_action :check_read_permissions, only: %i(show) - before_action :check_manage_permissions, only: %i(edit update) - - def show - render json: @my_module.user_assignments.includes(:user_role, :user).order('users.full_name ASC'), - each_serializer: UserAssignmentSerializer, user: current_user - end - - def new - render json: @available_users, each_serializer: UserSerializer, user: current_user - end - def edit; end def update - user_id = permitted_update_params[:user_id] - @user_assignment = @my_module.user_assignments.find_by(user_id: user_id, team: current_team) + 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"] || current_team.id, + team: current_team + ) - if permitted_update_params[:user_role_id] == 'reset' - @user_assignment.update!( - user_role_id: @experiment.user_assignments.find_by(user_id: user_id, team: current_team).user_role_id, + @assignment.update!( + user_role_id: parent_assignment.user_role_id, + assigned_by: current_user, assigned: :automatically ) else - @user_assignment.update!( - user_role_id: permitted_update_params[:user_role_id], + @assignment.update!( + user_role_id: permitted_params[:user_role_id], + assigned_by: current_user, assigned: :manually ) end - log_change_activity + 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 :my_module_member + render json: { user_role_id: @assignment.user_role_id }, status: :ok end private - def permitted_update_params - params.require(:user_assignment) - .permit(%i(user_role_id user_id)) - end - def set_project @project = @experiment.project end def set_experiment - @experiment = @my_module.experiment + @experiment = @model.experiment end - def set_my_module - @my_module = MyModule.includes(user_assignments: %i(user user_role)).find_by(id: params[:id]) + def set_model + @model = MyModule.includes(user_assignments: %i(user user_role)).find_by(id: params[:id]) - render_404 unless @my_module + render_404 unless @model end def check_manage_permissions - render_403 unless can_manage_my_module_users?(@my_module) + render_403 unless can_manage_my_module_users?(@model) end def check_read_permissions - render_403 unless can_read_my_module?(@my_module) - end - - def log_change_activity - Activities::CreateActivityService.call( - activity_type: :change_user_role_on_my_module, - owner: current_user, - subject: @my_module, - team: @project.team, - project: @project, - message_items: { - my_module: @my_module.id, - user_target: @user_assignment.user_id, - role: @user_assignment.user_role.name - } - ) + render_403 unless can_read_my_module?(@model) end end end diff --git a/app/controllers/access_permissions/projects_controller.rb b/app/controllers/access_permissions/projects_controller.rb index 3527cfefe..23cf7068e 100644 --- a/app/controllers/access_permissions/projects_controller.rb +++ b/app/controllers/access_permissions/projects_controller.rb @@ -1,206 +1,36 @@ # frozen_string_literal: true module AccessPermissions - class ProjectsController < ApplicationController - include InputSanitizeHelper - - before_action :set_project - before_action :check_read_permissions, only: %i(show) - before_action :check_manage_permissions, except: %i(show) - before_action :available_users, only: %i(new create) - - def show - render json: @project.user_assignments.includes(:user_role, :user).order('users.full_name ASC'), - each_serializer: UserAssignmentSerializer, user: current_user - end - - def new - render json: @available_users, each_serializer: UserSerializer, user: current_user - end - - def edit; end - - def create - ActiveRecord::Base.transaction do - created_count = 0 - if permitted_create_params[:user_id] == 'all' - @project.update!(visibility: :visible, default_public_user_role_id: permitted_create_params[:user_role_id]) - log_activity(:project_grant_access_to_all_team_members, - { visibility: t('projects.activity.visibility_visible'), - role: @project.default_public_user_role.name, - team: @project.team.id }) - else - user_assignment = UserAssignment.find_or_initialize_by( - assignable: @project, - user_id: permitted_create_params[:user_id], - team: current_team - ) - - user_assignment.update!( - user_role_id: permitted_create_params[:user_role_id], - assigned_by: current_user, - assigned: :manually - ) - - log_activity(:assign_user_to_project, { user_target: user_assignment.user.id, - role: user_assignment.user_role.name }) - created_count += 1 - propagate_job(user_assignment) - end - - @message = if created_count.zero? - t('access_permissions.create.success', member_name: t('access_permissions.all_team')) - else - t('access_permissions.create.success', member_name: escape_input(user_assignment.user.name)) - end - render json: { message: @message } - rescue ActiveRecord::RecordInvalid => e - Rails.logger.error e.message - errors = @project.errors.present? ? @project.errors&.map(&:message)&.join(',') : e.message - render json: { flash: errors }, status: :unprocessable_entity - raise ActiveRecord::Rollback - end - end - - def update - @user_assignment = @project.user_assignments.find_by( - user_id: permitted_update_params[:user_id], - team: current_team - ) - - # prevent role change if it would result in no manually assigned users having the user management permission - new_user_role = UserRole.find(permitted_update_params[:user_role_id]) - if !new_user_role.has_permission?(ProjectPermissions::USERS_MANAGE) && - @user_assignment.last_with_permission?(ProjectPermissions::USERS_MANAGE, assigned: :manually) - raise ActiveRecord::RecordInvalid - end - - @user_assignment.update!(permitted_update_params) - - log_activity(:change_user_role_on_project, { user_target: @user_assignment.user.id, - role: @user_assignment.user_role.name }) - propagate_job(@user_assignment) - - render :project_member - rescue ActiveRecord::RecordInvalid - render json: { flash: t('access_permissions.update.failure') }, status: :unprocessable_entity - end - - def destroy - user = @project.assigned_users.find(params[:user_id]) - user_assignment = @project.user_assignments.find_by(user: user, team: current_team) - - # prevent deletion of last manually assigned user that can manage users - if user_assignment.last_with_permission?(ProjectPermissions::USERS_MANAGE, assigned: :manually) - raise ActiveRecord::RecordInvalid - end - - UserAssignments::PropagateAssignmentJob.perform_now( - @project, - user_assignment.user.id, - user_assignment.user_role, - current_user.id, - destroy: true - ) - - log_activity(:unassign_user_from_project, { user_target: user_assignment.user.id, - role: user_assignment.user_role.name }) - - render json: { message: t('access_permissions.destroy.success', member_name: escape_input(user.full_name)) } - rescue ActiveRecord::RecordInvalid - render json: { message: t('access_permissions.destroy.failure') }, - status: :unprocessable_entity - end - - def update_default_public_user_role - Project.transaction do - @project.visibility_will_change! - @project.last_modified_by = current_user - if permitted_default_public_user_role_params[:default_public_user_role_id].blank? - # revoke all team members access - @project.visibility = :hidden - previous_user_role_name = @project.default_public_user_role.name - @project.default_public_user_role_id = nil - @project.save! - log_activity(:project_remove_access_from_all_team_members, - { visibility: t('projects.activity.visibility_hidden'), - role: previous_user_role_name, - team: @project.team.id }) - render json: { message: t('access_permissions.update.revoke_all_team_members') } - else - # update all team members access - @project.visibility = :visible - @project.assign_attributes(permitted_default_public_user_role_params) - @project.save! - log_activity(:project_access_changed_all_team_members, - { team: @project.team.id, role: @project.default_public_user_role&.name }) - end - rescue ActiveRecord::RecordInvalid => e - Rails.logger.error e.message - render json: { flash: @project.errors&.map(&:message)&.join(',') }, status: :unprocessable_entity - raise ActiveRecord::Rollback - end - end + class ProjectsController < AccessPermissions::BaseController + # legacy activity map + ACTIVITY_TYPE_MAP = { + project_access_granted: :assign_user_to_project, + project_access_changed: :change_user_role_on_project, + project_access_revoked: :unassign_user_from_project, + project_access_granted_all_team_members: :project_grant_access_to_all_team_members, + project_access_changed_all_team_members: :project_access_changed_all_team_members, + project_access_revoked_all_team_members: :project_remove_access_from_all_team_members + }.freeze private - def permitted_default_public_user_role_params - params.require(:object).permit(:default_public_user_role_id) - end + def set_model + @model = current_team.projects.includes(user_assignments: %i(user user_role)).find_by(id: params[:id]) + @project = @model - def permitted_update_params - params.require(:user_assignment) - .permit(%i(user_role_id user_id)) - end - - def permitted_create_params - params.require(:user_assignment) - .permit(%i(user_id user_role_id)) - end - - def set_project - @project = current_team.projects.includes(user_assignments: %i(user user_role)).find_by(id: params[:id]) - - render_404 unless @project - end - - def propagate_job(user_assignment, destroy: false) - UserAssignments::PropagateAssignmentJob.perform_later( - @project, - user_assignment.user.id, - user_assignment.user_role, - current_user.id, - destroy: destroy - ) + render_404 unless @model end def check_manage_permissions - render_403 unless can_manage_project_users?(@project) + render_403 unless can_manage_project_users?(@model) end def check_read_permissions - render_403 unless can_read_project_users?(@project) - end - - def available_users - # automatically assigned or not assigned to project - @available_users = current_team.users.where( - id: @project.user_assignments.automatically_assigned.select(:user_id) - ).or( - current_team.users.where.not(id: @project.users.select(:id)) - ).order('users.full_name ASC') + render_403 unless can_read_project_users?(@model) end def log_activity(type_of, message_items = {}) - message_items = { project: @project.id }.merge(message_items) - - Activities::CreateActivityService - .call(activity_type: type_of, - owner: current_user, - subject: @project, - team: @project.team, - project: @project, - message_items: message_items) + super(ACTIVITY_TYPE_MAP[type_of] || type_of, message_items) end end end diff --git a/app/controllers/access_permissions/protocols_controller.rb b/app/controllers/access_permissions/protocols_controller.rb index 1614a93ae..32eede72d 100644 --- a/app/controllers/access_permissions/protocols_controller.rb +++ b/app/controllers/access_permissions/protocols_controller.rb @@ -1,188 +1,41 @@ # frozen_string_literal: true module AccessPermissions - class ProtocolsController < ApplicationController - include InputSanitizeHelper - - before_action :set_protocol - before_action :check_read_permissions, only: %i(show) - before_action :check_manage_permissions, except: %i(show) - before_action :available_users, only: %i(new create) - - def show - render json: @protocol.user_assignments.includes(:user_role, :user).order('users.full_name ASC'), - each_serializer: UserAssignmentSerializer, user: current_user - end - - def new - render json: @available_users, each_serializer: UserSerializer, user: current_user - end - - def edit; end - - def create - ActiveRecord::Base.transaction do - created_count = 0 - if permitted_create_params[:user_id] == 'all' - @protocol.update!(visibility: :visible, default_public_user_role_id: permitted_create_params[:user_role_id]) - log_activity(:protocol_template_access_granted_all_team_members, - { team: @protocol.team.id, role: @protocol.default_public_user_role&.name }) - else - user_assignment = UserAssignment.find_or_initialize_by( - assignable: @protocol, - user_id: permitted_create_params[:user_id], - team: current_team - ) - - user_assignment.update!( - user_role_id: permitted_create_params[:user_role_id], - assigned_by: current_user, - assigned: :manually - ) - - log_activity(:protocol_template_access_granted, { user_target: user_assignment.user.id, - role: user_assignment.user_role.name }) - created_count += 1 - end - - @message = if created_count.zero? - t('access_permissions.create.success', member_name: t('access_permissions.all_team')) - else - t('access_permissions.create.success', member_name: escape_input(user_assignment.user.name)) - end - render json: { message: @message } - rescue ActiveRecord::RecordInvalid => e - Rails.logger.error e.message - errors = @protocol.errors.present? ? @protocol.errors&.map(&:message)&.join(',') : e.message - render json: { flash: errors }, status: :unprocessable_entity - raise ActiveRecord::Rollback - end - end - - def update - @user_assignment = @protocol.user_assignments.find_by( - user_id: permitted_update_params[:user_id], - team: current_team - ) - - # prevent role change if it would result in no manually assigned users having the user management permission - new_user_role = UserRole.find(permitted_update_params[:user_role_id]) - if !new_user_role.has_permission?(ProtocolPermissions::USERS_MANAGE) && - @user_assignment.last_with_permission?(ProtocolPermissions::USERS_MANAGE, assigned: :manually) - raise ActiveRecord::RecordInvalid - end - - @user_assignment.update!(permitted_update_params) - log_activity(:protocol_template_access_changed, { user_target: @user_assignment.user.id, - role: @user_assignment.user_role.name }) - - render :protocol_member - rescue ActiveRecord::RecordInvalid - render json: { flash: t('access_permissions.update.failure') }, status: :unprocessable_entity - end - - def destroy - user = @protocol.assigned_users.find(params[:user_id]) - user_assignment = @protocol.user_assignments.find_by(user: user, team: current_team) - - # prevent deletion of last manually assigned user that can manage users - if user_assignment.last_with_permission?(ProtocolPermissions::USERS_MANAGE, assigned: :manually) - raise ActiveRecord::RecordInvalid - end - - Protocol.transaction do - if @protocol.visible? - user_assignment.update!( - user_role: @protocol.default_public_user_role, - assigned: :automatically - ) - else - user_assignment.destroy! - end - log_activity(:protocol_template_access_revoked, { user_target: user_assignment.user.id, - role: user_assignment.user_role.name }) - end - - render json: { message: t('access_permissions.destroy.success', member_name: user.full_name) } - rescue ActiveRecord::RecordInvalid => e - Rails.logger.error e.message - render json: { message: t('access_permissions.destroy.failure') }, status: :unprocessable_entity - raise ActiveRecord::Rollback - end - - def update_default_public_user_role - ActiveRecord::Base.transaction do - current_role = @protocol.default_public_user_role.name - @protocol.update!(permitted_default_public_user_role_params) - - # revoke all team members access - if permitted_default_public_user_role_params[:default_public_user_role_id].blank? - log_activity(:protocol_template_access_revoked_all_team_members, - { team: @protocol.team.id, role: current_role }) - render json: { flash: t('access_permissions.update.revoke_all_team_members') }, status: :ok - else - # update all team members access - log_activity(:protocol_template_access_changed_all_team_members, - { team: @protocol.team.id, role: @protocol.default_public_user_role&.name }) - end - rescue ActiveRecord::RecordInvalid => e - Rails.logger.error e.message - render json: { flash: @protocol&.errors&.map(&:message)&.join(',') }, status: :unprocessable_entity - raise ActiveRecord::Rollback - end - end + class ProtocolsController < BaseController + # protocol template activity naming is inconsistent with model name (model_parameter in BaseController), + # so we need to map them for now + ACTIVITY_TYPE_MAP = { + protocol_access_granted_all_team_members: :protocol_template_access_granted_all_team_members, + protocol_access_revoked_all_team_members: :protocol_template_access_revoked_all_team_members, + protocol_access_changed_all_team_members: :protocol_template_access_changed_all_team_members, + protocol_access_granted: :protocol_template_access_granted, + protocol_access_revoked: :protocol_template_access_revoked, + protocol_access_changed: :protocol_template_access_changed, + protocol_access_granted_user_group: :protocol_template_access_granted_user_group, + protocol_access_revoked_user_group: :protocol_template_access_revoked_user_group, + protocol_access_changed_user_group: :protocol_template_access_changed_user_group + }.freeze private - def permitted_default_public_user_role_params - params.require(:object).permit(:default_public_user_role_id) - end + def set_model + @model = current_team.protocols.includes(user_assignments: %i(user user_role)).find_by(id: params[:id]) - def permitted_update_params - params.require(:user_assignment) - .permit(%i(user_role_id user_id)) - end + return render_404 unless @model - def permitted_create_params - params.require(:user_assignment) - .permit(%i(user_id user_role_id)) - end - - def available_users - # automatically assigned or not assigned to project - @available_users = current_team.users.where( - id: @protocol.user_assignments.automatically_assigned.select(:user_id) - ).or( - current_team.users.where.not(id: @protocol.users.select(:id)) - ).order('users.full_name ASC') - end - - def set_protocol - @protocol = current_team.protocols.includes(user_assignments: %i(user user_role)).find_by(id: params[:id]) - - return render_404 unless @protocol - - @protocol = @protocol.parent if @protocol.parent_id + @model = @model.parent if @model.parent_id end def check_manage_permissions - render_403 unless can_manage_protocol_users?(@protocol) + render_403 unless can_manage_protocol_users?(@model) end def check_read_permissions - render_403 unless can_read_protocol_in_repository?(@protocol) || can_manage_team?(@protocol.team) + render_403 unless can_read_protocol_in_repository?(@model) || can_manage_team?(@model.team) end def log_activity(type_of, message_items = {}) - message_items = { protocol: @protocol.id }.merge(message_items) - - Activities::CreateActivityService - .call(activity_type: type_of, - owner: current_user, - subject: @protocol, - team: @protocol.team, - project: nil, - message_items: message_items) + super(ACTIVITY_TYPE_MAP[type_of] || type_of, message_items) end end end diff --git a/app/controllers/access_permissions/repositories_controller.rb b/app/controllers/access_permissions/repositories_controller.rb new file mode 100644 index 000000000..7a1b6a136 --- /dev/null +++ b/app/controllers/access_permissions/repositories_controller.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module AccessPermissions + class RepositoriesController < BaseController + private + + def set_model + @model = Repository.includes(user_assignments: %i(user user_role)).find_by(id: params[:id]) + + render_404 unless @model + end + + def check_manage_permissions + render_403 unless can_manage_repository_users?(@model) + end + + def check_read_permissions + render_403 unless can_manage_repository_users?(@model) || can_read_repository?(@model) + end + end +end diff --git a/app/controllers/api/v1/experiment_user_assignments_controller.rb b/app/controllers/api/v1/experiment_user_assignments_controller.rb index 65b0e238b..6ef48346c 100644 --- a/app/controllers/api/v1/experiment_user_assignments_controller.rb +++ b/app/controllers/api/v1/experiment_user_assignments_controller.rb @@ -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 diff --git a/app/controllers/api/v1/inventories_controller.rb b/app/controllers/api/v1/inventories_controller.rb index 7c4089615..b148aa520 100644 --- a/app/controllers/api/v1/inventories_controller.rb +++ b/app/controllers/api/v1/inventories_controller.rb @@ -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)) diff --git a/app/controllers/api/v1/project_user_assignments_controller.rb b/app/controllers/api/v1/project_user_assignments_controller.rb index a51814181..ba3218cab 100644 --- a/app/controllers/api/v1/project_user_assignments_controller.rb +++ b/app/controllers/api/v1/project_user_assignments_controller.rb @@ -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 diff --git a/app/controllers/api/v1/projects_controller.rb b/app/controllers/api/v1/projects_controller.rb index 19ec39c40..fd253aeff 100644 --- a/app/controllers/api/v1/projects_controller.rb +++ b/app/controllers/api/v1/projects_controller.rb @@ -11,7 +11,13 @@ module Api before_action :load_project_for_managing, only: %i(update) def index - projects = @team.projects.visible_to(current_user, @team) + projects = + if can_manage_team?(@team) + # Team owners see all projects in the team + @team.projects + else + @team.projects.readable_by_user(current_user, @team) + end projects = metadata_filter(timestamps_filter(archived_filter(projects))) .page(params.dig(:page, :number)) .per(params.dig(:page, :size)) @@ -26,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 diff --git a/app/controllers/api/v1/protocol_templates_controller.rb b/app/controllers/api/v1/protocol_templates_controller.rb index 725ee147d..dc739e718 100644 --- a/app/controllers/api/v1/protocol_templates_controller.rb +++ b/app/controllers/api/v1/protocol_templates_controller.rb @@ -11,13 +11,11 @@ module Api end def index - protocol_templates = - timestamps_filter( - Protocol.latest_available_versions(@team) - ) - .viewable_by_user(current_user, @team) - .page(params.dig(:page, :number)) - .per(params.dig(:page, :size)) + protocol_templates = timestamps_filter(Protocol.latest_available_versions(@team)) + # Team owners see all protocol templates in the team + protocol_templates = protocol_templates.readable_by_user(current_user, @team) unless can_manage_team?(@team) + protocol_templates = protocol_templates.page(params.dig(:page, :number)) + .per(params.dig(:page, :size)) render jsonapi: protocol_templates, each_serializer: ProtocolTemplateSerializer, rte_rendering: render_rte?, team: @team diff --git a/app/controllers/api/v1/task_inventory_items_controller.rb b/app/controllers/api/v1/task_inventory_items_controller.rb index c93ced8e5..e43d74ff9 100644 --- a/app/controllers/api/v1/task_inventory_items_controller.rb +++ b/app/controllers/api/v1/task_inventory_items_controller.rb @@ -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 diff --git a/app/controllers/api/v2/inventory_item_child_relationships_controller.rb b/app/controllers/api/v2/inventory_item_child_relationships_controller.rb index 0576e2027..2cd2327fb 100644 --- a/app/controllers/api/v2/inventory_item_child_relationships_controller.rb +++ b/app/controllers/api/v2/inventory_item_child_relationships_controller.rb @@ -21,7 +21,7 @@ module Api end def create - inventory_item_to_link = RepositoryRow.where(repository: Repository.viewable_by_user(current_user, @team)) + inventory_item_to_link = RepositoryRow.where(repository: Repository.readable_by_user(current_user, @team)) .find(connection_params[:child_id]) child_connection = @inventory_item.child_connections.create!( child: inventory_item_to_link, diff --git a/app/controllers/api/v2/inventory_item_parent_relationships_controller.rb b/app/controllers/api/v2/inventory_item_parent_relationships_controller.rb index c7929eb67..2aa5fa0ff 100644 --- a/app/controllers/api/v2/inventory_item_parent_relationships_controller.rb +++ b/app/controllers/api/v2/inventory_item_parent_relationships_controller.rb @@ -21,7 +21,7 @@ module Api end def create - inventory_item_to_link = RepositoryRow.where(repository: Repository.viewable_by_user(current_user, @team)) + inventory_item_to_link = RepositoryRow.where(repository: Repository.readable_by_user(current_user, @team)) .find(connection_params[:parent_id]) parent_connection = @inventory_item.parent_connections.create!( parent: inventory_item_to_link, diff --git a/app/controllers/at_who_controller.rb b/app/controllers/at_who_controller.rb index f48645f81..ba1a5e4b7 100644 --- a/app/controllers/at_who_controller.rb +++ b/app/controllers/at_who_controller.rb @@ -27,7 +27,7 @@ class AtWhoController < ApplicationController if params[:repository_id].present? Repository.find_by(id: params[:repository_id]) else - Repository.active.viewable_by_user(current_user, @team).first + Repository.active.readable_by_user(current_user, @team).first end items = [] @@ -36,7 +36,7 @@ class AtWhoController < ApplicationController if repository && can_read_repository?(repository) assignable_my_module = if params[:assignable_my_module_id].present? - MyModule.viewable_by_user(current_user, @team).find_by(id: params[:assignable_my_module_id]) + MyModule.readable_by_user(current_user, @team).find_by(id: params[:assignable_my_module_id]) end items = SmartAnnotation.new(current_user, current_team, @query) .repository_rows(repository, assignable_my_module&.id) @@ -54,7 +54,7 @@ class AtWhoController < ApplicationController end def menu - repositories = Repository.active.viewable_by_user(current_user, @team) + repositories = Repository.active.readable_by_user(current_user, @team) render json: { html: render_to_string(partial: 'shared/smart_annotation/menu', locals: { repositories: repositories }, diff --git a/app/controllers/concerns/steps_actions.rb b/app/controllers/concerns/steps_actions.rb index 428ee05ce..4ec796ff9 100644 --- a/app/controllers/concerns/steps_actions.rb +++ b/app/controllers/concerns/steps_actions.rb @@ -48,7 +48,7 @@ module StepsActions smart_annotation_notification( old_text: old_text, new_text: checklist_item.text, - subject: step.protocol, + subject: step, title: t('notifications.checklist_title', user: current_user.full_name, step: step.name), @@ -60,7 +60,7 @@ module StepsActions smart_annotation_notification( old_text: old_text, new_text: step_text.text, - subject: step.protocol, + subject: step, title: t('notifications.step_text_title', user: current_user.full_name, step: step.name), @@ -72,7 +72,7 @@ module StepsActions smart_annotation_notification( old_text: old_text, new_text: checklist.name, - subject: step.protocol, + subject: step, title: t('notifications.checklist_title', user: current_user.full_name, step: step.name), @@ -84,7 +84,7 @@ module StepsActions smart_annotation_notification( old_text: old_text, new_text: step.description, - subject: step.protocol, + subject: step, title: t('notifications.step_description_title', user: current_user.full_name, step: step.name), @@ -96,7 +96,7 @@ module StepsActions smart_annotation_notification( old_text: old_content, new_text: table.contents, - subject: step.protocol, + subject: step, title: t(table.metadata['plateTemplate'] ? 'notifications.step_well_plate_title' : 'notifications.step_table_title', user: current_user.full_name, step: step.name), diff --git a/app/controllers/dashboard/current_tasks_controller.rb b/app/controllers/dashboard/current_tasks_controller.rb index f548a8a4d..a61a2c787 100644 --- a/app/controllers/dashboard/current_tasks_controller.rb +++ b/app/controllers/dashboard/current_tasks_controller.rb @@ -30,7 +30,7 @@ module Dashboard def project_filter projects = current_team.projects .where(archived: false) - .viewable_by_user(current_user, current_team) + .readable_by_user(current_user, current_team) .search_by_name(current_user, current_team, params[:query]).select(:id, :name) unless params[:mode] == 'team' @@ -47,7 +47,7 @@ module Dashboard end experiments = @project.experiments .where(archived: false) - .viewable_by_user(current_user, current_team) + .readable_by_user(current_user, current_team) .search_by_name(current_user, current_team, params[:query]).select(:id, :name) unless params[:mode] == 'team' @@ -103,7 +103,7 @@ module Dashboard MyModule.active end - tasks = tasks.viewable_by_user(current_user, current_team) + tasks = tasks.readable_by_user(current_user, current_team) tasks = tasks.joins(experiment: :project) .where(experiments: { archived: false }) diff --git a/app/controllers/dashboard/quick_start_controller.rb b/app/controllers/dashboard/quick_start_controller.rb index a16cc8f2e..2fa8f186c 100644 --- a/app/controllers/dashboard/quick_start_controller.rb +++ b/app/controllers/dashboard/quick_start_controller.rb @@ -60,7 +60,7 @@ module Dashboard end def create_project_params - params.require(:project).permit(:name, :visibility, :default_public_user_role_id) + params.require(:project).permit(:name) end def create_experiment_params diff --git a/app/controllers/experiments_controller.rb b/app/controllers/experiments_controller.rb index b55090028..67cc028e6 100644 --- a/app/controllers/experiments_controller.rb +++ b/app/controllers/experiments_controller.rb @@ -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 @@ -72,10 +72,13 @@ class ExperimentsController < ApplicationController def canvas @project = @experiment.project @active_modules = unless @experiment.archived_branch? - @experiment.my_modules.active.order(:name) + @experiment.my_modules + .active + .readable_by_user(current_user) .left_outer_joins(:designated_users, :task_comments) .preload(:tags, outputs: :to) .preload(:my_module_status, :my_module_group, user_assignments: %i(user user_role)) + .order(:name) .select('COUNT(DISTINCT users.id) as designated_users_count') .select('COUNT(DISTINCT comments.id) as task_comments_count') .select('my_modules.*').group(:id) @@ -202,7 +205,7 @@ class ExperimentsController < ApplicationController def projects_to_clone projects = @experiment.project.team.projects.active - .with_user_permission(current_user, ProjectPermissions::EXPERIMENTS_CREATE) + .with_granted_permissions(current_user, ProjectPermissions::EXPERIMENTS_CREATE) .where('trim_html_tags(projects.name) ILIKE ?', "%#{ActiveRecord::Base.sanitize_sql_like(params['query'])}%") .map { |p| [p.id, p.name] } @@ -355,10 +358,10 @@ class ExperimentsController < ApplicationController end def inventory_assigning_experiment_filter - viewable_experiments = Experiment.viewable_by_user(current_user, current_team) + viewable_experiments = Experiment.readable_by_user(current_user, current_team) assignable_my_modules = MyModule.repository_row_assignable_by_user(current_user) - project = Project.viewable_by_user(current_user, current_team) + project = Project.readable_by_user(current_user, current_team) .joins(experiments: :my_modules) .where(experiments: { id: viewable_experiments }) .where(my_modules: { id: assignable_my_modules }) diff --git a/app/controllers/external_protocols_controller.rb b/app/controllers/external_protocols_controller.rb index c38826e5f..8bfe34f37 100644 --- a/app/controllers/external_protocols_controller.rb +++ b/app/controllers/external_protocols_controller.rb @@ -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 diff --git a/app/controllers/form_field_values_controller.rb b/app/controllers/form_field_values_controller.rb index 4b4f577e7..6fb2b6305 100644 --- a/app/controllers/form_field_values_controller.rb +++ b/app/controllers/form_field_values_controller.rb @@ -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, diff --git a/app/controllers/forms_controller.rb b/app/controllers/forms_controller.rb index cbb218d67..a7e130d32 100644 --- a/app/controllers/forms_controller.rb +++ b/app/controllers/forms_controller.rb @@ -2,9 +2,9 @@ class FormsController < ApplicationController include InputSanitizeHelper - include UserRolesHelper before_action :check_forms_enabled + before_action :load_forms, only: %i(index actions_toolbar) before_action :load_form, only: %i(show update publish unpublish export_form_responses duplicate) before_action :set_breadcrumbs_items, only: %i(index show) before_action :check_manage_permissions, only: :update @@ -14,11 +14,11 @@ class FormsController < ApplicationController respond_to do |format| format.html format.json do - forms = Lists::FormsService.new(current_user, current_team, params).call - render json: forms, + forms_list = Lists::FormsService.new(@forms, params, user: current_user).call + render json: forms_list, each_serializer: Lists::FormSerializer, user: current_user, - meta: pagination_dict(forms) + meta: pagination_dict(forms_list) end end end @@ -201,19 +201,16 @@ class FormsController < ApplicationController end def actions_toolbar + selected_forms = @forms.where(id: JSON.parse(params[:items]).pluck('id')) render json: { actions: Toolbars::FormsService.new( - current_user, - form_ids: JSON.parse(params[:items]).map { |i| i['id'] } + selected_forms, + current_user ).actions } end - def user_roles - render json: { data: user_roles_collection(Form.new).map(&:reverse) } - end - private def set_breadcrumbs_items @@ -235,6 +232,12 @@ class FormsController < ApplicationController end end + def load_forms + # Team owners see all forms in the team + @forms = current_team.forms + @forms = @forms.readable_by_user(current_user) unless can_manage_team?(current_team) + end + def load_form @form = current_team.forms.readable_by_user(current_user).find_by(id: params[:id]) diff --git a/app/controllers/hidden_repository_cell_reminders_controller.rb b/app/controllers/hidden_repository_cell_reminders_controller.rb index ad7472d8c..5ca5c19fc 100644 --- a/app/controllers/hidden_repository_cell_reminders_controller.rb +++ b/app/controllers/hidden_repository_cell_reminders_controller.rb @@ -15,7 +15,7 @@ class HiddenRepositoryCellRemindersController < ApplicationController private def load_repository - @repository = Repository.viewable_by_user(current_user).find_by(id: params[:repository_id]) + @repository = Repository.readable_by_user(current_user).find_by(id: params[:repository_id]) render_404 unless @repository end diff --git a/app/controllers/label_templates_controller.rb b/app/controllers/label_templates_controller.rb index dcb312dac..710b71d42 100644 --- a/app/controllers/label_templates_controller.rb +++ b/app/controllers/label_templates_controller.rb @@ -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) diff --git a/app/controllers/my_module_repositories_controller.rb b/app/controllers/my_module_repositories_controller.rb index 547ea2fd0..700bfb01f 100644 --- a/app/controllers/my_module_repositories_controller.rb +++ b/app/controllers/my_module_repositories_controller.rb @@ -6,7 +6,7 @@ class MyModuleRepositoriesController < ApplicationController before_action :load_my_module, except: :assign_my_modules before_action :load_repository, except: %i(repositories_dropdown_list repositories_list_html repositories_list create) before_action :check_my_module_view_permissions, except: %i(update consume_modal update_consumption assign_my_modules) - before_action :check_repository_view_permissions, except: %i(repositories_dropdown_list repositories_list_html repositories_list create) + before_action :check_repository_view_permissions, except: %i(index_dt repositories_dropdown_list repositories_list_html repositories_list create) before_action :check_repository_row_consumption_permissions, only: %i(consume_modal update_consumption) before_action :check_assign_repository_records_permissions, only: %i(update create) before_action :load_my_modules, only: :assign_my_modules @@ -19,6 +19,8 @@ class MyModuleRepositoriesController < ApplicationController rows_view = 'repository_rows/simple_view_index' preload_cells = false else + return render_403 unless can_read_repository?(@repository) + rows_view = 'repository_rows/index' preload_cells = true end @@ -150,8 +152,8 @@ class MyModuleRepositoriesController < ApplicationController end def repositories_list - @assigned_repositories = @my_module.readable_live_and_snapshot_repositories_list(current_user) - render json: @assigned_repositories, each_serializer: AssignedRepositorySerializer, scope: {user: current_user, my_module: @my_module } + @assigned_repositories = @my_module.live_and_snapshot_repositories_list + render json: @assigned_repositories, each_serializer: AssignedRepositorySerializer, scope: { user: current_user, my_module: @my_module } end def full_view_table @@ -164,7 +166,7 @@ class MyModuleRepositoriesController < ApplicationController end def repositories_dropdown_list - @repositories = Repository.viewable_by_user(current_user).joins(" + @repositories = Repository.readable_by_user(current_user).joins(" LEFT OUTER JOIN repository_rows ON repository_rows.repository_id = repositories.id LEFT OUTER JOIN my_module_repository_rows ON diff --git a/app/controllers/my_module_repository_snapshots_controller.rb b/app/controllers/my_module_repository_snapshots_controller.rb index 7c45cd093..323a24164 100644 --- a/app/controllers/my_module_repository_snapshots_controller.rb +++ b/app/controllers/my_module_repository_snapshots_controller.rb @@ -67,7 +67,7 @@ class MyModuleRepositorySnapshotsController < ApplicationController end def full_view_sidebar - @repository = Repository.viewable_by_user(current_user, current_team).find_by(id: params[:repository_id]) + @repository = Repository.readable_by_user(current_user, current_team).find_by(id: params[:repository_id]) @repository_snapshots = @my_module.repository_snapshots .where(parent_id: params[:repository_id]) .order(created_at: :desc) diff --git a/app/controllers/my_module_shareable_links_controller.rb b/app/controllers/my_module_shareable_links_controller.rb index 625bce799..fe9055901 100644 --- a/app/controllers/my_module_shareable_links_controller.rb +++ b/app/controllers/my_module_shareable_links_controller.rb @@ -71,7 +71,8 @@ class MyModuleShareableLinksController < ApplicationController my_module: @my_module, include_stock_consumption: @repository.has_stock_management? && params[:assigned].present?, disable_reminders: true, # reminders are always disabled for shareable links - disable_stock_management: true # stock management is always disabled in MyModule context + disable_stock_management: true, # stock management is always disabled in MyModule context + shareable_link_view: true } @all_rows_count = datatable_service.all_count @@ -89,6 +90,7 @@ class MyModuleShareableLinksController < ApplicationController page = (params[:start].to_i / per_page) + 1 datatable_service = RepositorySnapshotDatatableService.new(@repository_snapshot, params, nil, @my_module, preload_cells: false) + @datatable_params = { shareable_link_view: true } @all_rows_count = datatable_service.all_count @filtered_rows_count = datatable_service.filtered_count @columns_mappings = datatable_service.mappings diff --git a/app/controllers/my_modules_controller.rb b/app/controllers/my_modules_controller.rb index 8398698f4..781e4f3d5 100644 --- a/app/controllers/my_modules_controller.rb +++ b/app/controllers/my_modules_controller.rb @@ -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( @@ -423,10 +423,10 @@ class MyModulesController < ApplicationController end def inventory_assigning_my_module_filter - viewable_experiments = Experiment.viewable_by_user(current_user, current_team) + viewable_experiments = Experiment.readable_by_user(current_user, current_team) assignable_my_modules = MyModule.repository_row_assignable_by_user(current_user) - experiment = Experiment.viewable_by_user(current_user, current_team) + experiment = Experiment.readable_by_user(current_user, current_team) .joins(:my_modules) .where(experiments: { id: viewable_experiments }) .where(my_modules: { id: assignable_my_modules }) diff --git a/app/controllers/navigations_controller.rb b/app/controllers/navigations_controller.rb index 964ca12e4..b11fa6f9d 100644 --- a/app/controllers/navigations_controller.rb +++ b/app/controllers/navigations_controller.rb @@ -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 diff --git a/app/controllers/navigator/base_controller.rb b/app/controllers/navigator/base_controller.rb index df82e27f2..828ed098f 100644 --- a/app/controllers/navigator/base_controller.rb +++ b/app/controllers/navigator/base_controller.rb @@ -53,7 +53,7 @@ module Navigator end def fetch_projects(object = nil, archived = false) - if object&.is_a?(ProjectFolder) + if object.is_a?(ProjectFolder) folder = object project = nil else @@ -70,37 +70,65 @@ module Navigator (projects.archived IS TRUE AND experiments.id IS NOT NULL) THEN 1 ELSE 0 END) > 0 AS has_children' end - disabled_sql = 'SUM(CASE WHEN project_user_roles IS NULL THEN 0 ELSE 1 END) < 1 AS disabled' + disabled_sql = 'SUM(CASE + WHEN project_user_roles IS NULL AND + project_user_group_roles IS NULL AND + project_team_roles IS NULL + THEN 0 ELSE 1 END) < 1 AS disabled' - current_team.projects - .where(project_folder_id: folder) - .visible_to(current_user, current_team) - .with_children_viewable_by_user(current_user) - .joins("LEFT OUTER JOIN user_assignments project_user_assignments - ON project_user_assignments.assignable_type = 'Project' - AND project_user_assignments.assignable_id = projects.id - AND project_user_assignments.user_id = #{current_user.id} - LEFT OUTER JOIN user_roles project_user_roles - ON project_user_roles.id = project_user_assignments.user_role_id - AND project_user_roles.permissions @> ARRAY['#{ProjectPermissions::READ}']::varchar[]") - .where('projects.archived = :archived OR - ( - ( - experiments.archived = :archived OR - my_modules.archived = :archived - ) AND - :archived IS TRUE - ) OR - projects.id = :project_id', - archived: archived, - project_id: project&.id || -1) - .select( - 'projects.id', - 'projects.name', - 'projects.archived', - disabled_sql, - has_children_sql - ).group('projects.id') + projects = + if can_manage_team?(current_team) + # Team owners see all projects in the team + current_team.projects + else + current_team.projects.readable_by_user(current_user, current_team) + end + + projects.where(project_folder_id: folder) + .with_children_viewable_by_user(current_user) + .joins("LEFT OUTER JOIN user_assignments project_user_assignments + ON project_user_assignments.assignable_type = 'Project' + AND project_user_assignments.assignable_id = projects.id + AND project_user_assignments.user_id = #{current_user.id} + LEFT OUTER JOIN user_roles project_user_roles + ON project_user_roles.id = project_user_assignments.user_role_id + AND project_user_roles.permissions @> ARRAY['#{ProjectPermissions::READ}']::varchar[] + LEFT OUTER JOIN user_group_assignments project_user_group_assignments + ON project_user_group_assignments.assignable_type = 'Project' + AND project_user_group_assignments.assignable_id = projects.id + AND project_user_group_assignments.user_group_id IN ( + SELECT user_group_memberships.user_group_id FROM user_group_memberships + WHERE user_group_memberships.user_id = #{current_user.id} + ) + LEFT OUTER JOIN user_roles project_user_group_roles + ON project_user_group_roles.id = project_user_group_assignments.user_role_id + AND project_user_group_roles.permissions @> ARRAY['#{ProjectPermissions::READ}']::varchar[] + LEFT OUTER JOIN team_assignments project_team_assignments + ON project_team_assignments.assignable_type = 'Project' + AND project_team_assignments.assignable_id = projects.id + AND project_team_assignments.team_id = #{current_team.id} + LEFT OUTER JOIN user_roles project_team_roles + ON project_team_roles.id = project_team_assignments.user_role_id + AND project_team_roles.permissions @> ARRAY['#{ProjectPermissions::READ}']::varchar[] + ") + .where('projects.archived = :archived OR + ( + ( + experiments.archived = :archived OR + my_modules.archived = :archived + ) AND + :archived IS TRUE + ) OR + projects.id = :project_id', + archived: archived, + project_id: project&.id || -1) + .select( + 'projects.id', + 'projects.name', + 'projects.archived', + disabled_sql, + has_children_sql + ).group('projects.id') end def fetch_project_folders(object = nil, archived = false) @@ -109,13 +137,20 @@ module Navigator else object&.project_folder end - has_children_sql = 'SUM(CASE WHEN viewable_projects.id IS NOT NULL OR project_folders_project_folders.id IS NOT NULL + has_children_sql = 'SUM(CASE WHEN visible_projects.id IS NOT NULL OR project_folders_project_folders.id IS NOT NULL THEN 1 ELSE 0 END) > 0' + visible_projects = + if can_manage_team?(current_team) + # Team owners see all projects in the team + current_team.projects + else + current_team.projects.readable_by_user(current_user, current_team) + end current_team.project_folders.where(parent_folder: folder) .left_outer_joins(:projects, project_folders: {}) .joins( - "LEFT OUTER JOIN (#{Project.viewable_by_user(current_user, current_team).where(archived: archived).to_sql}) " \ - "viewable_projects ON viewable_projects.project_folder_id = project_folders.id" + "LEFT OUTER JOIN (#{visible_projects.where(archived: archived).to_sql}) " \ + "visible_projects ON visible_projects.project_folder_id = project_folders.id" ) .select( 'project_folders.id', @@ -147,7 +182,7 @@ module Navigator THEN 1 ELSE 0 END) > 0 AS has_children' end experiments = project.experiments - .viewable_by_user(current_user, current_team) + .readable_by_user(current_user, current_team) .with_children_viewable_by_user(current_user) .select( 'experiments.id', @@ -169,8 +204,7 @@ module Navigator end def fetch_my_modules(experiment, archived = false) - my_modules = experiment.my_modules - .viewable_by_user(current_user, current_team) + my_modules = experiment.my_modules.readable_by_user(current_user, current_team) my_modules = my_modules.where(archived: archived) unless experiment.archived_branch? my_modules diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index e622fd917..a864845a2 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -8,7 +8,6 @@ class ProjectsController < ApplicationController include CardsViewHelper include ExperimentsHelper include Breadcrumbs - include UserRolesHelper include FavoritesActions attr_reader :current_folder @@ -16,11 +15,12 @@ class ProjectsController < ApplicationController helper_method :current_folder before_action :switch_team_with_param, only: :index - before_action :load_vars, only: %i(update create_tag assigned_users_list show) + before_action :load_projects, only: %i(index actions_toolbar) + before_action :load_project, only: %i(update create_tag assigned_users_list show) before_action :load_current_folder, only: :index before_action :check_read_permissions, except: %i(index create update archive_group restore_group inventory_assigning_project_filter - actions_toolbar user_roles users_filter head_of_project_users_list + actions_toolbar users_filter head_of_project_users_list favorite unfavorite) before_action :check_create_permissions, only: :create before_action :check_manage_permissions, only: :update @@ -32,9 +32,9 @@ class ProjectsController < ApplicationController def index respond_to do |format| format.json do - projects = Lists::ProjectsService.new(current_team, current_user, current_folder, params).call - render json: projects, each_serializer: Lists::ProjectAndFolderSerializer, user: current_user, - meta: pagination_dict(projects) + projects_list = Lists::ProjectsService.new(current_team, @projects, @current_folder, params, user: current_user).call + render json: projects_list, each_serializer: Lists::ProjectAndFolderSerializer, user: current_user, + meta: pagination_dict(projects_list) end format.html do render 'projects/index' @@ -54,10 +54,10 @@ class ProjectsController < ApplicationController end def inventory_assigning_project_filter - viewable_experiments = Experiment.viewable_by_user(current_user, current_team) + viewable_experiments = Experiment.readable_by_user(current_user, current_team) assignable_my_modules = MyModule.repository_row_assignable_by_user(current_user) - projects = Project.viewable_by_user(current_user, current_team) + projects = Project.readable_by_user(current_user, current_team) .active .joins(experiments: :my_modules) .where(experiments: { id: viewable_experiments }) @@ -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 @@ -300,16 +264,17 @@ class ProjectsController < ApplicationController render json: { data: users.map { |u| [u.id, u.name, { avatar_url: avatar_path(u, :icon_small) }] } }, status: :ok end - def user_roles - render json: { data: user_roles_collection(Project.new).map(&:reverse) } - end - def actions_toolbar + project_ids = JSON.parse(params[:items]).select { |i| i['type'] == 'projects' }.pluck('id') + project_folder_ids = JSON.parse(params[:items]).select { |i| i['type'] == 'project_folders' }.pluck('id') + selected_projects = @projects.where(id: project_ids) + selected_project_folders = current_user.current_team.project_folders.where(id: project_folder_ids) render json: { actions: Toolbars::ProjectsService.new( - current_user, - items: JSON.parse(params[:items]) + selected_projects, + selected_project_folders, + current_user ).actions } end @@ -319,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 @@ -330,14 +294,23 @@ 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 params.require(:project).require(:view_type) end - def load_vars + def load_projects + @projects = if can_manage_team?(current_team) + # Team owners see all projects in the team + current_team.projects + else + current_team.projects.readable_by_user(current_user, current_team) + end + end + + def load_project @project = Project.find_by(id: params[:id] || params[:project_id]) render_404 unless @project diff --git a/app/controllers/protocols_controller.rb b/app/controllers/protocols_controller.rb index 4731f91db..a96270be6 100644 --- a/app/controllers/protocols_controller.rb +++ b/app/controllers/protocols_controller.rb @@ -7,7 +7,7 @@ class ProtocolsController < ApplicationController include ProtocolsIoHelper include TeamsHelper include ProtocolsExporterV2 - include UserRolesHelper + include FormFieldValuesHelper before_action :check_create_permissions, only: %i( create @@ -79,11 +79,17 @@ class ProtocolsController < ApplicationController def index respond_to do |format| format.json do - protocols = Lists::ProtocolsService.new(Protocol.viewable_by_user(current_user, @current_team), params).call - render json: protocols, + protocols = if can_manage_team?(current_team) + # Team owners see all protocol templates in the team + current_team.repository_protocols + else + current_team.repository_protocols.readable_by_user(current_user, current_team) + end + protocols_list = Lists::ProtocolsService.new(protocols, params).call + render json: protocols_list, each_serializer: Lists::ProtocolSerializer, user: current_user, - meta: pagination_dict(protocols) + meta: pagination_dict(protocols_list) end format.html do render 'index' @@ -531,6 +537,7 @@ class ProtocolsController < ApplicationController Protocol.transaction do protocol = @importer.import_new_protocol(@protocol_json) rescue StandardError => e + Rails.logger.error e.message Rails.logger.error e.backtrace.join("\n") transaction_error = true raise ActiveRecord::Rollback @@ -876,10 +883,6 @@ class ProtocolsController < ApplicationController render json: { job_id: @job.job_id } end - def user_roles - render json: { data: user_roles_collection(Protocol.new).map(&:reverse) } - end - private def set_importer @@ -1089,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 diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index fab290fd7..d2d95001e 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -26,7 +26,7 @@ class ReportsController < ApplicationController def index respond_to do |format| format.json do - reports = Lists::ReportsService.new(Report.viewable_by_user(current_user, current_team), params).call + reports = Lists::ReportsService.new(Report.readable_by_user(current_user, current_team), params).call render json: reports, each_serializer: Lists::ReportSerializer, user: current_user, meta: pagination_dict(reports) end @@ -370,10 +370,10 @@ class ReportsController < ApplicationController end def load_repositories_vars - live_repositories = Repository.viewable_by_user(current_user).sort_by { |r| r.name.downcase } + live_repositories = Repository.readable_by_user(current_user).sort_by { |r| r.name.downcase } snapshots_of_deleted = RepositorySnapshot.left_outer_joins(:original_repository) .where(team: current_team) - .where.not(original_repository: live_repositories) + .where.not(original_repository: current_team.repositories) .select('DISTINCT ON ("repositories"."parent_id") "repositories".*') .sort_by { |r| r.name.downcase } @repositories = live_repositories + snapshots_of_deleted @@ -398,7 +398,7 @@ class ReportsController < ApplicationController def load_available_repositories @available_repositories = [] repositories = Repository.active - .viewable_by_user(current_user) + .readable_by_user(current_user) .name_like(search_params[:query]) .limit(Constants::SEARCH_LIMIT) repositories.each do |repository| diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 4baf20faa..34c01b9c7 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -10,14 +10,13 @@ class RepositoriesController < ApplicationController include MyModulesHelper before_action :switch_team_with_param, only: %i(index) - before_action :load_repository, except: %i(index create create_modal sidebar archive restore actions_toolbar + 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) + 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_all_permissions, only: %i(index sidebar list) before_action :check_view_permissions, except: %i(index create_modal create update destroy parse_sheet - import_records sidebar archive restore actions_toolbar + import_records archive restore actions_toolbar export_repositories list) before_action :check_manage_permissions, only: %i(rename_modal update) before_action :check_delete_permissions, only: %i(destroy destroy_modal) @@ -45,7 +44,12 @@ class RepositoriesController < ApplicationController end def list - results = @repositories.select(:id, :name, 'LOWER(repositories.name)') + repositories = if 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? @@ -74,15 +78,6 @@ class RepositoriesController < ApplicationController } end - def sidebar - render json: { - html: render_to_string(partial: 'repositories/sidebar', locals: { - repositories: @repositories, - archived: params[:archived] == 'true' - }) - } - end - def show respond_to do |format| format.html do @@ -332,14 +327,14 @@ class RepositoriesController < ApplicationController end def import_records - render_403 unless can_create_repository_rows?(Repository.viewable_by_user(current_user) + render_403 unless can_create_repository_rows?(Repository.readable_by_user(current_user) .find_by(id: import_params[:id])) # Check if there exist mapping for repository record (it's mandatory) if import_params[:mappings].present? && import_params[:mappings].value?('-1') status = ImportRepository::ImportRecords .new( temp_file: TempFile.find_by(id: import_params[:file_id]), - repository: Repository.viewable_by_user(current_user).find_by(id: import_params[:id]), + repository: Repository.readable_by_user(current_user).find_by(id: import_params[:id]), mappings: import_params[:mappings], session: session, user: current_user, @@ -404,7 +399,7 @@ class RepositoriesController < ApplicationController end def export_repositories - repositories = Repository.viewable_by_user(current_user, current_team).where(id: params[:repository_ids]) + repositories = Repository.readable_by_user(current_user, current_team).where(id: params[:repository_ids]) if repositories.present? RepositoriesExportJob .perform_later(params[:file_type], repositories.pluck(:id), user_id: current_user.id, team_id: current_team.id) @@ -472,12 +467,12 @@ class RepositoriesController < ApplicationController end def actions_toolbar + selected_repositories = @repositories.where(id: JSON.parse(params[:items]).pluck('id')) render json: { actions: Toolbars::RepositoriesService.new( - current_user, - current_team, - repository_ids: JSON.parse(params[:items]).map { |i| i['id'] } + selected_repositories, + current_user ).actions } end @@ -486,16 +481,20 @@ class RepositoriesController < ApplicationController def load_repository repository_id = params[:id] || params[:repository_id] - @repository = Repository.viewable_by_user(current_user, current_user.teams).find_by(id: repository_id) + @repository = Repository.readable_by_user(current_user, current_user.teams).find_by(id: repository_id) render_404 unless @repository end def load_repositories - @repositories = if params[:appendable] == 'true' - Repository.appendable_by_user(current_user) - else - Repository.viewable_by_user(current_user) - end + @repositories = + if params[:appendable] == 'true' + Repository.appendable_by_user(current_user) + elsif can_manage_team?(current_team) + # Team owners see all repositories in the team + current_team.repositories.or(Repository.shared_with_team(current_team)) + else + Repository.readable_by_user(current_user, current_team) + end end def load_repositories_for_archiving @@ -530,10 +529,6 @@ class RepositoriesController < ApplicationController } end - def check_view_all_permissions - render_403 unless can_read_team?(current_team) - end - def check_view_permissions current_team_switch(@repository.team) unless @repository.shared_with?(current_team) render_403 unless can_read_repository?(@repository) diff --git a/app/controllers/repository_columns_controller.rb b/app/controllers/repository_columns_controller.rb index 73cefa87b..c9fa98253 100644 --- a/app/controllers/repository_columns_controller.rb +++ b/app/controllers/repository_columns_controller.rb @@ -108,7 +108,7 @@ class RepositoryColumnsController < ApplicationController AvailableRepositoryColumn = Struct.new(:id, :name) def load_repository - @repository = Repository.viewable_by_user(current_user).find_by(id: params[:repository_id]) + @repository = Repository.readable_by_user(current_user).find_by(id: params[:repository_id]) render_404 unless @repository end diff --git a/app/controllers/repository_row_connections_controller.rb b/app/controllers/repository_row_connections_controller.rb index 4398959bd..3e6955ae7 100644 --- a/app/controllers/repository_row_connections_controller.rb +++ b/app/controllers/repository_row_connections_controller.rb @@ -56,7 +56,7 @@ class RepositoryRowConnectionsController < ApplicationController end def repositories - repositories = Repository.viewable_by_user(current_user) + repositories = Repository.readable_by_user(current_user) .search_by_name_and_id(current_user, current_user.teams, params[:query]) .order(name: :asc) .page(params[:page] || 1) @@ -70,7 +70,7 @@ class RepositoryRowConnectionsController < ApplicationController end def repository_rows - selected_repository = Repository.viewable_by_user(current_user).find(params[:selected_repository_id]) + selected_repository = Repository.readable_by_user(current_user).find(params[:selected_repository_id]) repository_rows = selected_repository.repository_rows .where.not(id: @repository_row.id) @@ -95,14 +95,14 @@ class RepositoryRowConnectionsController < ApplicationController return render_422(t('.invalid_params')) unless @relation_type - @connection_repository = Repository.viewable_by_user(current_user) + @connection_repository = Repository.readable_by_user(current_user) .find_by(id: connection_params[:connection_repository_id]) return render_404 unless @connection_repository return render_403 unless can_connect_repository_rows?(@connection_repository) end def load_repository - @repository = Repository.viewable_by_user(current_user).find_by(id: params[:repository_id]) + @repository = Repository.readable_by_user(current_user).find_by(id: params[:repository_id]) render_404 unless @repository end @@ -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 diff --git a/app/controllers/repository_rows_controller.rb b/app/controllers/repository_rows_controller.rb index 02ca7d6de..3b5f0866d 100644 --- a/app/controllers/repository_rows_controller.rb +++ b/app/controllers/repository_rows_controller.rb @@ -58,13 +58,11 @@ class RepositoryRowsController < ApplicationController end end - @assigned_modules = @repository_row.my_modules - .joins(experiment: :project) - .joins(:my_module_repository_rows) - .select('my_module_repository_rows.created_at, my_modules.*') - .order('my_module_repository_rows.created_at': :desc) - .distinct - @viewable_modules = @assigned_modules.viewable_by_user(current_user, current_user.teams) + @assigned_modules = @repository_row.my_modules.distinct + @viewable_modules = @assigned_modules.readable_by_user(current_user, current_user.teams) + .joins(:my_module_repository_rows) + .select('my_module_repository_rows.created_at, my_modules.*') + .order(my_module_repository_rows: { created_at: :desc }) @reminders_present = @repository_row.repository_cells.with_active_reminder(@current_user).any? end end @@ -83,7 +81,7 @@ class RepositoryRowsController < ApplicationController end if update_params[:my_module_id].present? - my_module = MyModule.viewable_by_user(current_user, current_team).find_by(id: update_params[:my_module_id]) + my_module = MyModule.readable_by_user(current_user, current_team).find_by(id: update_params[:my_module_id]) return render_403 unless my_module.present? && can_read_my_module?(my_module) @@ -215,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? @@ -299,7 +299,7 @@ class RepositoryRowsController < ApplicationController params[:query], whole_phrase: true ) - viewable_modules = assigned_modules.viewable_by_user(current_user, current_user.teams) + viewable_modules = assigned_modules.readable_by_user(current_user, current_user.teams) private_modules_number = assigned_modules.where.not(id: viewable_modules).count render json: { html: render_to_string(partial: 'shared/my_modules_list_partial', locals: { @@ -372,7 +372,7 @@ class RepositoryRowsController < ApplicationController AvailableRepositoryRow = Struct.new(:id, :name, :has_file_attached) def load_repository - @repository = Repository.viewable_by_user(current_user) + @repository = Repository.readable_by_user(current_user) .eager_load(:repository_columns) .find_by(id: params[:repository_id]) render_404 unless @repository @@ -384,7 +384,7 @@ class RepositoryRowsController < ApplicationController FormRepositoryRowsFieldValue.find_by(id: params[:form_repository_rows_field_value_id]) end - @repository = Repository.viewable_by_user(current_user).find_by(id: params[:repository_id]) || + @repository = Repository.readable_by_user(current_user).find_by(id: params[:repository_id]) || RepositorySnapshot.find_by(id: params[:repository_id]) render_404 unless @form_repository_rows_field_value || @repository @@ -476,7 +476,7 @@ class RepositoryRowsController < ApplicationController smart_annotation_notification( old_text: old_text, new_text: cell.value.data, - subject: cell.repository_column.repository, + subject: cell.repository_row, title: t('notifications.repository_annotation_title', user: current_user.full_name, column: cell.repository_column.name, diff --git a/app/controllers/repository_table_filters_controller.rb b/app/controllers/repository_table_filters_controller.rb index a5009529a..7e231a2ee 100644 --- a/app/controllers/repository_table_filters_controller.rb +++ b/app/controllers/repository_table_filters_controller.rb @@ -70,7 +70,7 @@ class RepositoryTableFiltersController < ApplicationController private def load_repository - @repository = Repository.viewable_by_user(current_user).find_by(id: params[:repository_id]) + @repository = Repository.readable_by_user(current_user).find_by(id: params[:repository_id]) render_403 unless can_read_repository?(@repository) end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 5623e71cc..e2d3006e4 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -171,8 +171,7 @@ class SearchController < ApplicationController current_team, params[:query], search_object_classes, - limit: Constants::QUICK_SEARCH_LIMIT, - fetch_latest_versions: class_name == 'protocol') + limit: Constants::QUICK_SEARCH_LIMIT) .order(updated_at: :desc) end diff --git a/app/controllers/smart_annotations_controller.rb b/app/controllers/smart_annotations_controller.rb index 239cbd1b7..85b9fe9a8 100644 --- a/app/controllers/smart_annotations_controller.rb +++ b/app/controllers/smart_annotations_controller.rb @@ -84,7 +84,7 @@ class SmartAnnotationsController < ApplicationController when MyModule protocols_my_module_path(resource) when RepositoryRow - repository_repository_row_path(resource.repository, resource) + repository_repository_row_path(resource.repository, resource, my_module_id: params[:my_module_id]) end end diff --git a/app/controllers/storage_locations_controller.rb b/app/controllers/storage_locations_controller.rb index 088fb1a7f..ef819e974 100644 --- a/app/controllers/storage_locations_controller.rb +++ b/app/controllers/storage_locations_controller.rb @@ -8,6 +8,7 @@ class StorageLocationsController < ApplicationController before_action :switch_team_with_param, only: %i(index show) before_action :check_storage_locations_enabled, except: :unassign_rows + before_action :load_storage_locations, only: %i(index actions_toolbar) before_action :load_storage_location, only: %i(update destroy duplicate move show available_positions unassign_rows export_container import_container) before_action :set_inline_name_editing, only: %i(show) before_action :check_read_permissions, except: %i(index create tree actions_toolbar import_container unassign_rows) @@ -26,7 +27,7 @@ class StorageLocationsController < ApplicationController respond_to do |format| format.html format.json do - storage_locations = Lists::StorageLocationsService.new(current_user, current_team, params).call + storage_locations = Lists::StorageLocationsService.new(@storage_locations, params, user: current_user).call render json: storage_locations, each_serializer: Lists::StorageLocationSerializer, user: current_user, @@ -131,7 +132,7 @@ class StorageLocationsController < ApplicationController end def tree - records = StorageLocation.viewable_by_user(current_user, current_team) + records = StorageLocation.readable_by_user(current_user, current_team) .where( parent: nil, container: [false, params[:container] == 'true'] @@ -188,11 +189,12 @@ class StorageLocationsController < ApplicationController end def actions_toolbar + selected_storage_locations = @storage_locations.where(id: JSON.parse(params[:items]).pluck('id')) render json: { actions: Toolbars::StorageLocationsService.new( - current_user, - storage_location_ids: JSON.parse(params[:items]).pluck('id') + selected_storage_locations, + current_user ).actions } end @@ -212,6 +214,10 @@ class StorageLocationsController < ApplicationController params.permit(:id, :destination_storage_location_id) end + def load_storage_locations + @storage_locations = StorageLocation.readable_by_user(current_user, current_team).or(StorageLocation.shared_with_team(current_team)) + end + def load_storage_location @storage_location = StorageLocation.find(storage_location_params[:id]) @parent_location = @storage_location.parent diff --git a/app/controllers/team_shared_objects_controller.rb b/app/controllers/team_shared_objects_controller.rb index 3276b5721..1132bb993 100644 --- a/app/controllers/team_shared_objects_controller.rb +++ b/app/controllers/team_shared_objects_controller.rb @@ -22,6 +22,14 @@ class TeamSharedObjectsController < ApplicationController if @model.permission_level_changed? @model.save! @model.team_shared_objects.each(&:destroy!) unless global_permission_level == :not_shared + + case global_permission_level + when :shared_read + @model.demote_all_sharing_assignments_to_viewer! + when :not_shared + @model.destroy_all_sharing_assignments! + end + case @model when Repository setup_repository_global_share_activity @@ -66,9 +74,9 @@ class TeamSharedObjectsController < ApplicationController def load_vars case params[:object_type] when 'Repository' - @model = Repository.viewable_by_user(current_user).find_by(id: params[:object_id]) + @model = Repository.readable_by_user(current_user).find_by(id: params[:object_id]) when 'StorageLocation' - @model = StorageLocation.viewable_by_user(current_user).find_by(id: params[:object_id]) + @model = StorageLocation.readable_by_user(current_user).find_by(id: params[:object_id]) end render_404 unless @model @@ -88,7 +96,7 @@ class TeamSharedObjectsController < ApplicationController def check_sharing_permissions object_name = @model.is_a?(RepositoryBase) ? 'repository' : @model.model_name.param_key - render_403 unless public_send("can_share_#{object_name}?", @model) + render_403 unless public_send(:"can_share_#{object_name}?", @model) render_403 if !@model.shareable_write? && update_params[:write_permissions].present? end diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb index 61707f01b..1b67ceeb1 100644 --- a/app/controllers/teams_controller.rb +++ b/app/controllers/teams_controller.rb @@ -9,7 +9,7 @@ class TeamsController < ApplicationController before_action :load_vars, only: %i(sidebar export_projects export_projects_modal disable_tasks_sharing_modal shared_tasks_toggle) before_action :load_current_folder, only: :sidebar - before_action :check_read_permissions, except: %i(view_type visible_teams visible_users) + before_action :check_read_permissions, except: %i(view_type visible_teams visible_users current_team_users) before_action :check_export_projects_permissions, only: %i(export_projects_modal export_projects) def visible_teams @@ -22,7 +22,12 @@ class TeamsController < ApplicationController if params[:teams].present? teams = teams.where(id: params[:teams]) end - users = User.where(id: teams.joins(:users).select('users.id')).order(:full_name) + users = User.where(id: UserAssignment.where(assignable: teams).select(:user_id)).order(:full_name) + render json: users, each_serializer: UserSerializer, user: current_user + end + + def current_team_users + users = current_team.users.order(:full_name) render json: users, each_serializer: UserSerializer, user: current_user end @@ -154,7 +159,7 @@ class TeamsController < ApplicationController if export_projects_params[:project_folder_ids] folders = @team.project_folders.where(id: export_projects_params[:project_folder_ids]) folders.each do |folder| - @exp_projects += folder.inner_projects.visible_to(current_user, @team) + @exp_projects += folder.inner_projects.readable_by_user(current_user, @team) end end diff --git a/app/controllers/users/passwords_controller.rb b/app/controllers/users/passwords_controller.rb index f10e61f31..99c38f15e 100644 --- a/app/controllers/users/passwords_controller.rb +++ b/app/controllers/users/passwords_controller.rb @@ -35,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 diff --git a/app/controllers/users/settings/teams_controller.rb b/app/controllers/users/settings/teams_controller.rb index 6208fc2f8..dce1104b2 100644 --- a/app/controllers/users/settings/teams_controller.rb +++ b/app/controllers/users/settings/teams_controller.rb @@ -15,6 +15,7 @@ module Users create show users_datatable + members ) before_action :load_team, only: %i( @@ -24,12 +25,13 @@ module Users description_html update destroy + members ) before_action :check_create_team_permission, only: %i(new create) - before_action :set_breadcrumbs_items, only: %i(index show) + before_action :set_breadcrumbs_items, only: %i(index show members) layout 'fluid' @@ -57,7 +59,13 @@ module Users end end - def show; end + def show + @active_tab = :details + end + + def members + @active_tab = :members + end def users_datatable render json: ::TeamUsersDatatable.new(view_context, @team, @user) diff --git a/app/controllers/users/settings/user_group_memberships_controller.rb b/app/controllers/users/settings/user_group_memberships_controller.rb new file mode 100644 index 000000000..a64379dde --- /dev/null +++ b/app/controllers/users/settings/user_group_memberships_controller.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +module Users + module Settings + class UserGroupMembershipsController < ApplicationController + before_action :check_user_groups_enabled + before_action :load_team + before_action :load_user_group + before_action :check_manage_permissions, except: %i(index show) + + def index + memberships = Lists::UserGroupMembershipsService.new(@user_group.user_group_memberships, params).call + render json: memberships, each_serializer: Lists::UserGroupMembershipSerializer, user: current_user, meta: pagination_dict(memberships) + end + + def actions_toolbar + render json: { + actions: + Toolbars::UserGroupMembershipsService.new( + current_user, + @user_group, + user_group_membership_ids: JSON.parse(params[:items]).pluck('id') + ).actions + } + end + + def show; end + + def create + ActiveRecord::Base.transaction do + new_users = @team.users.where(id: params[:user_ids]) + + new_users.each do |user| + @user_group.user_group_memberships.create!(user: user, created_by: current_user) + log_activity(:add_group_user_member, user) + end + + render json: { message: :success }, status: :created + rescue ActiveRecord::RecordInvalid => e + Rails.logger.error e.message + head :unprocessable_entity + raise ActiveRecord::Rollback + end + end + + def destroy_multiple + members = @user_group.user_group_memberships.where(id: params[:membership_ids]) + + members.each do |member| + log_activity(:remove_group_user_member, member.user) + end + + if members.destroy_all + render json: { message: :success }, status: :ok + else + head :unprocessable_entity + end + end + + private + + def check_user_groups_enabled + render '/users/settings/user_groups/promo' unless UserGroup.enabled? + end + + def load_team + @team = Team.find(params[:team_id]) + end + + def load_user_group + @user_group = @team.user_groups.find(params[:user_group_id]) + end + + def load_user_group_membership + @user_group_membership = @user_group.user_group_memberships.find(params[:id]) + end + + def check_manage_permissions + render_403 unless can_manage_team?(@team) + end + + def log_activity(type_of, user_target) + Activities::CreateActivityService + .call(activity_type: type_of, + owner: current_user, + subject: @user_group.team, + team: @user_group.team, + message_items: { + user_group: @user_group.id, + team: @user_group.team.id, + user_target: user_target.id + }) + end + end + end +end diff --git a/app/controllers/users/settings/user_groups_controller.rb b/app/controllers/users/settings/user_groups_controller.rb new file mode 100644 index 000000000..12e8530f7 --- /dev/null +++ b/app/controllers/users/settings/user_groups_controller.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +module Users + module Settings + class UserGroupsController < ApplicationController + before_action :load_team + before_action :set_breadcrumbs_items, only: %i(index show) + 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(users) + before_action :check_manage_permissions, except: %i(users) + + def index + respond_to do |format| + format.html do + @active_tab = :user_groups + end + format.json do + user_groups = Lists::UserGroupsService.new(@team.user_groups, params).call + render json: user_groups, each_serializer: Lists::UserGroupSerializer, user: current_user, meta: pagination_dict(user_groups) + end + end + end + + def actions_toolbar + render json: { + actions: + Toolbars::UserGroupsService.new( + current_user, + @team, + user_group_ids: JSON.parse(params[:items]).pluck('id') + ).actions + } + end + + def unassigned_users + @unassigned_users = @team.users.search(false, params[:query]) + if params[:user_group_id].present? + @user_group = @team.user_groups.find(params[:user_group_id]) + @unassigned_users = @unassigned_users.where.not(id: @user_group.users.select(:id)) + end + end + + def show + @active_tab = :user_groups + end + + def create + @user_group = @team.user_groups.new + @user_group.created_by = current_user + @user_group.last_modified_by = current_user + @user_group.assign_attributes(user_group_params) + + if @user_group.save + log_activity(:create_user_group) + @user_group.users.each do |user| + log_activity(:add_group_user_member, { user_target: user.id }) + end + render json: { message: t('user_groups.create.success') }, status: :created + else + render json: { error: @user_group.errors.full_messages.join(", ") }, status: :unprocessable_entity + end + end + + def update + ActiveRecord::Base.transaction do + @user_group.last_modified_by = current_user + @user_group.assign_attributes(user_group_params) + @user_group.save! + log_activity(:update_user_group) + render json: {}, status: :ok + rescue ActiveRecord::RecordInvalid => e + render json: { errors: e.message }, status: :unprocessable_entity + raise ActiveRecord::Rollback + end + end + + def destroy + log_activity(:delete_user_group) + if @user_group.destroy + render json: { message: t('user_groups.delete.success') }, status: :ok + else + render json: { errors: t('user_groups.delete.error') }, status: :unprocessable_entity + end + end + + def users + render json: @user_group.users, each_serializer: UserSerializer, user: current_user + end + + private + + def check_user_groups_enabled + @active_tab = :user_groups + render '/users/settings/user_groups/promo' unless UserGroup.enabled? + end + + def user_group_params + params.require(:user_group).permit( + :name, + user_group_memberships_attributes: %i(id user_id) + ) + end + + def load_team + @team = Team.find(params[:team_id]) + end + + def load_user_group + @user_group = @team.user_groups.find(params[:user_group_id] || params[:id]) + end + + def check_read_permissions + render_403 unless can_read_team?(@team) + end + + def check_manage_permissions + render_403 unless can_manage_team?(@team) + end + + def log_activity(type_of, message_items = {}) + Activities::CreateActivityService + .call(activity_type: type_of, + owner: current_user, + subject: @user_group.team, + team: @user_group.team, + message_items: { + user_group: @user_group.id, + team: @user_group.team.id + }.merge(message_items)) + end + + def set_breadcrumbs_items + @breadcrumbs_items = [ + { label: t('breadcrumbs.teams'), url: teams_path }, + { label: @team.name, url: team_path(@team) } + ] + end + end + end +end diff --git a/app/controllers/users/settings/user_teams_controller.rb b/app/controllers/users/settings/user_teams_controller.rb index ebf1ccbe4..f316ee7a6 100644 --- a/app/controllers/users/settings/user_teams_controller.rb +++ b/app/controllers/users/settings/user_teams_controller.rb @@ -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 diff --git a/app/datatables/teams_datatable.rb b/app/datatables/teams_datatable.rb index 4bca0126e..7601d4758 100644 --- a/app/datatables/teams_datatable.rb +++ b/app/datatables/teams_datatable.rb @@ -2,7 +2,7 @@ class TeamsDatatable < CustomDatatable include InputSanitizeHelper def_delegator :@view, :link_to - def_delegator :@view, :team_path + def_delegator :@view, :members_users_settings_team_path def_delegator :@view, :leave_user_team_html_path MEMEBERS_SORT_COL = 'members'.freeze @@ -28,7 +28,7 @@ class TeamsDatatable < CustomDatatable { 'DT_RowId': record.id, '0': if current_user_team_role(record)&.owner? - link_to(escape_input(record.name), team_path(record)) + link_to(escape_input(record.name), members_users_settings_team_path(record)) else escape_input(record.name) end, diff --git a/app/helpers/comment_helper.rb b/app/helpers/comment_helper.rb index 81aa82646..ba6dc2566 100644 --- a/app/helpers/comment_helper.rb +++ b/app/helpers/comment_helper.rb @@ -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), diff --git a/app/helpers/form_field_values_helper.rb b/app/helpers/form_field_values_helper.rb new file mode 100644 index 000000000..545c320b3 --- /dev/null +++ b/app/helpers/form_field_values_helper.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module FormFieldValuesHelper + include Canaid::Helpers::PermissionsHelper + + def form_repository_rows_field_value_formatter(field_values, user = current_user) + field_values&.value&.map do |value| + row_code = "#{RepositoryRow::ID_PREFIX}#{value['id']}" + repository = Repository.find_by(id: value['repository_id']) + + if repository.nil? || can_read_repository?(user, repository) + "#{value['name']} (#{row_code})" + else + I18n.t('my_modules.assigned_items.repository.private_repository_row_name', repository_row_code: row_code) + end + end&.join(' | ') + end +end diff --git a/app/helpers/global_activities_helper.rb b/app/helpers/global_activities_helper.rb index 585495a46..3a17f2c67 100644 --- a/app/helpers/global_activities_helper.rb +++ b/app/helpers/global_activities_helper.rb @@ -53,6 +53,8 @@ module GlobalActivitiesHelper path = '' case obj + when UserGroup + path = users_settings_team_user_group_path(obj.team, obj) when User return "[@#{obj.full_name}~#{obj.id.base62_encode}]" when Tag @@ -61,6 +63,8 @@ module GlobalActivitiesHelper when Team path = projects_path(team: obj.id) when Repository + return I18n.t('repositories.private') unless can_read_repository?(obj) + path = repository_path(obj, team: obj.team.id) when RepositoryRow # Handle private repository rows @@ -70,6 +74,8 @@ module GlobalActivitiesHelper path = repository_path(obj.repository, team: obj.repository.team.id) when RepositoryColumn + return I18n.t('repositories.repository_column.private') unless can_read_repository?(obj.repository) + return current_value unless obj.repository path = repository_path(obj.repository, team: obj.repository.team.id) diff --git a/app/helpers/reports_helper.rb b/app/helpers/reports_helper.rb index fc90a2b3a..7f4c5b7f5 100644 --- a/app/helpers/reports_helper.rb +++ b/app/helpers/reports_helper.rb @@ -2,6 +2,7 @@ module ReportsHelper include StringUtility + include FormFieldValuesHelper include Canaid::Helpers::PermissionsHelper def render_report_element(element, provided_locals = nil) @@ -10,6 +11,8 @@ module ReportsHelper return unless can_read_experiment?(element.experiment) when 'my_module' return unless can_read_my_module?(element.my_module) + when 'my_module_repository' + return unless can_read_repository?(element.repository) end # Determine partial @@ -22,7 +25,7 @@ module ReportsHelper # First, recursively render element's children if element.children.active.present? element.children.active.find_each do |child| - children_html.safe_concat render_report_element(child, provided_locals) + children_html.safe_concat render_report_element(child, provided_locals) || '' end end locals[:report_element] = element @@ -139,6 +142,8 @@ module ReportsHelper ) elsif form_field_value.is_a?(FormDatetimeFieldValue) form_field_value&.formatted_localize + elsif form_field_value.is_a?(FormRepositoryRowsFieldValue) + form_repository_rows_field_value_formatter(form_field_value) else form_field_value&.formatted end diff --git a/app/helpers/repository_datatable_helper.rb b/app/helpers/repository_datatable_helper.rb index 14505d311..490623075 100644 --- a/app/helpers/repository_datatable_helper.rb +++ b/app/helpers/repository_datatable_helper.rb @@ -101,11 +101,14 @@ module RepositoryDatatableHelper # otherwise it will result in duplicated SQL queries has_stock_management = repository.has_stock_management? reminders_enabled = !options[:disable_reminders] && Repository.reminders_enabled? + shareable_link_view = options[:shareable_link_view] && my_module.shared? # Always disabled in a simple view stock_managable = false stock_consumption_permitted = has_stock_management && stock_consumption_permitted?(repository, my_module) repository_rows.map do |record| + next { code: record.code } unless shareable_link_view || can_read_repository?(record.repository) + row = { DT_RowId: record.id, DT_RowAttr: { 'data-state': row_style(record, my_module) }, @@ -156,7 +159,7 @@ module RepositoryDatatableHelper consumed_stock_formatted = number_with_precision( record.consumed_stock, - precision: (record.repository.repository_stock_column.metadata['decimals'].to_i || 0), + precision: record.repository.repository_stock_column.metadata['decimals'].to_i || 0, strip_insignificant_zeros: true ) row['consumedStock'][:value] = { diff --git a/app/helpers/table_helper.rb b/app/helpers/table_helper.rb index e5a64911d..0842cf1af 100644 --- a/app/helpers/table_helper.rb +++ b/app/helpers/table_helper.rb @@ -4,7 +4,7 @@ module TableHelper def table_data content = {} data = JSON.parse(contents)['data'] - return [] if data.blank? + return {} if data.blank? cells = metadata.fetch('cells', []) diff --git a/app/helpers/user_roles_helper.rb b/app/helpers/user_roles_helper.rb index 17bf224dd..94f24db15 100644 --- a/app/helpers/user_roles_helper.rb +++ b/app/helpers/user_roles_helper.rb @@ -2,15 +2,23 @@ module UserRolesHelper def user_roles_collection(object, with_inherit: false) - permission_group = "#{object.class.name}Permissions".constantize - permissions = permission_group.constants.map { |const| permission_group.const_get(const) } + if (object.respond_to?(:private_shared_with_read?) && object.private_shared_with_read?(current_team)) || + (object.respond_to?(:shared_with_read?) && object.shared_with_read?(current_team)) + viewer_role = UserRole.find_predefined_viewer_role + roles = [[viewer_role.name, viewer_role.id]] + else + 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) + end - roles = user_roles_subset_by_permissions(permissions).order(id: :asc).pluck(:name, :id) if with_inherit roles = [[t('access_permissions.reset'), 'reset', t("access_permissions.partials.#{object.class.name.underscore}_member_field.reset_description")]] + roles end + roles end diff --git a/app/javascript/packs/vue/user_groups_show.js b/app/javascript/packs/vue/user_groups_show.js new file mode 100644 index 000000000..6d5ede78a --- /dev/null +++ b/app/javascript/packs/vue/user_groups_show.js @@ -0,0 +1,10 @@ +import { createApp } from 'vue/dist/vue.esm-bundler.js'; +import { PerfectScrollbar } from 'vue3-perfect-scrollbar'; +import UserGroupShow from '../../vue/user_groups/show.vue'; +import { mountWithTurbolinks } from './helpers/turbolinks.js'; + +const app = createApp(); +app.component('UserGroupShow', UserGroupShow); +app.config.globalProperties.i18n = window.I18n; +app.use(PerfectScrollbar); +mountWithTurbolinks(app, '#userGroupShow'); diff --git a/app/javascript/packs/vue/user_groups_table.js b/app/javascript/packs/vue/user_groups_table.js new file mode 100644 index 000000000..abb4e6a37 --- /dev/null +++ b/app/javascript/packs/vue/user_groups_table.js @@ -0,0 +1,10 @@ +import { createApp } from 'vue/dist/vue.esm-bundler.js'; +import { PerfectScrollbar } from 'vue3-perfect-scrollbar'; +import UserGroupsTable from '../../vue/user_groups/index.vue'; +import { mountWithTurbolinks } from './helpers/turbolinks.js'; + +const app = createApp(); +app.component('UserGroupsTable', UserGroupsTable); +app.config.globalProperties.i18n = window.I18n; +app.use(PerfectScrollbar); +mountWithTurbolinks(app, '#userGroupsTable'); diff --git a/app/javascript/vue/dashboard/new_task.vue b/app/javascript/vue/dashboard/new_task.vue index 92cf6853a..fc09d5913 100644 --- a/app/javascript/vue/dashboard/new_task.vue +++ b/app/javascript/vue/dashboard/new_task.vue @@ -22,24 +22,6 @@ @change="changeProject" /> -
<%= t('comments.empty_state.title') %>
-<%= t('comments.empty_state.description') %>
++ <%= t('comments.empty_state.description') %> +
+ <% if can_manage_team?(@team) %> + <%= render partial: "shared/inline_editing", + locals: { + initial_value: @team.name, + config: { + field_to_udpate: 'name', + params_group: 'team', + path_to_update: update_team_path(@team, format: :json) + } + } %> + <% else %> + <%= @team.name %> + <% end %> +
+diff --git a/app/views/users/settings/teams/index.html.erb b/app/views/users/settings/teams/index.html.erb index 979e49f75..afc21a3c4 100644 --- a/app/views/users/settings/teams/index.html.erb +++ b/app/views/users/settings/teams/index.html.erb @@ -21,7 +21,7 @@ <%= t("users.settings.teams.index.no_teams") %> <% end %> <% if can_create_teams? %> - + <%= link_to new_team_path, class: "btn btn-primary", style: "margin-left: 30px;" do %> diff --git a/app/views/users/settings/teams/members.html.erb b/app/views/users/settings/teams/members.html.erb new file mode 100644 index 000000000..bd8946494 --- /dev/null +++ b/app/views/users/settings/teams/members.html.erb @@ -0,0 +1,47 @@ +<% provide(:head_title, t("users.settings.teams.head_title")) %> +<% provide(:container_class, "no-second-nav-container") %> + +<% content_for :head do %> + +<% end %> + +
+ <%= render partial: 'header' %>
+
+
+
+ <% if can_invite_team_users?(@team) %>
+
+
+
+ <%= I18n.t('users.settings.teams.edit.add_user') %>
+
+
+ <%= render(partial: 'shared/invite_users_modal',
+ locals: { modal_id: 'team-invite-users-modal',
+ type: 'invite_to_team_with_role',
+ team: @team } ) %>
+ <% end %>
+
+
+
+ <%= t("users.settings.teams.edit.thead_user_name") %>
+ <%= t("users.settings.teams.edit.thead_email") %>
+ <%= t("users.settings.teams.edit.thead_role") %>
+ <%= t("users.settings.teams.edit.thead_joined_on") %>
+ <%= t("users.settings.teams.edit.thead_status") %>
+ <%= t("users.settings.teams.edit.thead_actions") %>
+
+
+
+
+
+
+
+
+<%= render partial: 'users/settings/teams/description_modal' %>
+<%= render partial: 'users/settings/teams/destroy_modal', locals: { team: @team } %>
+<%= render partial: 'users/settings/user_teams/destroy_user_team_modal' %>
+<%= stylesheet_link_tag 'datatables' %>
+<%= javascript_include_tag 'users/settings/teams/show' %>
+
diff --git a/app/views/users/settings/teams/new.html.erb b/app/views/users/settings/teams/new.html.erb
index c12e24504..8cce67c77 100644
--- a/app/views/users/settings/teams/new.html.erb
+++ b/app/views/users/settings/teams/new.html.erb
@@ -11,7 +11,9 @@
<%= f.text_field :name,
autofocus: true,
class: 'sci-input-field',
- placeholder: t('users.settings.teams.new.name_placeholder') %>
+ placeholder: t('users.settings.teams.new.name_placeholder'),
+ data: {'e2e'=>'e2e-IF-settings-workspaces-newWorkspace-name'}
+ %>
<%=t 'users.settings.teams.new.name_sublabel' %> @@ -19,15 +21,26 @@
<%=t 'users.settings.teams.new.description_sublabel' %>
- <% if can_manage_team?(@team) %> - <%= render partial: "shared/inline_editing", - locals: { - initial_value: @team.name, - config: { - field_to_udpate: 'name', - params_group: 'team', - path_to_update: update_team_path(@team, format: :json) - } - } %> - <% else %> - <%= @team.name %> - <% end %> -
- ++ <%= t('user_groups.promo.promo_title') %> +
++ <% if can_manage_team?(@team) %> + <%= render partial: "shared/inline_editing", + locals: { + initial_value: @user_group.name, + config: { + field_to_udpate: 'name', + params_group: 'user_group', + path_to_update: users_settings_team_user_group_path(@team, @user_group, format: :json) + } + } %> + <% else %> + <%= t('user_groups.show.title') %> + <%= @user_group.name %> + <% end %> +
+Ask your team admin to enable sharing in the team settings." + disabled: "Sharing is disabled for your workspace.
Ask your workspace admin to enable sharing in the workspace settings." notes: title: "Notes" no_description: "No task description" @@ -1485,6 +1468,9 @@ en: checkbox_label: 'Mark as output' direct_assign: success: "Successfully assigned an item to the task." + repository: + private_repository_name: "Private inventory" + private_repository_row_name: "Private item (%{repository_row_code})" protocol: title: "Protocol" options_dropdown: @@ -1605,9 +1591,9 @@ en: name_label: "Templates protocol name" name_placeholder: "My protocol" type_label: "Save to" - type_public: "Team protocols" + type_public: "Workspace protocols" type_private: "My protocols" - type_text: "You can make a copy of a protocol and save it to Team protocols or My protocols. Team protocols are visible to all team members, My protocols are for personal usage only." + type_text: "You can make a copy of a protocol and save it to Workspace protocols or My protocols. Workspace protocols are visible to all workspace members, My protocols are for personal usage only." link_label: "Link task to this protocol in templates" link_text: "Warning! This will unlink the currently linked protocol." error_400: "Due to unknown error, protocol could not be copied to templates." @@ -1805,8 +1791,8 @@ en: title: 'No results found' description: 'Try another search request' unshared_inventory: - title_html: The inventory %{inventory_name} is no longer shared with your team. - body_html: This inventory has been unshared with your team by the inventory’s owner. To view the item/s that are assigned to your task/s contact the %{team_name} team administrator %{admin_name} (%{admin_email}). + title_html: The inventory %{inventory_name} is no longer shared with your workspace. + body_html: This inventory has been unshared with your workspace by the inventory’s owner. To view the item/s that are assigned to your task/s contact the %{team_name} workspace administrator %{admin_name} (%{admin_email}). open_mobile_app: "Open mobile app" status_error: general: "Status transition failed" @@ -2111,8 +2097,8 @@ en: user_join_full: "%{user} joined on %{date} at %{time}." create: "Add" invite_users_link: "Invite users" - invite_users_details: "to team %{team}." - contact_admins: "To invite additional users to team %{team}, contact its administrator/s." + invite_users_details: "to workspace %{team}." + contact_admins: "To invite additional users to workspace %{team}, contact its administrator/s." medium_zoom: no_access: "Can't view task" update: @@ -2145,7 +2131,7 @@ en: left_navigation: welcome: "Welcome to SciNote!" title: "SciNote Electronic Lab Notebook (ELN) Software" - description_1: "SciNote is a leading ELN with inventory, compliance and team management tools, used by the FDA, USDA and over 100K scientific professionals." + description_1: "SciNote is a leading ELN with inventory, compliance and workspace management tools, used by the FDA, USDA and over 100K scientific professionals." description_2: "What do our users like the most?" description_3: bullet_point_1: "Ease of use" @@ -2243,10 +2229,13 @@ en: success_flash: "Table result successfully deleted." repositories: + private: "Private inventory" repository: "Inventory: %{name}" snapshot_failed: "Snapshot failed" + repository_column: + private: "Private column" icon_title: - i_shared: "Shared inventory (owned by your Team)" + i_shared: "Shared inventory (owned by your Workspace)" shared_edit: "Shared inventory (owned by %{team_name}). You can edit." shared_read: "Shared inventory (owned by %{team_name}). You can view." index: @@ -2257,8 +2246,8 @@ en: checked_description: "Show all items" head_title: "Inventories" title: "Inventories" - empty_title: "Your team has no inventories yet" - empty_description_no_create: "It would be a shame to waste all this space. Sadly you don’t have permission to add inventories. You can ask your team admin to grant you that permission." + empty_title: "Your workspace has no inventories yet" + empty_description_no_create: "It would be a shame to waste all this space. Sadly you don’t have permission to add inventories. You can ask your workspace admin to grant you that permission." empty_description_with_create: "It would be a shame to waste all this space. Create your first inventory now" sidebar_instructions: "This is where a list of inventories will appear once they are created. This list will allow you to quickly jump from one inventory to another" sidebar_repo1: "Reagents" @@ -2292,7 +2281,8 @@ en: message_html: "Are you sure you want to delete inventory %{name}? This action is irreversible." alert_heading: "Deleting inventory has following consequences:" alert_line_1: "all data inside the inventory will be lost;" - alert_line_2: "all references to inventory items will be rendered as invalid." + alert_line_2: "all references to inventory items will be rendered as invalid;" + alert_line_3: "if task assigned item snapshots exist, access to them will follow task-level permissions." delete: "Delete" modal_rename: title_html: "Rename inventory: %{name}" @@ -2308,8 +2298,8 @@ en: modal_export: title: "Export Inventories" description_p1_html: - one: "You are about to export 1 inventory in %{team_name}'s team." - other: "You are about to export %{count} inventories in %{team_name}'s team." + one: "You are about to export 1 inventory in %{team_name}'s workspace." + other: "You are about to export %{count} inventories in %{team_name}'s workspace." description_alert: "This process may take minutes to hours, depending on the file size. Any new data entered afterward won't be included in the export." description_p2: "You will receive an email and a notification when the download file is ready. For security reasons, the link will expire in 7 days." description_p3_html: "After this export, you will have %{remaining_export_requests} export requests left today (max %{requests_limit} requests per day)." @@ -2328,20 +2318,22 @@ en: only_system_defined_columns: 'Only system defined columns.' modal_confirm_sharing: title: "Inventory sharing changes" - description_1: "You will no longer share this inventory with some of the teams. All unshared inventory items assigned to tasks will be automatically removed and this action is irreversible. Any item relationship links (if they exist) will also be deleted." + description_1: "You will no longer share this inventory with some of the workspaces. All unshared inventory items assigned to tasks will be automatically removed and this action is irreversible. Any item relationship links (if they exist) will also be deleted." description_2: "Are you sure you want to apply the changes you made?" confirm: "Apply" export: notification: error: title: "Your Inventories export failed. Please contact support." + table: + access: "Access" show: name: "Name" archived_inventory_items: "%{repository_name} archived items" archived_inventory: "Archived %{repository_name}" inventory_archived_items: "%{repository_name} archived items" subtitle: "Owned by %{team_name}" - your_team: "your Team" + your_team: "your Workspace" show_active_items: "Show active items" show_archived_items: "Show archived items" archived_view_label: @@ -2742,7 +2734,7 @@ en: message: "Are you sure you wish to permanently delete selected column %{column}? This action is irreversible." alert_heading: "Deleting a column has following consequences:" alert_line_1: "you will lose information in this column for %{nr} item(s);" - alert_line_2: "the column will be deleted for all team members." + alert_line_2: "the column will be deleted for all workspace members." alert_line_3: "any saved inventory filters relying on this column will be updated (individual filters based on this column will be removed from saved filters)." delete: "Delete" modal_parse: @@ -2787,9 +2779,9 @@ en: success_flash: "Successfully restored items in inventory %{repository}" unsuccess_flash: "Unsuccessfully restored items in inventory %{repository}" multiple_share_service: - unable_to_share: "Unable to share %{repository} inventory with %{team} team." - unable_to_unshare: "Unable to unshare %{repository} inventory with %{team} team." - unable_to_update: "Unable to update sharing %{repository} inventory with %{team} team." + unable_to_share: "Unable to share %{repository} inventory with %{team} workspace." + unable_to_unshare: "Unable to unshare %{repository} inventory with %{team} workspace." + unable_to_update: "Unable to update sharing %{repository} inventory with %{team} workspace." not_allowed: "You are not allowed to share this repository!" invalid_arguments: "Can't find %{key}" nothing_to_delete: "Can't find sharing relation for destroy" @@ -2829,7 +2821,7 @@ en: one: "Assigned to %{count} private task that will not be displayed" other: "Assigned to %{count} private tasks that will not be displayed" labels: - team: "Team:" + team: "Workspace:" project: "Project:" experiment: "Experiment:" my_module: "Task:" @@ -2838,6 +2830,7 @@ en: item: "Item: " inventory: "Inventory: " empty: "This item has no relationships assigned." + private_item_name: "Private item" parents: count: "Parents (%{count})" empty: "" @@ -2926,8 +2919,8 @@ en: select_valid_value: 'Select a valid date or date-time value' browser_alert_message: 'You have made some changes, are you sure you want to leave this page?' errors: - load_error_header: 'Your team does not have the necessary permissions to view the details of this item.' - load_error_description: 'To obtain access, reach out to your team administrator.' + load_error_header: 'You do not have the necessary permissions to view the details of this item.' + load_error_description: 'To obtain access, reach out to your workspace administrator.' highlight_component: information_label: 'Information' custom_columns_label: 'Custom columns' @@ -2977,7 +2970,7 @@ en: added_amount_unit: 'Added amount unit' consumed_by: 'Consumed by' consumed_on: 'Consumed on' - team: 'Team' + team: 'Workspace' project: 'Project' project_id: 'Project ID' experiment: 'Experiment' @@ -3255,6 +3248,7 @@ en: no_libraries: create_new_button: "New inventory" create_new_button_tooltip: "Create new inventory" + access: 'Access' show: head_title: "Inventories | %{library}" repository_row: @@ -3324,8 +3318,8 @@ en: started: "editing started" finished: "editing finished" protocols: - my_to_team_message: 'My protocols to Team protocols' - team_to_my_message: 'Team protocols to My protocols' + my_to_team_message: 'My protocols to Workspace protocols' + team_to_my_message: 'Workspace protocols to My protocols' user_my_modules: new: @@ -3455,7 +3449,7 @@ en: revoked: "API key revoked!" new: head_title: "Sign up" - team_name_label: "Team name" + team_name_label: "Workspace name" team_name_placeholder: "e.g. John's lab" email_label: "E-mail" full_name_label: "Full name" @@ -3467,18 +3461,19 @@ en: head_title: "Complete the Sign up" statistics: title: "My statistics" - team: "Team" + team: "Workspace" project: "Project" experiment: "Experiment" protocol: "Protocol" settings: changed_team_flash: "You are working on %{team} now!" changed_team_error_flash: "Something went wrong! Try again later." - changed_team_in_search: "The searched item is not in your current team. You will be redirected to %{team} team!" + changed_team_in_search: "The searched item is not in your current workspace. You will be redirected to %{team} workspace!" sidebar: account: "Account" - teams: "Teams" + teams: "Workspaces" webhooks: "Webhooks" + groups: "Groups" account_nav: profile: "My profile" preferences: "My preferences" @@ -3612,12 +3607,16 @@ en: not_found: "You have no Connected accounts for this provider" generic: "Unable to unlink linked account" teams: - head_title: "Settings | Teams" + head_title: "Settings | Workspaces" breadcrumbs: - all: "All teams" - new_team: "New team" + all: "All workspaces" + new_team: "New workspace" + navigation: + details: "Details" + members: "Members" + groups: "Groups" show: - enter_description: "Enter Team description" + enter_description: "Enter Workspace description" tasks_share: enable_label: "Enable task sharing" enable_success_message: "Task sharing is enabled." @@ -3629,25 +3628,25 @@ en: description_p2: "This action will deactivate sharing tasks and permanently deactivate all shared links for %{counter} task(s). Any user who has shared links will no longer be able to access the shared tasks." disable_button: "Disable" index: - description_label: "Team is a group of SciNote users who are working on the same projects and share the inventories." + description_label: "Workspace is a group of SciNote users who are working on the same projects and share the inventories." member_of: - one: "Currently you are member of %{count} team." - other: "Currently you are member of %{count} teams." - no_teams: "You are not a member of any team." - new_team: "New team" - thead_name: "Team" + one: "Currently you are member of %{count} workspace." + other: "Currently you are member of %{count} workspaces." + no_teams: "You are not a member of any workspace." + new_team: "New workspace" + thead_name: "Workspace" thead_role: "Role" thead_created_at: "Created at" thead_joined_on: "Joined on" thead_members: "Members" na: "n/a" - leave: "Leave team" + leave: "Leave workspace" new: - name_label: "Team name" - name_placeholder: "My team" - name_sublabel: "Pick a name that would best describe your team (e.g. 'University of ..., Department of ...')." + name_label: "Workspace name" + name_placeholder: "My workspace" + name_sublabel: "Pick a name that would best describe your workspace (e.g. 'University of ..., Department of ...')." description_label: "Description" - description_sublabel: "Describe your team." + description_sublabel: "Describe your workspace." create: "Create" edit: header_created_at: "Created on:" @@ -3655,12 +3654,12 @@ en: header_created_by_name_email: "%{name} (%{email})" header_space_taken: "Space usage:" header_no_description: "No description" - name_title: "Edit team name" + name_title: "Edit workspace name" name_label: "Name" - description_title: "Edit team description" + description_title: "Edit workspace description" description_label: "Description" - team_members_title: "Team members" - add_user: "Add team members" + team_members_title: "Workspace members" + add_user: "Add workspace members" thead_user_name: "Name" thead_email: "Email" thead_joined_on: "Joined on" @@ -3670,27 +3669,27 @@ en: user_dropdown: role_label: "User role" remove_label: "Remove" - delete_team_heading: "Delete team" - can_delete_message: "This team can be deleted because it doesn't have any projects." - delete_text: "Delete team." - cannot_delete_message_projects: "Cannot delete this team. Only empty teams (without any projects) can be deleted." + delete_team_heading: "Delete workspace" + can_delete_message: "This workspace can be deleted because it doesn't have any projects." + delete_text: "Delete workspace." + cannot_delete_message_projects: "Cannot delete this workspace. Only empty workspaces (without any projects) can be deleted." modal_destroy_team: - title: "Delete team" - message: "Are you sure you wish to delete team %{team}? All of the users will be removed from the team as well. This action is irreversible." - confirm: "Delete team" - flash_success: "Team %{team} was successfully deleted." + title: "Delete workspace" + message: "Are you sure you wish to delete workspace %{team}? All of the users will be removed from the workspace as well. This action is irreversible." + confirm: "Delete workspace" + flash_success: "Workspace %{team} was successfully deleted." user_teams: - leave_uo_heading: "Leave team %{team}" - leave_message: "You will lose access to all content associated with the team, including projects, inventories, and protocol templates." - destroy_message: 'Removing a team %{role} will revoke their access to all content associated with the team, including projects, inventories, and protocol templates.' - leave_uo_confirm: "Leave team" - destroy_uo_heading: "Remove team %{role} %{user} from team %{team}" - message_owner: "Other team owner(s) will be able to view and manage access to the content." + leave_uo_heading: "Leave workspace %{team}" + leave_message: "You will lose access to all content associated with the workspace, including projects, inventories, and protocol templates." + destroy_message: 'Removing a workspace %{role} will revoke their access to all content associated with the workspace, including projects, inventories, and protocol templates.' + leave_uo_confirm: "Leave workspace" + destroy_uo_heading: "Remove workspace %{role} %{user} from workspace %{team}" + message_owner: "Other workspace owner(s) will be able to view and manage access to the content." message_alert: "This action cannot be undone." destroy_uo_alert_line_3: "This action cannot be undone." destroy_uo_confirm: "Remove" leave_flash: "You have left %{team}." - remove_flash: "%{user} has been successfully removed from the team %{team}" + remove_flash: "%{user} has been successfully removed from the workspace %{team}" general_error: "Something went wrong" user_roles: @@ -3831,13 +3830,12 @@ en: import_current: "Load Current" import_all: "Load All" import: "Load" - import_to_team_protocols_label: "Import to Team Protocols" + import_to_team_protocols_label: "Import to Workspace Protocols" import_to_private_protocols_label: "Import to My Protocols" import_protocols_label: "Import" forms_error: "Form %{form_id} doesn't exist or you don't have access and it won’t be imported." import_protocol_notification: title: "The import process has been successfully completed. You can download original file here: %{link}" - message: "Protocol template:" import_protocol_notification_error: title: "Import failed" message: "We're sorry but the file import process has encountered an error. Please make another attempt, and if the issue persists, please contact support for further assistance." @@ -3866,7 +3864,7 @@ en: name_label: "Protocol template name" name_placeholder: "Enter a protocol template name…" description: "You will create a copy of a protocol and save it to Protocol templates." - access_label: "Grant access to all team members" + access_label: "Grant access to all workspace members" role_label: "Default user role" create_new: "Create" create_copy: "Save" @@ -3922,7 +3920,7 @@ en: archive: "Archive" restore: "Restore" empty_placeholder: "There is no action available" - public_description: "Team protocols are visible and can be used by everyone from the team." + public_description: "Workspace protocols are visible and can be used by everyone from the workspace." private_description: "My protocols are only visible to you." create_new: "New protocol template" create_new_tooltip: "Create new protocol template" @@ -3939,7 +3937,7 @@ en: modal_import_json_notice: "Upload your protocols.io protocol file" export: "Export" make_private: "Move to My Protocols" - publish: "Move to Team protocols" + publish: "Move to Workspace protocols" archive_action: "Archive" archive_flash_html: "%{count} protocol template(s) successfully archived." thead: @@ -3970,15 +3968,15 @@ en: restore_flash_html: "%{count} protocol template(s) successfully restored." make_private_unauthorized: "You do not have permission to move selected protocols to My protocols." make_private_error: "Error occurred while moving selected protocols to My protocols." - publish_unauthorized: "You do not have permission to move selected protocols to Team protocols." - publish_error: "Error occurred while moving selected protocols to Team protocols." + publish_unauthorized: "You do not have permission to move selected protocols to Workspace protocols." + publish_error: "Error occurred while moving selected protocols to Workspace protocols." row_renamed_html: "%{old_name} to %{new_name}" no_protocol_name: "(no name)" create: title: "Create new protocol" name_label: "Protocol name" name_placeholder: "My protocol" - message_public: "When you create a new Team protocol, it will instantly be visible to all members of the team." + message_public: "When you create a new Workspace protocol, it will instantly be visible to all members of the workspace." message_private: "When you create a new My protocol, it will only be visible to you." submit: "Create" clone: @@ -4017,7 +4015,7 @@ en: powered_by: 'Powered by protocols.io' banner_text: 'Protocol Preview' import: - public: "Team protocols" + public: "Workspace protocols" private: "My protocols" success_flash: 'Protocol %{name} successfully imported.' sort: @@ -4121,7 +4119,7 @@ en: description: "Select a form you want to add to your protocol" label: "Form" placeholder: "Select a form" - no_forms: "Create and publish one or request access from your team." + no_forms: "Create and publish one or request access from your workspace." add_form: "Add form" take_me_there: "Take me there" recently_used: "Recently used" @@ -4216,11 +4214,60 @@ en: edit: head_title: "Edit protocol" no_keywords: "—" - + user_groups: + promo: + head_title: 'User groups' + promo_title: 'This feature is disabled by default in open source SciNote' + create: + success: "Group created successfully." + error: "There was a problem creating group. Please try again." + delete: + success: "Group deleted successfully." + error: "There was a problem deleting group. Please try again." + index: + new_group: "New group" + group_name: "Group name" + members: "Members" + created_by: "Created by" + created_on: "Created on" + updated_on: "Updated on" + create_modal: + title: "Create a new group" + description: "You can add as many members from this workspace as you wish." + name: "Group name" + name_placeholder: "Add a name for your group" + select_members: "Select members" + select_members_placeholder: "Select for members" + create_button: "Create group" + delete_modal: + title: "Delete group" + description_html: "You are about to delete %{group}. All members added to this group will lose their access to all related content in this workspace.
Access added to members outside of this group will not be affected.
This action cannot be undone. Are you sure you want to delete this group?" + confirm: "Delete group" + toolbar: + delete: "Delete" + show: + title: "Group" + add_members: "Add members" + name: "Name" + email: "Email" + created_at: "Added on" + remove: "Remove" + add_members_modal: + title: "Add members" + select_members: "Select members" + select_members_placeholder: "Search for members" + success: "Members added successfully." + error: "There was a problem adding members. Please try again." + remove_modal: + title: "Remove member from %{group}" + description_html: "You are about to remove %{number} member(s) from this group.
Group-based access to content will be removed.
Are you sure you want to remove members from group?" + confirm: "Remove" + success: "Members removed successfully." + error: "There was a problem removing members. Please try again." invite_users: to_team: title: "Invite members to %{team}" - heading: "Invite more people to team %{team} and start using SciNote." + heading: "Invite more people to workspace %{team} and start using SciNote." no_team: title: "Invite users to SciNote" heading: "Invite more people to start using SciNote." @@ -4230,16 +4277,16 @@ en: input_label: "New member emails" input_subtitle: "You can enter one or more emails." input_subtitle: "You can enter one or more emails." - invite_to_team_heading: "Invite users to my team:" + invite_to_team_heading: "Invite users to my workspace:" invite_btn: "Invite members" invite_as: "As %{role}" invite_guest: "As Guests" invite_user: "As Normal Users" invite_admin: "As Administrators" new_member_email: "New member email" - select_team: "Select a team" - select_team_blank: "Do not add this user to any team" - select_team_role: "Select a team role" + select_team: "Select a workspace" + select_team_blank: "Do not add this user to any workspace" + select_team_role: "Select a workspace role" default: "default" errors: @@ -4247,11 +4294,11 @@ en: results: heading: "Invitation results:" user_exists: "User is already a member of SciNote." - user_exists_unconfirmed_invited_to_team: "User is already a member of SciNote but is not confirmed yet - successfully invited to team %{team} as %{role}." - user_exists_and_in_team: "User is already a member of SciNote and team %{team} as %{role}." - user_exists_invited_to_team: "User was already a member of SciNote - successfully invited to team %{team} as %{role}." + user_exists_unconfirmed_invited_to_team: "User is already a member of SciNote but is not confirmed yet - successfully invited to workspace %{team} as %{role}." + user_exists_and_in_team: "User is already a member of SciNote and workspace %{team} as %{role}." + user_exists_invited_to_team: "User was already a member of SciNote - successfully invited to workspace %{team} as %{role}." user_created: "User successfully invited to SciNote." - user_created_invited_to_team: "User successfully invited to SciNote and team %{team} as %{role}." + user_created_invited_to_team: "User successfully invited to SciNote and workspace %{team} as %{role}." user_invalid: "Invalid email." too_many_emails: "Only invited first %{nr} emails. To invite more users, " @@ -4290,9 +4337,10 @@ en: repository_stock: "Low stock reminder" repository_date_reminder: "Date reminder" other_smart_annotation: "You were tagged" - other_team_invitation: "You were invited or removed from the team" + other_team_invitation: "You were invited or removed from the workspace" experiment_due_date: "Due date reminder if you have Owner or User access role on a Experiment" project_due_date: "Due date reminder if you have Owner access role on a Project" + other_user_group_member: "You were added or removed from the group" content: my_module_due_date_reminder: title_html: "Task due date reminder" @@ -4359,8 +4407,8 @@ en: protocol_description_annotation_message_html: "Protocol: %{protocol}" protocol_step_annotation_message_html: "Protocol: %{protocol}" email_title: "You've received a SciNote notification!" - assign_user_to_team: "%{assigned_user} was added as %{role} to team %{team} by %{assigned_by_user}." - unassign_user_from_team: "%{unassigned_user} was removed from team %{team} by %{unassigned_by_user}." + assign_user_to_team: "%{assigned_user} was added as %{role} to workspace %{team} by %{assigned_by_user}." + unassign_user_from_team: "%{unassigned_user} was removed from workspace %{team} by %{unassigned_by_user}." task_completed: "%{user} completed task %{module}. %{date} | Project: %{project} | Experiment: %{experiment}" assets: @@ -4473,8 +4521,8 @@ en: title: "People" header: "Type the name or email of the user you want to mention (they will be notified)" help: "Navigate: ↑ ↓ • Insert: Enter / Tab • Dismiss: Esc" - not_in_this_team: 'Not in this team' - popover_html: "Team: %{team}
Role: %{role}
Joined: %{time}" + not_in_this_team: 'Not in this workspace' + popover_html: "Workspace: %{team}
Role: %{role}
Joined: %{time}" res: archived: "(archived)" removed: "(removed)" @@ -4495,7 +4543,7 @@ en: change_experiment_role: "Change experiment role" change_my_module_role: "Change task role" select_role: "Select role" - assign_all_team_members: "Grant access to all team members" + assign_all_team_members: "Grant access to all workspace members" from_project: "%{user_role} [from project]" from_experiment: "%{user_role} [from experiment]" experiment_select_role: "Change experiment role" @@ -4506,8 +4554,8 @@ en: general_error: "Something went wrong" access_permissions: - all_team: "all team" - everyone_else: "Everyone else at %{team_name}" + all_team: "all workspace" + everyone_else: "Everyone at %{team_name}" reset: "Inherit role" remove_access: "Remove access" grant_access: "Grant new access" @@ -4521,7 +4569,7 @@ en: failure: "Something went wrong" update: failure: "Something went wrong" - revoke_all_team_members: "You have successfully removed access from all team members" + revoke_all_team_members: "You have successfully removed access from all workspace members" partials: edit_assignments_content: title: "Manage access for %{resource_name}" @@ -4559,8 +4607,10 @@ en: mymodule_tooltip: "This role was set on this task" form_tooltip: "This role was set on this form." form_tooltip_inherit: "This role was inherited from the form." + repository_tooltip: "This role was set on this inventory." + repository_tooltip_inherit: "This role was inherited from the inventory." public_members_dropdown: - title: "Members of team %{team}" + title: "Members of workspace %{team}" projects: modals: @@ -4574,6 +4624,12 @@ en: title: "Access to %{resource_name}" edit_modal: title: "Manage access for %{resource_name}" + repositories: + modals: + show_modal: + title: "Access to %{resource_name}" + edit_modal: + title: "Manage access for %{resource_name}" experiments: modals: show_modal: @@ -4755,7 +4811,7 @@ en: filter: "Filter:" no_teams: title: "Your dashboard is empty!" - text: "You should request access from your team administrators." + text: "You should request access from your workspace administrators." select_dropdown: placeholder: "Select an option" few_options_placeholder: 'options selected' @@ -4857,7 +4913,7 @@ en: inventories: "Inventories" protocols: "Protocol templates" labels: "Label templates" - teams: "All Teams" + teams: "All Workspaces" addons: "Add-ons" locations: "Locations" label_printer: "Label printer" @@ -4977,20 +5033,21 @@ en: modal_share: title: "Share %{object_name}" submit: "Save sharing options" - share_with_team: "Share with Team" + share_with_team: "Share with Workspace" can_edit: "Can Edit" - all_teams: "All teams (current & new)" - all_teams_tooltip: "This will disable individual team settings" + all_teams: "All workspaces (current & new)" + all_teams_tooltip: "This will disable individual workspace settings" success_message: "Selected sharing options for the %{object_name} have been saved." errors: general: "Something went wrong." general_text_too_long: 'Text is too long' + general_saving_data: 'Error saving data' forbidden: title: "Access to this page is denied (403)." dialog: title: "It would seem that you are not allowed in here." - text: "You should request access from your team administrators." + text: "You should request access from your workspace administrators." button: "Go back" not_found: title: "This page could not be found (404)." diff --git a/config/locales/global_activities/en.yml b/config/locales/global_activities/en.yml index a2366eeab..393bd5661 100644 --- a/config/locales/global_activities/en.yml +++ b/config/locales/global_activities/en.yml @@ -1,7 +1,7 @@ en: global_activities: index: - teams: "Teams" + teams: "Workspaces" activity: "Activity created" period_label: "Activity created: " today: "Today" @@ -16,7 +16,7 @@ en: activity_type: "Activity types" user: "Users" clear_filters: "Clear filters" - select_teams: "Select Teams" + select_teams: "Select Workspaces" select_activity_groups: "Select Activity groups" select_activity_types: "Select Activity types" select_users: "Select Users" @@ -47,8 +47,8 @@ en: create_project_html: "%{user} created project %{project}." edit_project_html: "%{user} edited project %{project}." change_project_visibility_html: "%{user} changed project %{project}'s visibility to %{visibility}." - project_grant_access_to_all_team_members_html: "%{user} granted access to all team members of %{team} team with user role %{role} to project %{project}." - project_remove_access_from_all_team_members_html: "%{user} removed %{team} team members with user role %{role} from project %{project}." + project_grant_access_to_all_team_members_html: "%{user} granted access to all workspace members of %{team} workspace with user role %{role} to project %{project}." + project_remove_access_from_all_team_members_html: "%{user} removed %{team} workspace members with user role %{role} from project %{project}." archive_project_html: "%{user} archived project %{project}." restore_project_html: "%{user} restored project %{project} from archive." add_comment_to_project_html: "%{user} commented on project %{project}." @@ -125,7 +125,7 @@ en: edit_wopi_file_on_result_html: "%{user} edited Microsoft 365 file %{asset_name} on result %{result}: %{action}." export_protocol_from_task_html: "%{user} exported protocol from task %{my_module}" copy_protocol_in_repository_html: "%{user} duplicated protocol %{protocol_new} from protocol %{protocol_original} as a template" - user_leave_team_html: "%{user} left team %{team}" + user_leave_team_html: "%{user} left workspace %{team}" edit_wopi_file_on_step_html: "%{user} edited Microsoft 365 file %{asset_name} on protocol's step %{step_position} %{step} on task %{my_module}: %{action}." edit_wopi_file_on_step_in_repository_html: "%{user} edited Microsoft 365 file %{asset_name} on protocol %{protocol}'s step %{step_position} %{step} in Protocol repository: %{action}." restore_experiment_html: "%{user} restored experiment %{experiment}." @@ -145,12 +145,12 @@ en: archive_inventory_html: "%{user} archived inventory %{repository}." restore_inventory_html: "%{user} restored inventory %{repository}." delete_inventory_html: "%{user} deleted inventory %{repository}." - share_inventory_html: "%{user} shared inventory %{repository} with team %{team} with %{permission_level} permission." - unshare_inventory_html: "%{user} stopped sharing inventory %{repository} with team %{team}." - update_share_inventory_html: "%{user} changed permission of shared inventory %{repository} with team %{team} to %{permission_level}." - share_inventory_with_all_html: "%{user} shared inventory %{repository} with all teams (current and future) with %{permission_level} permission." - unshare_inventory_with_all_html: "%{user} stopped sharing inventory %{repository} with all teams (current and future)." - update_share_with_all_permission_level_html: "%{user} changed permission of shared inventory %{repository} with all teams to %{permission_level}." + share_inventory_html: "%{user} shared inventory %{repository} with workspace %{team} with %{permission_level} permission." + unshare_inventory_html: "%{user} stopped sharing inventory %{repository} with workspace %{team}." + update_share_inventory_html: "%{user} changed permission of shared inventory %{repository} with workspace %{team} to %{permission_level}." + share_inventory_with_all_html: "%{user} shared inventory %{repository} with all workspaces (current and future) with %{permission_level} permission." + unshare_inventory_with_all_html: "%{user} stopped sharing inventory %{repository} with all workspaces (current and future)." + update_share_with_all_permission_level_html: "%{user} changed permission of shared inventory %{repository} with all workspaces to %{permission_level}." create_item_inventory_html: "%{user} created inventory item %{repository_row}." edit_item_inventory_html: "%{user} edited inventory item %{repository_row}." delete_item_inventory_html: "%{user} deleted inventory item %{repository_row}." @@ -177,9 +177,9 @@ en: move_protocol_in_repository_html: "%{user} moved protocol %{protocol} from %{storage} in Protocol repository." import_protocol_in_repository_html: "%{user} imported protocol %{protocol} to Protocol repository from file." export_protocol_in_repository_html: "%{user} exported protocol %{protocol} from Protocol repository." - invite_user_to_team_html: "%{user} invited user %{user_invited} to team %{team} with user role %{role}." - remove_user_from_team_html: "%{user} removed user %{user_removed} from team %{team}." - change_users_role_on_team_html: "%{user} changed user %{user_changed}'s role in team %{team} to %{role}." + invite_user_to_team_html: "%{user} invited user %{user_invited} to workspace %{team} with user role %{role}." + remove_user_from_team_html: "%{user} removed user %{user_removed} from workspace %{team}." + change_users_role_on_team_html: "%{user} changed user %{user_changed}'s role in workspace %{team} to %{role}." export_projects_html: "%{user} exported project(s) %{projects} to .zip." export_inventory_items_html: "%{user} exported inventory item(s) from %{repository}." create_tag_html: "%{user} created tag %{tag} in project %{project}." @@ -276,13 +276,13 @@ en: protocol_template_access_granted_html: "%{user} granted access to %{user_target} with user role %{role} to protocol template %{protocol}." protocol_template_access_changed_html: "%{user} changed %{user_target}’s role on protocol template %{protocol} to %{role}." protocol_template_access_revoked_html: "%{user} removed %{user_target} with user role %{role} from protocol template %{protocol}." - protocol_template_access_granted_all_team_members_html: "%{user} granted access to all team members of %{team} team with user role %{role} to protocol template %{protocol}." + protocol_template_access_granted_all_team_members_html: "%{user} granted access to all workspace members of %{team} workspace with user role %{role} to protocol template %{protocol}." protocol_template_access_changed_all_team_members_html: "%{user} changed %{team}’s role on protocol template %{protocol} to %{role}." - protocol_template_access_revoked_all_team_members_html: "%{user} removed %{team} team members with user role %{role} from protocol template %{protocol}." + protocol_template_access_revoked_all_team_members_html: "%{user} removed %{team} workspace members with user role %{role} from protocol template %{protocol}." task_protocol_save_to_template_html: "%{user} created a new protocol template %{protocol} from a task." project_access_changed_all_team_members_html: "%{user} changed %{team}’s role on project %{project} to %{role}." - team_sharing_tasks_enabled_html: "%{user} enabled sharing tasks in team %{team}." - team_sharing_tasks_disabled_html: "%{user} disabled sharing tasks in team %{team}." + team_sharing_tasks_enabled_html: "%{user} enabled sharing tasks in workspace %{team}." + team_sharing_tasks_disabled_html: "%{user} disabled sharing tasks in workspace %{team}." task_link_sharing_enabled_html: "%{user} enabled link sharing for task %{my_module}." task_link_sharing_disabled_html: "%{user} disabled link sharing for task %{my_module}." shared_task_message_edited_html: "%{user} edited message for shared task %{my_module}." @@ -326,17 +326,17 @@ en: storage_location_deleted_html: "%{user} deleted location %{storage_location}." storage_location_edited_html: "%{user} edited location %{storage_location}." storage_location_moved_html: "%{user} moved location %{storage_location} from %{storage_location_original} to %{storage_location_destination}." - storage_location_shared_html: "%{user} shared location %{storage_location} with team %{team} with %{permission_level} permission." - storage_location_unshared_html: "%{user} unshared location %{storage_location} with team %{team}." - storage_location_sharing_updated_html: "%{user} changed permission of shared location %{storage_location} with team %{team} to %{permission_level}." + storage_location_shared_html: "%{user} shared location %{storage_location} with workspace %{team} with %{permission_level} permission." + storage_location_unshared_html: "%{user} unshared location %{storage_location} with workspace %{team}." + storage_location_sharing_updated_html: "%{user} changed permission of shared location %{storage_location} with workspace %{team} to %{permission_level}." storage_location_duplicated_html: "%{user} duplicated location %{storage_location}." container_storage_location_created_html: "%{user} created box %{storage_location}." container_storage_location_deleted_html: "%{user} deleted box %{storage_location}." container_storage_location_edited_html: "%{user} edited box %{storage_location}." container_storage_location_moved_html: "%{user} moved box %{storage_location} from %{storage_location_original} to %{storage_location_destination}." - container_storage_location_shared_html: "%{user} shared box %{storage_location} with team %{team} with %{permission_level} permission." - container_storage_location_unshared_html: "%{user} unshared box %{storage_location} with team %{team}." - container_storage_location_sharing_updated_html: "%{user} changed permission of shared box %{storage_location} with team %{team} to %{permission_level}." + container_storage_location_shared_html: "%{user} shared box %{storage_location} with workspace %{team} with %{permission_level} permission." + container_storage_location_unshared_html: "%{user} unshared box %{storage_location} with workspace %{team}." + container_storage_location_sharing_updated_html: "%{user} changed permission of shared box %{storage_location} with workspace %{team} to %{permission_level}." container_storage_location_duplicated_html: "%{user} duplicated box %{storage_location}." storage_location_repository_row_created_html: "%{user} assigned %{repository_row} to box %{storage_location} %{position}." storage_location_repository_row_deleted_html: "%{user} unassigned %{repository_row} from box %{storage_location} %{position}." @@ -349,9 +349,9 @@ en: form_access_granted_html: "%{user} granted access to %{user_target} with user role %{role} to form %{form}." form_access_changed_html: "%{user} changed %{user_target}'s role on form %{form} to %{role}." form_access_revoked_html: "%{user} removed %{user_target} with user role %{role} from form %{form}." - form_access_granted_all_team_members_html: "%{user} granted access to all team members of %{team} team with user role %{role} to form template %{form}." + form_access_granted_all_team_members_html: "%{user} granted access to all workspace members of %{team} workspace with user role %{role} to form template %{form}." form_access_changed_all_team_members_html: "%{user} changed %{team}'s role on form template %{form} to %{role}." - form_access_revoked_all_team_members_html: "%{user} removed %{team} team members with user role %{role} from form template %{form}." + form_access_revoked_all_team_members_html: "%{user} removed %{team} workspace members with user role %{role} from form template %{form}." form_created_html: "%{user} created form %{form} in Form templates." form_archived_html: "%{user} archived form %{form} in Form templates." form_restored_html: "%{user} restored form %{form} from archive in Form templates." @@ -392,14 +392,44 @@ en: remove_head_of_project_html: "%{user} removed user %{user_target} as head of project %{project}." task_steps_loaded_from_template_html: "%{user} added %{count} steps from template %{protocol} to task %{my_module}." protocol_steps_loaded_from_template_html: "%{user} added %{count} steps from template %{protocol}." + create_user_group_html: "%{user} created group %{user_group} in workspace %{team}." + update_user_group_html: "%{user} updated group %{user_group} in workspace %{team}." + delete_user_group_html: "%{user} deleted group %{user_group} in workspace %{team}." + add_group_user_member_html: "%{user} added %{user_target} to group %{user_group} in workspace %{team}." + remove_group_user_member_html: "%{user} removed %{user_target} from group %{user_group} in workspace %{team}." + form_access_granted_user_group_html: "%{user} granted access to %{user_group} with user role %{role} to form template %{form}." + form_access_changed_user_group_html: "%{user} changed %{user_group}'s role on form template %{form} to %{role}." + form_access_revoked_user_group_html: "%{user} removed group %{user_group} with user role %{role} from form template %{form}." + protocol_template_access_granted_user_group_html: "%{user} granted access to %{user_group} with user role %{role} to protocol template %{protocol}." + protocol_template_access_changed_user_group_html: "%{user} changed %{user_group}'s role on protocol template %{protocol} to %{role}." + protocol_template_access_revoked_user_group_html: "%{user} removed group %{user_group} with user role %{role} from protocol template %{protocol}." + project_access_granted_user_group_html: "%{user} granted access to %{user_group} with user role %{role} to project %{project}." + project_access_changed_user_group_html: "%{user} changed %{user_group}'s role on project %{project} to %{role}." + project_access_revoked_user_group_html: "%{user} removed group %{user_group} with user role %{role} from project %{project}." + experiment_access_changed_user_group_html: "%{user} changed %{user_group}'s role on experiment %{experiment} to %{role}." + my_module_access_changed_user_group_html: "%{user} changed %{user_group}'s role on task %{my_module} to %{role}." + step_and_result_linked_html: "%{user} linked result %{result} and step %{position} %{step} on task %{my_module}." + step_and_result_unlinked_html: "%{user} unlinked result %{result} and step %{position} %{step} on task %{my_module}." step_and_result_linked_html: "%{user} linked result %{result} and step %{step_position} %{step} on task %{my_module}." step_and_result_unlinked_html: "%{user} unlinked result %{result} and step %{step_position} %{step} on task %{my_module}." + repository_access_granted_html: "%{user} granted access to %{user_target} with user role %{role} to inventory %{repository}." + repository_access_changed_html: "%{user} changed %{user_target}’s role on inventory %{repository} to %{role}." + repository_access_revoked_html: "%{user} removed %{user_target} with user role %{role} from inventory %{repository}." + repository_access_granted_all_team_members_html: "%{user} granted access to all workspace members of %{team} workspace with user role %{role} to inventory %{repository}." + repository_access_changed_all_team_members_html: "%{user} changed %{team}’s role on inventory %{repository} to %{role}." + repository_access_revoked_all_team_members_html: "%{user} removed %{team} workspace members with user role %{role} from inventory %{repository}." + repository_access_granted_user_group_html: "%{user} granted access to %{user_group} with user role %{role} to inventory %{repository}." + repository_access_changed_user_group_html: "%{user} changed %{user_group}'s role on inventory %{repository} to %{role}." + repository_access_revoked_user_group_html: "%{user} removed group %{user_group} with user role %{role} from inventory %{repository}." + experiment_access_changed_all_team_members_html: "%{user} changed %{team}'s role on experiment %{experiment} to %{role}." + my_module_access_changed_all_team_members_html: "%{user} changed %{team}'s role on task %{my_module} to %{role}." + activity_name: create_project: "Project created" edit_project: "Project edited" change_project_visibility: "Project visibility changed (obsolete)" - project_grant_access_to_all_team_members: "Grant access to all team members" - project_remove_access_from_all_team_members: "Remove access from all team members" + project_grant_access_to_all_team_members: "Grant access to all workspace members" + project_remove_access_from_all_team_members: "Remove access from all workspace members" archive_project: "Project archived" restore_project: "Project restored" add_comment_to_project: "Project comment added" @@ -524,10 +554,10 @@ en: copy_protocol_in_repository: "Protocol duplicated" import_protocol_in_repository: "Protocol imported from file" export_protocol_in_repository: "Protocol exported" - invite_user_to_team: "User invited to team" - remove_user_from_team: "User removed from team" - change_users_role_on_team: "Users role changed on team" - user_leave_team: "User left team" + invite_user_to_team: "User invited to workspace" + remove_user_from_team: "User removed from workspace" + change_users_role_on_team: "Users role changed on workspace" + user_leave_team: "User left workspace" export_projects: "Projects exported" export_inventory_items: "Inventory items exported" create_tag: "Tag created" @@ -625,10 +655,10 @@ en: protocol_template_access_changed: "User role changed on a protocol" protocol_template_access_revoked: "User removed from a protocol" task_protocol_save_to_template: "Save as new protocol template" - protocol_template_access_granted_all_team_members: "Grant access to all team members" - protocol_template_access_changed_all_team_members: "Change role of all team members" - protocol_template_access_revoked_all_team_members: "Remove access from all team members" - project_access_changed_all_team_members: "Change role of all team members" + protocol_template_access_granted_all_team_members: "Grant access to all workspace members" + protocol_template_access_changed_all_team_members: "Change role of all workspace members" + protocol_template_access_revoked_all_team_members: "Remove access from all workspace members" + project_access_changed_all_team_members: "Change role of all workspace members" team_sharing_tasks_enabled: "Sharing tasks enabled" team_sharing_tasks_disabled: "Sharing tasks disabled" task_link_sharing_enabled: "Task link sharing enabled" @@ -700,9 +730,9 @@ en: form_access_granted: "User granted access to a form" form_access_changed: "User role changed on a form" form_access_revoked: "User removed from a form" - form_access_granted_all_team_members: "Grant access to all team members" - form_access_changed_all_team_members: "Change role of all team members" - form_access_revoked_all_team_members: "Remove access from all team members" + form_access_granted_all_team_members: "Grant access to all workspace members" + form_access_changed_all_team_members: "Change role of all workspace members" + form_access_revoked_all_team_members: "Remove access from all workspace members" form_created: "Form created" form_archived: "Form archived" form_restored: "Form restored from archive" @@ -743,8 +773,35 @@ en: remove_head_of_project: "Head of project removed" task_steps_loaded_from_template: "Task step loaded from template" protocol_steps_loaded_from_template: "Step loaded from template" + create_user_group: "Group created" + update_user_group: "Group updated" + delete_user_group: "Group deleted" + add_group_user_member: "Group user added" + remove_group_user_member: "Group user removed" + form_access_granted_user_group: "Grant access to group" + form_access_changed_user_group: "Change role of group" + form_access_revoked_user_group: "Remove access to group" + protocol_template_access_granted_user_group: "Grant access to group" + protocol_template_access_changed_user_group: "Change role of group" + protocol_template_access_revoked_user_group: "Remove access to group" + project_access_granted_user_group: "Grant access to group" + project_access_changed_user_group: "Change role of group" + project_access_revoked_user_group: "Remove access to group" + experiment_access_changed_user_group: "Change role of group" + my_module_access_changed_user_group: "Change role of group" step_and_result_linked: "Task Result and Task protocol step linked" step_and_result_unlinked: "Task Result and Task protocol step unlinked" + repository_access_granted: "User granted access to inventory" + repository_access_changed: "User role changed on inventory" + repository_access_revoked: "User removed from inventory" + repository_access_granted_all_team_members: "Grant access to all workspace members" + repository_access_changed_all_team_members: "Change role of all workspace members" + repository_access_revoked_all_team_members: "Remove access from all workspace members" + repository_access_granted_user_group: "Grant access to group" + repository_access_changed_user_group: "Change role of group" + repository_access_revoked_user_group: "Remove access to group" + experiment_access_changed_all_team_members: "Change role of all workspace members" + my_module_access_changed_all_team_members: "Change role of all workspace members" activity_group: projects: "Projects" task_results: "Task results" @@ -755,7 +812,7 @@ en: reports: "Reports" inventories: "Inventories" protocol_repository: "Protocol templates" - team: "Team" + team: "Workspace" exports: "Exports" label_templates: "Label templates" storage_locations: "Inventory locations" diff --git a/config/routes.rb b/config/routes.rb index 966de1770..ad826f354 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -149,6 +149,24 @@ Rails.application.routes.draw do resource :user_settings, only: %i(show update) resources :teams, only: [] do + resources :user_groups, only: %i(index create update destroy show) do + resources :user_group_memberships, only: %i(index create update) do + collection do + delete :destroy_multiple + post :actions_toolbar + end + end + collection do + get :unassigned_users + post :actions_toolbar + get :users + end + end + + member do + get :members + end + collection do post :switch end @@ -236,6 +254,7 @@ Rails.application.routes.draw do collection do get :visible_users get :visible_teams + get :current_team_users end member do @@ -318,20 +337,29 @@ Rails.application.routes.draw do end namespace :access_permissions do - resources :projects, defaults: { format: 'json' } do - put :update_default_public_user_role, on: :member + %i(projects protocols forms repositories).each do |resource| + resources resource do + member do + get :show_user_group_assignments + get :unassigned_user_groups + get :user_roles + end + end end - resources :protocols, defaults: { format: 'json' } do - put :update_default_public_user_role, on: :member + resources :experiments, only: %i(show update edit) do + member do + get :user_roles + get :show_user_group_assignments + end end - resources :forms, defaults: { format: 'json' } do - put :update_default_public_user_role, on: :member + resources :my_modules, only: %i(show update edit) do + member do + get :user_roles + get :show_user_group_assignments + end end - - resources :experiments, only: %i(show update edit) - resources :my_modules, only: %i(show update edit) end namespace :navigator do @@ -400,7 +428,6 @@ Rails.application.routes.draw do post 'archive_group' post 'restore_group' post 'actions_toolbar' - get :user_roles get :head_of_project_users_list end end @@ -741,7 +768,6 @@ Rails.application.routes.draw do get 'export', to: 'protocols#export' get 'protocolsio', to: 'protocols#protocolsio_index' post 'actions_toolbar', to: 'protocols#actions_toolbar' - get 'user_roles', to: 'protocols#user_roles' end end @@ -826,7 +852,6 @@ Rails.application.routes.draw do end collection do - get :sidebar get 'available_rows', to: 'repository_rows#available_rows', defaults: { format: 'json' } get 'export_repository_stock_items_modal' get :rows_to_print, to: 'repository_rows#rows_to_print' @@ -916,7 +941,6 @@ Rails.application.routes.draw do post :actions_toolbar post :archive post :restore - get :user_roles get :published_forms get :latest_attached_forms end diff --git a/config/routes/users.rb b/config/routes/users.rb index 02f0453a1..37b49b135 100644 --- a/config/routes/users.rb +++ b/config/routes/users.rb @@ -3,9 +3,6 @@ get '/current_user_info', to: 'users/users#current_user_info' namespace :users do get '/sign_out_user', to: 'users#sign_out_user' - delete '/remove_user', to: 'user_teams#remove_user' - delete '/leave_team', to: 'user_teams#leave_team' - put '/update_role', to: 'user_teams#update_role' get '/profile_info', to: 'users#profile_info' get '/preferences_info', to: 'users#preferences_info' get '/statistics_info', to: 'users#statistics_info' diff --git a/config/webpack/webpack.config.js b/config/webpack/webpack.config.js index 1803a6414..2ef9dc729 100644 --- a/config/webpack/webpack.config.js +++ b/config/webpack/webpack.config.js @@ -74,7 +74,9 @@ const entryList = { vue_design_system_inputs: './app/javascript/packs/vue/design_system/inputs.js', vue_design_system_table: './app/javascript/packs/vue/design_system/table.js', vue_favorites_widget: './app/javascript/packs/vue/favorites_widget.js', - vue_experiment_description_modal: './app/javascript/packs/vue/experiment_description_modal.js' + vue_experiment_description_modal: './app/javascript/packs/vue/experiment_description_modal.js', + vue_user_groups_table: './app/javascript/packs/vue/user_groups_table.js', + vue_user_groups_show: './app/javascript/packs/vue/user_groups_show.js' }; // Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949 diff --git a/db/migrate/20150713070738_create_user_organizations.rb b/db/migrate/20150713070738_create_user_organizations.rb deleted file mode 100644 index 04dc7b3ea..000000000 --- a/db/migrate/20150713070738_create_user_organizations.rb +++ /dev/null @@ -1,13 +0,0 @@ -class CreateUserOrganizations < ActiveRecord::Migration[4.2] - def change - create_table :user_teams do |t| - t.column :role, :integer, null: false, default: 1 - t.integer :user_id, null: false - t.integer :team_id, null: false - - t.timestamps null: false - end - add_foreign_key :user_teams, :users - add_foreign_key :user_teams, :teams - end -end diff --git a/db/migrate/20150713072417_create_user_projects.rb b/db/migrate/20150713072417_create_user_projects.rb deleted file mode 100644 index 4823b2520..000000000 --- a/db/migrate/20150713072417_create_user_projects.rb +++ /dev/null @@ -1,17 +0,0 @@ -class CreateUserProjects < ActiveRecord::Migration[4.2] - def change - create_table :user_projects do |t| - t.column :role, :integer, default: 0 - - # Comment in spite of SQLite - # t.integer :permissions, array: true, default: [] - - t.integer :user_id, null: false - t.integer :project_id, null: false - - t.timestamps null: false - end - add_foreign_key :user_projects, :users - add_foreign_key :user_projects, :projects - end -end diff --git a/db/migrate/20151005122041_add_created_by_to_assets.rb b/db/migrate/20151005122041_add_created_by_to_assets.rb index a90c4c992..33088c7a1 100644 --- a/db/migrate/20151005122041_add_created_by_to_assets.rb +++ b/db/migrate/20151005122041_add_created_by_to_assets.rb @@ -27,7 +27,7 @@ class AddCreatedByToAssets < ActiveRecord::Migration[4.2] add_column table_name, :restored_on, :datetime end - %i(sample_my_modules user_my_modules user_teams user_projects).each do |table_name| + %i(sample_my_modules user_my_modules).each do |table_name| add_column table_name, :assigned_by_id, :integer add_index table_name, :assigned_by_id end diff --git a/db/migrate/20151021085335_add_search_query_indexes.rb b/db/migrate/20151021085335_add_search_query_indexes.rb index 1693a44ed..c34fc256d 100644 --- a/db/migrate/20151021085335_add_search_query_indexes.rb +++ b/db/migrate/20151021085335_add_search_query_indexes.rb @@ -4,10 +4,6 @@ include DatabaseHelper class AddSearchQueryIndexes < ActiveRecord::Migration[4.2] def up add_index :projects, :team_id - add_index :user_teams, :user_id - add_index :user_teams, :team_id - add_index :user_projects, :user_id - add_index :user_projects, :project_id add_index :tags, :project_id # Add GIST trigram indexes onto columns that check for @@ -26,10 +22,6 @@ class AddSearchQueryIndexes < ActiveRecord::Migration[4.2] def down remove_index :projects, :team_id - remove_index :user_teams, :user_id - remove_index :user_teams, :team_id - remove_index :user_projects, :user_id - remove_index :user_projects, :project_id remove_index :tags, :project_id remove_index :projects, :name diff --git a/db/migrate/20151215103642_add_foreign_keys_to_tables.rb b/db/migrate/20151215103642_add_foreign_keys_to_tables.rb index a2dc51a0f..f7c134fac 100644 --- a/db/migrate/20151215103642_add_foreign_keys_to_tables.rb +++ b/db/migrate/20151215103642_add_foreign_keys_to_tables.rb @@ -22,7 +22,7 @@ class AddForeignKeysToTables < ActiveRecord::Migration[4.2] add_foreign_key table_name, :users, column: :restored_by_id end - %i(user_my_modules user_teams user_projects).each do |table_name| + %i(user_my_modules).each do |table_name| add_foreign_key table_name, :users, column: :assigned_by_id end end diff --git a/db/migrate/20160722082700_add_experiment_level.rb b/db/migrate/20160722082700_add_experiment_level.rb index 1e13c55ba..4a1a5af9d 100644 --- a/db/migrate/20160722082700_add_experiment_level.rb +++ b/db/migrate/20160722082700_add_experiment_level.rb @@ -29,35 +29,6 @@ class AddExperimentLevel < ActiveRecord::Migration[4.2] add_column :my_modules, :experiment_id, :integer, default: 0 add_column :my_module_groups, :experiment_id, :integer, default: 0 - # Iterate through all projects - Project.find_each do |project| - my_modules = MyModule.where('project_id = ?', project.id) - my_module_groups = MyModuleGroup.where('project_id = ?', project.id) - - if my_modules.count > 0 - # Create an experiment - owner = project.user_projects.where(role: 0).first.user - exp = Experiment.create( - name: 'Test experiment', - project: project, - created_by: owner, - last_modified_by: owner - ) - - # Assign all modules onto new experiment - my_modules.find_each do |mm| - mm.update_column(:experiment_id, exp.id) - end - - if my_module_groups.count > 0 - # Also assign all module groups onto new experiment - my_module_groups.find_each do |mmg| - mmg.update_column(:experiment_id, exp.id) - end - end - end - end - change_column_null :my_modules, :experiment_id, false remove_column :my_modules, :project_id add_foreign_key :my_modules, :experiments, column: :experiment_id @@ -73,20 +44,6 @@ class AddExperimentLevel < ActiveRecord::Migration[4.2] add_column :my_modules, :project_id, :integer, default: 0 add_column :my_module_groups, :project_id, :integer, default: 0 - # Update reference to projects from modules - MyModule.find_each do |mm| - exp = Experiment.where('id = ?', mm[:experiment_id]).take - project = Project.where('id = ?', exp[:project_id]).take - mm.update_column(:project_id, project.id) - end - - # Update reference to projects from module groups - MyModuleGroup.find_each do |mmg| - exp = Experiment.where('id = ?', mmg[:experiment_id]).take - project = Project.where('id = ?', exp[:project_id]).take - mmg.update_column(:project_id, project.id) - end - change_column_null :my_modules, :project_id, false remove_index :my_modules, column: :experiment_id remove_foreign_key :my_modules, column: :experiment_id diff --git a/db/migrate/20170124135736_rename_organization_to_team.rb b/db/migrate/20170124135736_rename_organization_to_team.rb deleted file mode 100644 index c01c77c52..000000000 --- a/db/migrate/20170124135736_rename_organization_to_team.rb +++ /dev/null @@ -1,43 +0,0 @@ -class RenameOrganizationToTeam < ActiveRecord::Migration[4.2] - def up - unless ActiveRecord::Base.connection.table_exists?('organizations') && - ActiveRecord::Base.connection.table_exists?('user_organizations') - return - end - rename_table :organizations, :teams - rename_table :user_organizations, :user_teams - - rename_column :custom_fields, :organization_id, :team_id - rename_column :logs, :organization_id, :team_id - rename_column :projects, :organization_id, :team_id - rename_column :protocol_keywords, :organization_id, :team_id - rename_column :protocols, :organization_id, :team_id - rename_column :sample_groups, :organization_id, :team_id - rename_column :sample_types, :organization_id, :team_id - rename_column :samples, :organization_id, :team_id - rename_column :samples_tables, :organization_id, :team_id - rename_column :user_teams, :organization_id, :team_id - rename_column :users, :current_organization_id, :current_team_id - end - - def down - unless ActiveRecord::Base.connection.table_exists?('teams') && - ActiveRecord::Base.connection.table_exists?('user_teams') - return - end - rename_table :teams, :organizations - rename_table :user_teams, :user_organizations - - rename_column :custom_fields, :team_id, :organization_id - rename_column :logs, :team_id, :organization_id - rename_column :projects, :team_id, :organization_id - rename_column :protocol_keywords, :team_id, :organization_id - rename_column :protocols, :team_id, :organization_id - rename_column :sample_groups, :team_id, :organization_id - rename_column :sample_types, :team_id, :organization_id - rename_column :samples, :team_id, :organization_id - rename_column :samples_tables, :team_id, :organization_id - rename_column :user_organizations, :team_id, :organization_id - rename_column :users, :current_team_id, :current_organization_id - end -end diff --git a/db/migrate/20170404150845_remove_default_user_project_role_value.rb b/db/migrate/20170404150845_remove_default_user_project_role_value.rb deleted file mode 100644 index 4a13d353b..000000000 --- a/db/migrate/20170404150845_remove_default_user_project_role_value.rb +++ /dev/null @@ -1,9 +0,0 @@ -class RemoveDefaultUserProjectRoleValue < ActiveRecord::Migration[4.2] - def up - change_column_default(:user_projects, :role, nil) - end - - def down - change_column_default(:user_projects, :role, 0) - end -end diff --git a/db/migrate/20171026090804_create_datatables_teams.rb b/db/migrate/20171026090804_create_datatables_teams.rb deleted file mode 100644 index 1aa9b0f19..000000000 --- a/db/migrate/20171026090804_create_datatables_teams.rb +++ /dev/null @@ -1,5 +0,0 @@ -class CreateDatatablesTeams < ActiveRecord::Migration[5.0] - def change - create_view :datatables_teams - end -end diff --git a/db/migrate/20190117155006_change_indices_from_int_to_bigint.rb b/db/migrate/20190117155006_change_indices_from_int_to_bigint.rb index b9a67790e..2fb740e57 100644 --- a/db/migrate/20190117155006_change_indices_from_int_to_bigint.rb +++ b/db/migrate/20190117155006_change_indices_from_int_to_bigint.rb @@ -2,8 +2,6 @@ class ChangeIndicesFromIntToBigint < ActiveRecord::Migration[5.1] def up - drop_view :datatables_teams - change_column :activities, :id, :bigint change_column :assets, :id, :bigint change_column :asset_text_data, :id, :bigint @@ -50,9 +48,7 @@ class ChangeIndicesFromIntToBigint < ActiveRecord::Migration[5.1] change_column :user_identities, :id, :bigint change_column :user_my_modules, :id, :bigint change_column :user_notifications, :id, :bigint - change_column :user_projects, :id, :bigint change_column :users, :id, :bigint - change_column :user_teams, :id, :bigint change_column :wopi_actions, :id, :bigint change_column :wopi_apps, :id, :bigint change_column :wopi_discoveries, :id, :bigint @@ -174,18 +170,10 @@ class ChangeIndicesFromIntToBigint < ActiveRecord::Migration[5.1] change_column :user_my_modules, :user_id, :bigint change_column :user_notifications, :user_id, :bigint change_column :user_notifications, :notification_id, :bigint - change_column :user_projects, :assigned_by_id, :bigint - change_column :user_projects, :user_id, :bigint - change_column :user_projects, :project_id, :bigint change_column :users, :current_team_id, :bigint - change_column :user_teams, :user_id, :bigint - change_column :user_teams, :team_id, :bigint - change_column :user_teams, :assigned_by_id, :bigint change_column :wopi_actions, :wopi_app_id, :bigint change_column :wopi_apps, :wopi_discovery_id, :bigint change_column :zip_exports, :user_id, :bigint - - create_view :datatables_teams end def down diff --git a/db/migrate/20190227125306_add_unique_index_on_user_teams.rb b/db/migrate/20190227125306_add_unique_index_on_user_teams.rb deleted file mode 100644 index 095cc0e8f..000000000 --- a/db/migrate/20190227125306_add_unique_index_on_user_teams.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -class AddUniqueIndexOnUserTeams < ActiveRecord::Migration[5.1] - def up - # firstly delete the duplicates - execute 'WITH uniq AS - (SELECT DISTINCT ON (user_id, team_id) * FROM user_teams) - DELETE FROM user_teams WHERE user_teams.id NOT IN - (SELECT id FROM uniq)' - add_index :user_teams, %i(user_id team_id), unique: true - end - - def down - remove_index :user_teams, column: %i(user_id team_id) - end -end diff --git a/db/migrate/20190227125352_add_unique_index_on_user_projects.rb b/db/migrate/20190227125352_add_unique_index_on_user_projects.rb deleted file mode 100644 index 4aad180c4..000000000 --- a/db/migrate/20190227125352_add_unique_index_on_user_projects.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -class AddUniqueIndexOnUserProjects < ActiveRecord::Migration[5.1] - def up - # firstly delete the duplicates - execute 'WITH uniq AS - (SELECT DISTINCT ON (user_id, project_id) * FROM user_projects) - DELETE FROM user_projects WHERE user_projects.id NOT IN - (SELECT id FROM uniq)' - add_index :user_projects, %i(user_id project_id), unique: true - end - - def down - remove_index :user_projects, columns: %i(user_id project_id) - end -end diff --git a/db/migrate/20210222123823_migrate_to_new_user_roles.rb b/db/migrate/20210222123823_migrate_to_new_user_roles.rb index 862b0c36c..7cf20a8bf 100644 --- a/db/migrate/20210222123823_migrate_to_new_user_roles.rb +++ b/db/migrate/20210222123823_migrate_to_new_user_roles.rb @@ -13,71 +13,11 @@ class MigrateToNewUserRoles < ActiveRecord::Migration[6.1] viewer_role = UserRole.viewer_role viewer_role.save! - create_user_assignments(UserProject.owner, owner_role) - create_user_assignments(UserProject.normal_user, normal_user_role) - create_user_assignments(UserProject.technician, technician_role) - create_user_assignments(UserProject.viewer, viewer_role) - create_public_project_assignments end dir.down do UserAssignment.joins(:user_role).where(user_role: { predefined: true }).delete_all - Project.where(default_public_user_role: UserRole.where(predefined: true)) - .update_all(default_public_user_role_id: nil) UserRole.where(predefined: true).delete_all end end end - - private - - def new_user_assignment(user, assignable, user_role, assigned) - UserAssignment.new( - user: user, - assignable: assignable, - assigned: assigned, - user_role: user_role - ) - end - - def create_public_project_assignments - viewer_role = UserRole.find_by(name: UserRole.viewer_role.name) - - Team.preload(:users).find_each do |team| - public_projects = team.projects.where(visibility: 'visible') - public_projects.preload(:team, experiments: :my_modules).find_each(batch_size: 10) do |project| - project.update!(default_public_user_role: viewer_role) - - user_assignments = [] - already_assigned_user_ids = project.user_assignments.pluck(:user_id) - unassigned_users = team.users.reject { |u| already_assigned_user_ids.include?(u.id) } - - unassigned_users.each do |user| - user_assignments << new_user_assignment(user, project, viewer_role, :automatically) - project.experiments.each do |experiment| - user_assignments << new_user_assignment(user, experiment, viewer_role, :automatically) - experiment.my_modules.each do |my_module| - user_assignments << new_user_assignment(user, my_module, viewer_role, :automatically) - end - end - end - UserAssignment.import(user_assignments) - end - end - end - - def create_user_assignments(user_projects, user_role) - user_projects.includes(:user, :project).find_in_batches(batch_size: 100) do |user_project_batch| - user_assignments = [] - user_project_batch.each do |user_project| - user_assignments << new_user_assignment(user_project.user, user_project.project, user_role, :manually) - user_project.project.experiments.preload(:my_modules).each do |experiment| - user_assignments << new_user_assignment(user_project.user, experiment, user_role, :automatically) - experiment.my_modules.each do |my_module| - user_assignments << new_user_assignment(user_project.user, my_module, user_role, :automatically) - end - end - end - UserAssignment.import(user_assignments) - end - end end diff --git a/db/migrate/20230720070830_drop_datatables_teams_view.rb b/db/migrate/20230720070830_drop_datatables_teams_view.rb deleted file mode 100755 index 292180774..000000000 --- a/db/migrate/20230720070830_drop_datatables_teams_view.rb +++ /dev/null @@ -1,5 +0,0 @@ -class DropDatatablesTeamsView < ActiveRecord::Migration[7.0] - def change - drop_view :datatables_teams - end -end diff --git a/db/migrate/20250609095009_add_user_groups.rb b/db/migrate/20250609095009_add_user_groups.rb new file mode 100644 index 000000000..2d29f776d --- /dev/null +++ b/db/migrate/20250609095009_add_user_groups.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +class AddUserGroups < ActiveRecord::Migration[7.0] + def change + create_table :user_groups do |t| + t.string :name + t.references :team, foreign_key: true, null: false + t.references :created_by, foreign_key: { to_table: :users }, null: false + t.references :last_modified_by, foreign_key: { to_table: :users }, null: false + + t.index 'LOWER(name), team_id', unique: true, name: 'index_user_groups_on_lower_name_and_team_id' + + t.timestamps + end + + create_table :user_group_memberships do |t| + t.references :user_group, foreign_key: true, null: false + t.references :user, foreign_key: true, null: false + t.references :created_by, foreign_key: { to_table: :users } + + t.index %i(user_group_id user_id), unique: true + + t.timestamps + end + + create_table :user_group_assignments do |t| + t.references :team, foreign_key: true, null: false + t.references :assignable, polymorphic: true, null: false + t.references :user_group, foreign_key: true, null: false + t.references :user_role, foreign_key: true, null: false + t.references :assigned_by, foreign_key: { to_table: :users } + t.integer :assigned, null: false, default: 0 + + t.index %i(user_group_id assignable_type assignable_id team_id), unique: true, name: 'index_user_group_assignments_on_unique_assignable_in_team' + + t.timestamps + end + + create_table :team_assignments do |t| + t.references :team, foreign_key: true, null: false + t.references :assignable, polymorphic: true, null: false + t.references :user_role, foreign_key: true, null: false + t.references :assigned_by, foreign_key: { to_table: :users } + t.integer :assigned, null: false, default: 0 + + t.index %i(assignable_type assignable_id team_id), unique: true, name: 'index_team_assignments_on_unique_assignable_in_team' + + t.timestamps + end + end +end diff --git a/db/migrate/20250707080048_move_every_one_else_assignment_to_team_assignments.rb b/db/migrate/20250707080048_move_every_one_else_assignment_to_team_assignments.rb new file mode 100644 index 000000000..2f9bf367d --- /dev/null +++ b/db/migrate/20250707080048_move_every_one_else_assignment_to_team_assignments.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +class MoveEveryOneElseAssignmentToTeamAssignments < ActiveRecord::Migration[7.2] + def up + # rubocop:disable Metrics/BlockLength + ActiveRecord::Base.no_touching do + viewer_role = UserRole.find_predefined_viewer_role + normal_user_role = UserRole.find_predefined_normal_user_role + + # Shared repositories with read permission + TeamSharedObject.where(permission_level: Extends::SHARED_OBJECTS_PERMISSION_LEVELS[:shared_read]) + .find_each do |shared_obj| + next if shared_obj.shared_repository.blank? + + shared_obj.shared_repository.user_assignments.where(team_id: shared_obj.team_id).delete_all + shared_obj.shared_repository.team_assignments.find_or_create_by!(team_id: shared_obj.team_id, user_role: viewer_role) + end + + # Shared repositories with write permission + TeamSharedObject.where(permission_level: Extends::SHARED_OBJECTS_PERMISSION_LEVELS[:shared_write]) + .find_each do |shared_obj| + next if shared_obj.shared_repository.blank? + + shared_obj.shared_repository.user_assignments.where(team_id: shared_obj.team_id).update_all(assigned: :manually) + + shared_obj.shared_repository.user_assignments.where(team_id: shared_obj.team_id, user_role: normal_user_role).delete_all + shared_obj.shared_repository.team_assignments.find_or_create_by!(team_id: shared_obj.team_id, user_role: normal_user_role) + end + + # Other repositories + Repository.find_each do |repository| + repository.user_assignments.where(team_id: repository.team_id).update_all(assigned: :manually) + + repository.user_assignments.where(team_id: repository.team_id, user_role: normal_user_role).delete_all + repository.team_assignments.find_or_create_by!(team_id: repository.team_id, user_role: normal_user_role) + end + + # global share + Repository.where(permission_level: %i(shared_read shared_write)).find_each do |repository| + Team.where.not(id: repository.team_id).find_each do |team| + next if repository.private_shared_with?(team) || repository.team_assignments.exists?(team: team) + + repository.user_assignments.where(team: team).delete_all + + if repository.permission_level == 'shared_read' + repository.team_assignments.find_or_create_by!(team_id: team.id, user_role: viewer_role) + else + repository.team_assignments.find_or_create_by!(team_id: team.id, user_role: normal_user_role) + team.user_assignments.where.not(user_role: normal_user_role).find_each do |user_assignment| + UserAssignment.create!( + assignable: repository, + team: team, + user: user_assignment.user, + user_role: user_assignment.user_role, + assigned: :manually + ) + end + end + end + end + + # Forms + Form.find_each do |form| + replace_automatic_user_assignments_with_team_assignment(record: form, team_id: form.team_id) + end + + # Protocols + Protocol.where(protocol_type: %i(in_repository_published_original in_repository_draft in_repository_published_version)) + .find_each do |protocol| + replace_automatic_user_assignments_with_team_assignment(record: protocol, team_id: protocol.team_id) + end + + # PET + Project.visible.preload(:experiments).find_in_batches(batch_size: 100) do |projects| + projects.each do |project| + project_automatic_user_assignments = project.user_assignments.where(assigned: :automatically) + next if project_automatic_user_assignments.blank? + + user_role = project_automatic_user_assignments.first.user_role + team_assignment_values = [] + + project.experiments.preload(:my_modules).each do |experiment| + experiment.my_modules.each do |my_module| + team_assignment_values << new_team_assignment(project.team_id, my_module, user_role) + my_module.automatic_user_assignments.where(user_id: project_automatic_user_assignments.select(:user_id)).delete_all + end + team_assignment_values << new_team_assignment(project.team_id, experiment, user_role) + experiment.automatic_user_assignments.where(user_id: project_automatic_user_assignments.select(:user_id)).delete_all + end + + team_assignment_values << new_team_assignment(project.team_id, project, user_role) + project_automatic_user_assignments.delete_all + + TeamAssignment.import(team_assignment_values, validate: false) + end + end + end + # rubocop:enable Metrics/BlockLength + end + + def down + ActiveRecord::Base.no_touching do + team_shared_read_objects = TeamSharedObject.where(permission_level: Extends::SHARED_OBJECTS_PERMISSION_LEVELS[:shared_read]) + + team_shared_read_objects.find_each do |team_shared_object| + next if team_shared_object.shared_repository.blank? + + # shared repositories with read permission + TeamAssignment.where(assignable: team_shared_object.shared_repository, team_id: team_shared_object.team_id).delete_all + + user_assignments = team_shared_object.team.user_assignments + user_assignments.find_each do |user_assignment| + UserAssignment.create!(assignable: team_shared_object.shared_repository, + team_id: team_shared_object.team_id, + user: user_assignment.user, + user_role: user_assignment.user_role) + end + end + + remove_team_assignments(TeamAssignment.where(assignable_type: 'RepositoryBase')) + remove_team_assignments(TeamAssignment.where(assignable_type: 'Form')) + remove_team_assignments(TeamAssignment.where(assignable_type: 'Protocol')) + remove_team_assignments(TeamAssignment.where(assignable_type: 'MyModule')) + remove_team_assignments(TeamAssignment.where(assignable_type: 'Experiment')) + remove_team_assignments(TeamAssignment.where(assignable_type: 'Project')) + end + end + + private + + def new_team_assignment(team_id, assignable, user_role) + TeamAssignment.new( + team_id: team_id, + assignable: assignable, + user_role: user_role + ) + end + + def replace_automatic_user_assignments_with_team_assignment(record:, team_id:) + scope = record.user_assignments.where(team_id: team_id, assigned: :automatically) + + return if scope.blank? + + record.team_assignments.create!(team_id: team_id, user_role: scope.first.user_role) + scope.delete_all + end + + def new_user_assignment(user, assignable, user_role, assigned, team) + UserAssignment.new( + user: user, + assignable: assignable, + assigned: assigned, + user_role: user_role, + team: team + ) + end + + def remove_team_assignments(data) + user_assignment_values = [] + ids_to_destroy = [] + + data.find_each do |obj| + user_assignments = obj.team.user_assignments.includes(:user).where.not(user_id: obj.assignable.user_assignments.select(:user_id)) + user_assignments.find_each do |user_assignment| + user_assignment_values << new_user_assignment(user_assignment.user, obj.assignable, obj.user_role, :automatically, obj.team) + end + + ids_to_destroy << obj.id + end + + TeamAssignment.where(id: ids_to_destroy).delete_all + UserAssignment.import(user_assignment_values, validate: false) + end +end diff --git a/db/migrate/20250709082818_remove_repository_column_management_permissions_for_normal_user_role.rb b/db/migrate/20250709082818_remove_repository_column_management_permissions_for_normal_user_role.rb new file mode 100644 index 000000000..ee6c782dc --- /dev/null +++ b/db/migrate/20250709082818_remove_repository_column_management_permissions_for_normal_user_role.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class RemoveRepositoryColumnManagementPermissionsForNormalUserRole < ActiveRecord::Migration[7.2] + REPOSITOY_COLUMN_MANAGE_PERMISSION = [ + RepositoryPermissions::COLUMNS_CREATE, + RepositoryPermissions::COLUMNS_UPDATE, + RepositoryPermissions::COLUMNS_DELETE + ].freeze + + def up + @normal_user_role = UserRole.find_predefined_normal_user_role + @normal_user_role.permissions = @normal_user_role.permissions - REPOSITOY_COLUMN_MANAGE_PERMISSION + @normal_user_role.save(validate: false) + end + + def down + @normal_user_role = UserRole.find_predefined_normal_user_role + @normal_user_role.permissions = @normal_user_role.permissions | REPOSITOY_COLUMN_MANAGE_PERMISSION + @normal_user_role.save(validate: false) + end +end diff --git a/db/migrate/20250717131344_update_repository_template_date_reminder_values.rb b/db/migrate/20250717131344_update_repository_template_date_reminder_values.rb new file mode 100644 index 000000000..3764ba985 --- /dev/null +++ b/db/migrate/20250717131344_update_repository_template_date_reminder_values.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class UpdateRepositoryTemplateDateReminderValues < ActiveRecord::Migration[7.2] + def change + return unless ENV['SCINOTE_REPOSITORY_TEMPLATES_ENABLED'] == 'true' + + RepositoryTemplate.where(name: I18n.t('repository_templates.equipment_template_name')).find_each do |repository_template| + repository_template.update!(column_definitions: RepositoryTemplate.equipment.column_definitions) + end + + RepositoryTemplate.where(name: I18n.t('repository_templates.chemicals_and_reagents_template_name')).find_each do |repository_template| + repository_template.update!(column_definitions: RepositoryTemplate.equipment.column_definitions) + end + end +end diff --git a/db/migrate/20250718132304_remove_user_teams_and_user_projects_tables.rb b/db/migrate/20250718132304_remove_user_teams_and_user_projects_tables.rb new file mode 100644 index 000000000..a00def616 --- /dev/null +++ b/db/migrate/20250718132304_remove_user_teams_and_user_projects_tables.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class RemoveUserTeamsAndUserProjectsTables < ActiveRecord::Migration[7.2] + def up + drop_table :user_projects, if_exists: true + drop_table :user_teams, if_exists: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 29bb4fcf2..46c874c8f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1288,6 +1288,21 @@ ActiveRecord::Schema[7.0].define(version: 2025_06_06_082935) do t.index ["project_id"], name: "index_tags_on_project_id" end + create_table "team_assignments", force: :cascade do |t| + t.bigint "team_id", null: false + t.string "assignable_type", null: false + t.bigint "assignable_id", null: false + t.bigint "user_role_id", null: false + t.bigint "assigned_by_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["assignable_type", "assignable_id", "team_id"], name: "index_team_assignments_on_unique_assignable_in_team", unique: true + t.index ["assignable_type", "assignable_id"], name: "index_team_assignments_on_assignable" + t.index ["assigned_by_id"], name: "index_team_assignments_on_assigned_by_id" + t.index ["team_id"], name: "index_team_assignments_on_team_id" + t.index ["user_role_id"], name: "index_team_assignments_on_user_role_id" + end + create_table "team_shared_objects", force: :cascade do |t| t.bigint "team_id" t.bigint "shared_object_id" @@ -1354,6 +1369,48 @@ ActiveRecord::Schema[7.0].define(version: 2025_06_06_082935) do t.index ["user_role_id"], name: "index_user_assignments_on_user_role_id" end + create_table "user_group_assignments", force: :cascade do |t| + t.bigint "team_id", null: false + t.string "assignable_type", null: false + t.bigint "assignable_id", null: false + t.bigint "user_group_id", null: false + t.bigint "user_role_id", null: false + t.bigint "assigned_by_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["assignable_type", "assignable_id"], name: "index_user_group_assignments_on_assignable" + t.index ["assigned_by_id"], name: "index_user_group_assignments_on_assigned_by_id" + t.index ["team_id"], name: "index_user_group_assignments_on_team_id" + t.index ["user_group_id", "assignable_type", "assignable_id", "team_id"], name: "index_user_group_assignments_on_unique_assignable_in_team", unique: true + t.index ["user_group_id"], name: "index_user_group_assignments_on_user_group_id" + t.index ["user_role_id"], name: "index_user_group_assignments_on_user_role_id" + end + + create_table "user_group_memberships", force: :cascade do |t| + t.bigint "user_group_id", null: false + t.bigint "user_id", null: false + t.bigint "created_by_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["created_by_id"], name: "index_user_group_memberships_on_created_by_id" + t.index ["user_group_id", "user_id"], name: "index_user_group_memberships_on_user_group_id_and_user_id", unique: true + t.index ["user_group_id"], name: "index_user_group_memberships_on_user_group_id" + t.index ["user_id"], name: "index_user_group_memberships_on_user_id" + end + + create_table "user_groups", force: :cascade do |t| + t.string "name" + t.bigint "team_id", null: false + t.bigint "created_by_id", null: false + t.bigint "last_modified_by_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index "lower((name)::text), team_id", name: "index_user_groups_on_lower_name_and_team_id", unique: true + t.index ["created_by_id"], name: "index_user_groups_on_created_by_id" + t.index ["last_modified_by_id"], name: "index_user_groups_on_last_modified_by_id" + t.index ["team_id"], name: "index_user_groups_on_team_id" + end + create_table "user_identities", force: :cascade do |t| t.integer "user_id" t.string "provider", null: false @@ -1376,19 +1433,6 @@ ActiveRecord::Schema[7.0].define(version: 2025_06_06_082935) do t.index ["user_id"], name: "index_user_my_modules_on_user_id" end - create_table "user_projects", force: :cascade do |t| - t.integer "role" - t.bigint "user_id", null: false - t.bigint "project_id", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.bigint "assigned_by_id" - t.index ["assigned_by_id"], name: "index_user_projects_on_assigned_by_id" - t.index ["project_id"], name: "index_user_projects_on_project_id" - t.index ["user_id", "project_id"], name: "index_user_projects_on_user_id_and_project_id", unique: true - t.index ["user_id"], name: "index_user_projects_on_user_id" - end - create_table "user_roles", force: :cascade do |t| t.string "name" t.boolean "predefined", default: false @@ -1401,19 +1445,6 @@ ActiveRecord::Schema[7.0].define(version: 2025_06_06_082935) do t.index ["last_modified_by_id"], name: "index_user_roles_on_last_modified_by_id" end - create_table "user_teams", force: :cascade do |t| - t.integer "role", default: 1, null: false - t.bigint "user_id", null: false - t.bigint "team_id", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.bigint "assigned_by_id" - t.index ["assigned_by_id"], name: "index_user_teams_on_assigned_by_id" - t.index ["team_id"], name: "index_user_teams_on_team_id" - t.index ["user_id", "team_id"], name: "index_user_teams_on_user_id_and_team_id", unique: true - t.index ["user_id"], name: "index_user_teams_on_user_id" - end - create_table "users", force: :cascade do |t| t.string "full_name", null: false t.string "initials", null: false @@ -1716,6 +1747,9 @@ ActiveRecord::Schema[7.0].define(version: 2025_06_06_082935) do add_foreign_key "tags", "projects" add_foreign_key "tags", "users", column: "created_by_id" add_foreign_key "tags", "users", column: "last_modified_by_id" + add_foreign_key "team_assignments", "teams" + add_foreign_key "team_assignments", "user_roles" + add_foreign_key "team_assignments", "users", column: "assigned_by_id" add_foreign_key "team_shared_objects", "teams" add_foreign_key "teams", "users", column: "created_by_id" add_foreign_key "teams", "users", column: "last_modified_by_id" @@ -1724,17 +1758,21 @@ ActiveRecord::Schema[7.0].define(version: 2025_06_06_082935) do add_foreign_key "user_assignments", "user_roles" add_foreign_key "user_assignments", "users" add_foreign_key "user_assignments", "users", column: "assigned_by_id" + add_foreign_key "user_group_assignments", "teams" + add_foreign_key "user_group_assignments", "user_groups" + add_foreign_key "user_group_assignments", "user_roles" + add_foreign_key "user_group_assignments", "users", column: "assigned_by_id" + add_foreign_key "user_group_memberships", "user_groups" + add_foreign_key "user_group_memberships", "users" + add_foreign_key "user_group_memberships", "users", column: "created_by_id" + add_foreign_key "user_groups", "teams" + add_foreign_key "user_groups", "users", column: "created_by_id" + add_foreign_key "user_groups", "users", column: "last_modified_by_id" add_foreign_key "user_my_modules", "my_modules" add_foreign_key "user_my_modules", "users" add_foreign_key "user_my_modules", "users", column: "assigned_by_id" - add_foreign_key "user_projects", "projects" - add_foreign_key "user_projects", "users" - add_foreign_key "user_projects", "users", column: "assigned_by_id" add_foreign_key "user_roles", "users", column: "created_by_id" add_foreign_key "user_roles", "users", column: "last_modified_by_id" - add_foreign_key "user_teams", "teams" - add_foreign_key "user_teams", "users" - add_foreign_key "user_teams", "users", column: "assigned_by_id" add_foreign_key "users", "teams", column: "current_team_id" add_foreign_key "view_states", "users" add_foreign_key "webhooks", "activity_filters" diff --git a/db/views/datatables_teams_v01.sql b/db/views/datatables_teams_v01.sql deleted file mode 100644 index 276290608..000000000 --- a/db/views/datatables_teams_v01.sql +++ /dev/null @@ -1,13 +0,0 @@ -SELECT - teams.id AS id, - teams.name AS name, - user_teams.role AS role, - ( - SELECT COUNT(*) - FROM user_teams - WHERE user_teams.team_id = teams.id - ) AS members, - CASE WHEN teams.created_by_id = user_teams.user_id THEN false ELSE true END AS can_be_left, - user_teams.id AS user_team_id, - user_teams.user_id AS user_id -FROM teams INNER JOIN user_teams ON teams.id=user_teams.team_id diff --git a/features/step_definitions/projects_page_steps.rb b/features/step_definitions/projects_page_steps.rb index a71c24053..b6da62307 100644 --- a/features/step_definitions/projects_page_steps.rb +++ b/features/step_definitions/projects_page_steps.rb @@ -31,20 +31,6 @@ Then('I click to down arrow of a {string} project card') do |project| page.find('.panel-project', text: project).hover.find('.caret').click end -Then('user {string} owner of project {string}') do |user, project| - FactoryBot.create(:user_project, - role: 0, - user: User.find_by(full_name: user), - project: Project.find_by(name: project)) -end - -Then('user {string} normal user of project {string}') do |user, project| - FactoryBot.create(:user_project, - role: 1, - user: User.find_by(full_name: user), - project: Project.find_by(name: project)) -end - Given('I click to {string} of a Options modal window') do |link| page.find('.panel-project .dropdown-menu', text: 'Options').find('a', text: link).click end @@ -55,7 +41,6 @@ end Then('I select user {string} in user dropdown of user manage modal for project {string}') do |user, project| within('.modal-content', text: "Manage users for #{project}") do - find('.btn[data-id="user_project_user_id"]').click find('.dropdown-menu.open a', text: user).click end end diff --git a/public/images/icon/group.svg b/public/images/icon/group.svg new file mode 100644 index 000000000..e7057628d --- /dev/null +++ b/public/images/icon/group.svg @@ -0,0 +1,5 @@ + diff --git a/spec/controllers/access_permissions/experiments_controller_spec.rb b/spec/controllers/access_permissions/experiments_controller_spec.rb index 20172d803..12bd111a4 100644 --- a/spec/controllers/access_permissions/experiments_controller_spec.rb +++ b/spec/controllers/access_permissions/experiments_controller_spec.rb @@ -27,11 +27,6 @@ describe AccessPermissions::ExperimentsController, type: :controller do expect(response).to have_http_status :success end - it 'renders edit template' do - get :edit, params: { project_id: project.id, id: experiment.id }, format: :json - expect(response).to render_template :edit - end - it 'renders 403 if user does not have manage permissions on project' do sign_in_viewer_user diff --git a/spec/controllers/access_permissions/my_modules_controller_spec.rb b/spec/controllers/access_permissions/my_modules_controller_spec.rb index db1e251e0..d41bfd5ea 100644 --- a/spec/controllers/access_permissions/my_modules_controller_spec.rb +++ b/spec/controllers/access_permissions/my_modules_controller_spec.rb @@ -12,9 +12,7 @@ describe AccessPermissions::MyModulesController, type: :controller do let!(:viewer_user_role) { create :viewer_role } let!(:technician_role) { create :technician_role } let!(:project) { create :project, team: team, created_by: user } - let!(:user_project) { create :user_project, user: user, project: project } let!(:viewer_user) { create :user, confirmed_at: Time.zone.now } - let!(:viewer_user_project) { create :user_project, user: viewer_user, project: project } let!(:my_module) { create :my_module, experiment: experiment, created_by: experiment.created_by, created_by: user } before do @@ -42,11 +40,6 @@ describe AccessPermissions::MyModulesController, type: :controller do expect(response).to have_http_status :success end - it 'renders edit template' do - get :edit, params: { project_id: project.id, experiment_id: experiment.id, id: my_module.id }, format: :json - expect(response).to render_template :edit - end - it 'renders 403 if user does not have manage permissions on project' do sign_in_viewer_user diff --git a/spec/controllers/access_permissions/projects_controller_spec.rb b/spec/controllers/access_permissions/projects_controller_spec.rb index f35c7b991..aa00a6048 100644 --- a/spec/controllers/access_permissions/projects_controller_spec.rb +++ b/spec/controllers/access_permissions/projects_controller_spec.rb @@ -11,7 +11,6 @@ describe AccessPermissions::ProjectsController, type: :controller do let!(:owner_role) { UserRole.find_by(name: I18n.t('user_roles.predefined.owner')) } let!(:normal_user_role) { create :normal_user_role } let!(:technician_role) { create :technician_role } - let!(:user_project) { create :user_project, user: user, project: project } let!(:normal_user) { create :user, confirmed_at: Time.zone.now } before do @@ -66,13 +65,7 @@ describe AccessPermissions::ProjectsController, type: :controller do expect(response).to have_http_status :success end - it 'renders edit template' do - get :edit, params: { id: project.id }, format: :json - expect(response).to render_template :edit - end - it 'renders 403 if user does not have manage permissions on project' do - create :user_project, user: normal_user, project: project create :user_assignment, assignable: project, user: normal_user, user_role: normal_user_role, assigned_by: user sign_in_normal_user @@ -83,7 +76,6 @@ describe AccessPermissions::ProjectsController, type: :controller do end describe 'PUT #update' do - let!(:normal_user_project) { create :user_project, user: normal_user, project: project } let!(:normal_user_assignment) do create :user_assignment, assignable: project, @@ -151,13 +143,11 @@ describe AccessPermissions::ProjectsController, type: :controller do end it 'does not create an assigment when the user is already assigned with different permission' do - create :user_project, user: normal_user, project: project create :user_assignment, assignable: project, user: normal_user, user_role: normal_user_role, assigned_by: user expect { post :create, params: valid_params, format: :json - }.to change(UserProject, :count).by(0).and \ - change(UserAssignment, :count).by(0) + }.to change(UserAssignment, :count).by(0) end it 'renders 403 if user does not have manage permissions on project' do @@ -169,7 +159,6 @@ describe AccessPermissions::ProjectsController, type: :controller do end describe 'DELETE #destroy' do - let!(:normal_user_project) { create :user_project, user: normal_user, project: project } let!(:normal_user_assignment) do create :user_assignment, assignable: project, @@ -181,7 +170,9 @@ describe AccessPermissions::ProjectsController, type: :controller do let(:valid_params) do { id: project.id, - user_id: normal_user.id + user_assignment: { + user_id: normal_user.id + } } end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 74d76acb2..f00db1ec3 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -69,13 +69,6 @@ describe ProjectsController, type: :controller do expect(response.media_type).to eq 'text/html' end - it 'calls create activity service (project_grant_access_to_all_team_members)' do - params[:project][:visibility] = 'visible' - expect(Activities::CreateActivityService).to receive(:call) - .with(hash_including(activity_type: :project_grant_access_to_all_team_members)) - action - end - it 'adds activity in DB' do params[:project][:archived] = true expect { action } diff --git a/spec/controllers/repositories_controller_spec.rb b/spec/controllers/repositories_controller_spec.rb index 3a5a1f8b4..f7cd5e824 100644 --- a/spec/controllers/repositories_controller_spec.rb +++ b/spec/controllers/repositories_controller_spec.rb @@ -24,7 +24,10 @@ describe RepositoriesController, type: :controller do 'id', 'type', 'attributes' ) expect(parsed_response['data'][0]['attributes'].keys).to contain_exactly( - 'name', 'code', 'nr_of_rows', 'shared', 'shared_label', 'ishared', 'team', 'created_at', 'created_by', 'archived_on', 'archived_by', 'urls', 'shared_read', 'shared_write', 'shareable_write' + 'name', 'code', 'nr_of_rows', 'shared', 'shared_label', 'ishared', 'team', + 'created_at', 'created_by', 'archived_on', 'archived_by', 'urls', 'shared_read', + 'shared_write', 'shareable_write', 'assigned_users', 'default_public_user_role_id', + 'permissions', 'top_level_assignable', 'current_team' ) end end diff --git a/spec/factories/user_projects.rb b/spec/factories/user_projects.rb deleted file mode 100644 index 494a85f29..000000000 --- a/spec/factories/user_projects.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -FactoryBot.define do - factory :user_project do - user - project - assigned_by { user } - end -end diff --git a/spec/jobs/user_assignments/propagate_assignment_job_spec.rb b/spec/jobs/user_assignments/propagate_assignment_job_spec.rb index d700a32af..4779bc637 100644 --- a/spec/jobs/user_assignments/propagate_assignment_job_spec.rb +++ b/spec/jobs/user_assignments/propagate_assignment_job_spec.rb @@ -10,7 +10,6 @@ module UserAssignments let!(:team) { create :team, created_by: user_one } let!(:owner_role) { UserRole.find_by(name: I18n.t('user_roles.predefined.owner')) } let!(:project) { create :project, team: team, created_by: user_one } - let!(:user_project) { create :user_project, user: user_one, project: project } let!(:experiment_one) { create :experiment, project: project, created_by: project.created_by } let!(:experiment_two) { create :experiment, project: project, created_by: project.created_by } let!(:my_module_one) { create :my_module, experiment: experiment_one, created_by: experiment_one.created_by } @@ -19,12 +18,19 @@ module UserAssignments describe 'perform' do it 'propagates the user assignments to project child object' do expect { - described_class.perform_now(project, user_two.id, technician_role, user_one.id) - }.to change(UserAssignment, :count).by(4) + user_assignment = create( + :user_assignment, assignable: project, user: user_two, user_role: technician_role, assigned_by: user_one + ) + described_class.perform_now(user_assignment) + }.to change(UserAssignment, :count).by(5) end it 'propagates the user assignments to project child object with the same role' do - described_class.perform_now(project, user_two.id, technician_role, user_one.id) + user_assignment = create( + :user_assignment, assignable: project, user: user_two, user_role: technician_role, assigned_by: user_one + ) + + described_class.perform_now(user_assignment) [ UserAssignment.find_by(user: user_two, assignable: experiment_one), UserAssignment.find_by(user: user_two, assignable: experiment_two), @@ -36,35 +42,47 @@ module UserAssignments end it 'removes all the child objects user assignments when the destroy flag is set' do + project_assignment = user_assignment = create( + :user_assignment, assignable: project, user: user_two, user_role: technician_role, assigned_by: user_one + ) + create :user_assignment, assignable: experiment_one, user: user_two, user_role: technician_role, assigned_by: user_one create :user_assignment, assignable: experiment_two, user: user_two, user_role: technician_role, assigned_by: user_one create :user_assignment, assignable: my_module_one, user: user_two, user_role: technician_role, assigned_by: user_one create :user_assignment, assignable: my_module_two, user: user_two, user_role: technician_role, assigned_by: user_one expect { - described_class.perform_now(project, user_two.id, technician_role, user_one, destroy: true) - }.to change(UserAssignment, :count).by(-4) + described_class.perform_now(project_assignment, destroy: true) + }.to change(UserAssignment, :count).by(-5) end it 'does not propagate the user assignment if the object was manually assigned' do + project_assignment = user_assignment = create( + :user_assignment, assignable: project, user: user_two, user_role: technician_role, assigned_by: user_one + ) + experiment_assignment = create :user_assignment, assignable: experiment_one, user: user_two, user_role: owner_role, assigned_by: user_one, assigned: :manually - described_class.perform_now(project, user_two.id, technician_role, user_one.id) + described_class.perform_now(project_assignment) expect(experiment_assignment.reload.user_role).to eq owner_role end it 'does propagate the user assignment if the object was automatically assigned' do + project_assignment = user_assignment = create( + :user_assignment, assignable: project, user: user_two, user_role: technician_role, assigned_by: user_one + ) + experiment_assignment = create :user_assignment, assignable: experiment_one, user: user_two, user_role: owner_role, assigned_by: user_one, assigned: :automatically - described_class.perform_now(project, user_two.id, technician_role, user_one.id) + described_class.perform_now(project_assignment) expect(experiment_assignment.reload.user_role).to eq technician_role end end diff --git a/spec/models/my_module_spec.rb b/spec/models/my_module_spec.rb index 58cf7c185..98dfb55f6 100644 --- a/spec/models/my_module_spec.rb +++ b/spec/models/my_module_spec.rb @@ -53,7 +53,6 @@ describe MyModule, type: :model do it { should have_many :my_module_repository_rows } it { should have_many :repository_rows } it { should have_many :user_my_modules } - it { should have_many :users } it { should have_many :activities } it { should have_many :report_elements } it { should have_many :protocols } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index d9c6d507b..b520ae026 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -44,8 +44,6 @@ describe Project, type: :model do it { should belong_to(:archived_by).class_name('User').optional } it { should belong_to(:restored_by).class_name('User').optional } it { should belong_to(:supervised_by).class_name('User').optional } - it { should have_many :user_projects } - it { should have_many :users } it { should have_many :experiments } it { should have_many :project_comments } it { should have_many :activities } diff --git a/spec/models/team_spec.rb b/spec/models/team_spec.rb index b50e79edb..e302d8fee 100644 --- a/spec/models/team_spec.rb +++ b/spec/models/team_spec.rb @@ -27,7 +27,6 @@ describe Team, type: :model do describe 'Relations' do it { should belong_to(:created_by).class_name('User').optional } it { should belong_to(:last_modified_by).class_name('User').optional } - it { should have_many :users } it { should have_many :projects } it { should have_many :protocols } it { should have_many :protocol_keywords } diff --git a/spec/models/user_project_spec.rb b/spec/models/user_project_spec.rb deleted file mode 100644 index 53b470e31..000000000 --- a/spec/models/user_project_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe UserProject, type: :model do - let(:user_project_without_role) { build :user_project } - let(:user_project) { build :user_project, :owner } - - - it 'is valid with role' do - expect(user_project).to be_valid - end - - it 'should be of class UserProject' do - expect(subject.class).to eq UserProject - end - - describe 'Database table' do - it { should have_db_column :user_id } - it { should have_db_column :project_id } - it { should have_db_column :created_at } - it { should have_db_column :updated_at } - it { should have_db_column :assigned_by_id } - end - - describe 'Relations' do - it { should belong_to :user } - it { should belong_to :project } - it { should belong_to(:assigned_by).class_name('User').optional } - end - - describe 'Should be a valid object' do - it { should validate_presence_of :user } - it { should validate_presence_of :project } - end -end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 2e660e35d..9a784d61e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -47,7 +47,6 @@ describe User, type: :model do describe 'Relations' do it { should have_many :teams } - it { should have_many :user_projects } it { should have_many :projects } it { should have_many :user_my_modules } it { should have_many :comments } @@ -84,7 +83,6 @@ describe User, type: :model do it { should have_many :tokens } it { should have_many :modified_tags } it { should have_many :assigned_user_my_modules } - it { should have_many :assigned_user_projects } it { should have_many :added_protocols } it { should have_many :archived_protocols } it { should have_many :restored_protocols } @@ -156,23 +154,6 @@ describe User, type: :model do it { is_expected.to respond_to(:export_vars) } end - describe '#last_activities' do - let!(:user) { create :user } - let!(:project) { create :project } - let!(:user_projects) do - create :user_project, :viewer, project: project, user: user - end - let!(:activity_one) { create :activity, owner: user, project: project } - let!(:activity_two) { create :activity, owner: user, project: project } - - it 'is expected to return an array of user\'s activities' do - activities = user.last_activities - expect(activities.count).to eq 2 - expect(activities).to include activity_one - expect(activities).to include activity_two - end - end - describe '#increase_daily_exports_counter!' do let(:user) { create :user } context 'when last_export_timestamp is set' do diff --git a/spec/requests/api/v1/connections_controller_spec.rb b/spec/requests/api/v1/connections_controller_spec.rb index d51970316..72da30967 100644 --- a/spec/requests/api/v1/connections_controller_spec.rb +++ b/spec/requests/api/v1/connections_controller_spec.rb @@ -153,7 +153,6 @@ RSpec.describe 'Api::V1::ConnectionsController', type: :request do describe 'POST connections, #create' do before :all do - create :user_project, :normal_user, user: @user, project: @valid_project @valid_headers['Content-Type'] = 'application/json' end diff --git a/spec/requests/api/v1/experiment_user_assignments_controller_spec.rb b/spec/requests/api/v1/experiment_user_assignments_controller_spec.rb index 04a3fdd62..20f6b2628 100644 --- a/spec/requests/api/v1/experiment_user_assignments_controller_spec.rb +++ b/spec/requests/api/v1/experiment_user_assignments_controller_spec.rb @@ -24,7 +24,6 @@ RSpec.describe "Api::V1::ExperimentUserAssignmentsController", type: :request do name: Faker::Name.unique.name, project: @invalid_project, created_by: @another_user - create :user_project, user: @user, project: @own_project @technician_user_role = create :technician_role @valid_headers = { 'Authorization': 'Bearer ' + generate_token(@user.id) } diff --git a/spec/requests/api/v1/task_inventory_items_controller_spec.rb b/spec/requests/api/v1/task_inventory_items_controller_spec.rb index 14249c6ff..9e8b570fc 100644 --- a/spec/requests/api/v1/task_inventory_items_controller_spec.rb +++ b/spec/requests/api/v1/task_inventory_items_controller_spec.rb @@ -148,10 +148,10 @@ RSpec.describe 'Api::V1::TasksController', type: :request do expect(json[:data]).to match( JSON.parse( ActiveModelSerializers::SerializableResource - .new(@my_module.repository_rows, + .new(@repository_row_second, show_repository: true, my_module: @my_module, - each_serializer: Api::V1::TaskInventoryItemSerializer) + serializer: Api::V1::TaskInventoryItemSerializer) .to_json )['data'] ) @@ -279,8 +279,8 @@ RSpec.describe 'Api::V1::TasksController', type: :request do team_id: @team.id, project_id: @project.id, experiment_id: @experiment.id, - task_id: @my_module.id - ), + task_id: @my_module.id + ), headers: @valid_headers) } it 'Delete assigned item' do diff --git a/spec/requests/api/v1/tasks_controller_spec.rb b/spec/requests/api/v1/tasks_controller_spec.rb index e5a7c9bb9..51c7bd469 100644 --- a/spec/requests/api/v1/tasks_controller_spec.rb +++ b/spec/requests/api/v1/tasks_controller_spec.rb @@ -212,7 +212,6 @@ RSpec.describe 'Api::V1::TasksController', type: :request do describe 'POST tasks, #create' do before :all do - create :user_project, user: @user, project: @valid_project @valid_headers['Content-Type'] = 'application/json' end diff --git a/spec/services/activities/activity_filter_matching_service_spec.rb b/spec/services/activities/activity_filter_matching_service_spec.rb index 3e63e740f..2d80602a0 100644 --- a/spec/services/activities/activity_filter_matching_service_spec.rb +++ b/spec/services/activities/activity_filter_matching_service_spec.rb @@ -8,10 +8,10 @@ describe Activities::ActivityFilterMatchingService do let(:team) { create :team, created_by: user } let(:team_2) { create :team, created_by: user_2 } let(:project) do - create :project, team: team, user_projects: [] + create :project, team: team end let(:project_2) do - create :project, team: team, user_projects: [] + create :project, team: team end let(:activity) { create :activity } diff --git a/spec/services/activities/create_activity_service_spec.rb b/spec/services/activities/create_activity_service_spec.rb index c5fe49b53..677b4d0bb 100644 --- a/spec/services/activities/create_activity_service_spec.rb +++ b/spec/services/activities/create_activity_service_spec.rb @@ -6,7 +6,7 @@ describe Activities::CreateActivityService do let(:user) { create :user } let(:team) { create :team, created_by: user } let(:project) do - create :project, team: team, user_projects: [] + create :project, team: team end let(:service_call) do Activities::CreateActivityService diff --git a/spec/services/experiments/move_to_project_service_spec.rb b/spec/services/experiments/move_to_project_service_spec.rb index da92d7693..fec775e32 100644 --- a/spec/services/experiments/move_to_project_service_spec.rb +++ b/spec/services/experiments/move_to_project_service_spec.rb @@ -12,7 +12,7 @@ describe Experiments::MoveToProjectService do create :project, team: team, created_by: user end let(:experiment) do - create :experiment, :with_tasks, name: 'MyExp', project: project + create :experiment, :with_tasks, name: 'MyExp', project: project, created_by: user end let(:service_call) do Experiments::MoveToProjectService.call(experiment_id: experiment.id, @@ -95,8 +95,9 @@ describe Experiments::MoveToProjectService do end it 'returns error if teams is not the same' do - t = create :team - project.update(team: t) + another_team = create :team + wrong_project = create :project, team: another_team + service_call = Experiments::MoveToProjectService.call(experiment_id: experiment.id, project_id: wrong_project.id, user_id: user.id) expect(service_call.errors).to have_key(:target_project_not_valid) end diff --git a/spec/services/lists/projects_service_spec.rb b/spec/services/lists/projects_service_spec.rb index 10b04b4ad..d30cc9022 100644 --- a/spec/services/lists/projects_service_spec.rb +++ b/spec/services/lists/projects_service_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Lists::ProjectsService do let!(:projects) { create_list(:project, 5, team: team) } let!(:archived_projects) { create_list(:project, 5, :archived, team: team) } let(:params) {{ page: 1, per_page: 10, search: '', team: team } } - let(:service) { described_class.new(team, user, folder, params) } + let(:service) { described_class.new(team, team.projects, folder, params, user: user) } before(:all) do MyModuleStatusFlow.ensure_default diff --git a/spec/services/smart_annotations/permission_eval_spec.rb b/spec/services/smart_annotations/permission_eval_spec.rb index 8614dfa3c..eb654a198 100644 --- a/spec/services/smart_annotations/permission_eval_spec.rb +++ b/spec/services/smart_annotations/permission_eval_spec.rb @@ -11,7 +11,6 @@ describe SmartAnnotations::PermissionEval do let!(:owner_role) { UserRole.find_by(name: I18n.t('user_roles.predefined.owner')) } let!(:team_assignment) { create_user_assignment(team, owner_role, user) } let(:project) { create :project, name: 'my project', team: team } - let!(:user_project) { create :user_project, project: project, user: user } let!(:user_assignment) do create :user_assignment, assignable: project, @@ -31,68 +30,53 @@ describe SmartAnnotations::PermissionEval do describe '#validate_prj_permissions/2' do it 'returns a boolean' do - value = subject.__send__(:validate_prj_permissions, user, team, project) + value = subject.__send__(:validate_prj_permissions, user, project) expect(value).to be_in([true, false]) end - it 'returns false on wrong team' do - value = subject.__send__(:validate_prj_permissions, user, another_team, project) - expect(value).to be false - end - it 'returns true on the same team' do - value = subject.__send__(:validate_prj_permissions, user, team, project) + value = subject.__send__(:validate_prj_permissions, user, project) expect(value).to be true end end describe '#validate_exp_permissions/2' do it 'returns a boolean' do - value = subject.__send__(:validate_exp_permissions, user, team, experiment) + value = subject.__send__(:validate_exp_permissions, user, experiment) expect(value).to be_in([true, false]) end - it 'returns false on wrong team' do - value = subject.__send__(:validate_exp_permissions, user, another_team, experiment) - expect(value).to be false - end - it 'returns true on the same team' do - value = subject.__send__(:validate_exp_permissions, user, team, experiment) + value = subject.__send__(:validate_exp_permissions, user, experiment) expect(value).to be true end end describe '#validate_tsk_permissions/2' do it 'returns a boolean' do - value = subject.__send__(:validate_tsk_permissions, user, team, task) + value = subject.__send__(:validate_tsk_permissions, user, task) expect(value).to be_in([true, false]) end - it 'returns false on wrong team' do - value = subject.__send__(:validate_tsk_permissions, user, another_team, task) - expect(value).to be false - end - it 'returns true on the same team' do - value = subject.__send__(:validate_tsk_permissions, user, team, task) + value = subject.__send__(:validate_tsk_permissions, user, task) expect(value).to be true end end describe '#validate_rep_item_permissions/2' do it 'returns a boolean' do - value = subject.__send__(:validate_rep_item_permissions, user, team, repository_item) + value = subject.__send__(:validate_rep_item_permissions, user, repository_item) expect(value).to be_in([true, false]) end it 'returns false on wrong user' do - value = subject.__send__(:validate_rep_item_permissions, another_user, another_team, repository_item) + value = subject.__send__(:validate_rep_item_permissions, another_user, repository_item) expect(value).to be false end it 'returns true on the same team' do - value = subject.__send__(:validate_rep_item_permissions, user, team, repository_item) + value = subject.__send__(:validate_rep_item_permissions, user, repository_item) expect(value).to be true end @@ -101,7 +85,7 @@ describe SmartAnnotations::PermissionEval do # Add anoteher user also as a member of team whos owes repository with this item create_user_assignment(team, owner_role, another_user) - value = subject.__send__(:validate_rep_item_permissions, another_user, another_team, repository_item) + value = subject.__send__(:validate_rep_item_permissions, another_user, repository_item) expect(value).to be false end end diff --git a/spec/services/smart_annotations/tag_to_html_spec.rb b/spec/services/smart_annotations/tag_to_html_spec.rb index 17950abef..990277235 100644 --- a/spec/services/smart_annotations/tag_to_html_spec.rb +++ b/spec/services/smart_annotations/tag_to_html_spec.rb @@ -8,9 +8,6 @@ describe SmartAnnotations::TagToHtml do let!(:owner_role) { UserRole.find_by(name: I18n.t('user_roles.predefined.owner')) } let!(:team_assignment) { create_user_assignment(team, owner_role, user) } let!(:project) { create :project, name: 'my project', team: team } - let!(:user_project) do - create :user_project, project: project, user: user - end let!(:user_assignment) do create :user_assignment, assignable: project, diff --git a/vendor/assets/stylesheets/fonts/SN-icon-font.eot b/vendor/assets/stylesheets/fonts/SN-icon-font.eot index 8480925ee..7a7f8d1ed 100644 Binary files a/vendor/assets/stylesheets/fonts/SN-icon-font.eot and b/vendor/assets/stylesheets/fonts/SN-icon-font.eot differ diff --git a/vendor/assets/stylesheets/fonts/SN-icon-font.svg b/vendor/assets/stylesheets/fonts/SN-icon-font.svg index 1ec9e57ff..fb8eefb21 100644 --- a/vendor/assets/stylesheets/fonts/SN-icon-font.svg +++ b/vendor/assets/stylesheets/fonts/SN-icon-font.svg @@ -10,9 +10,9 @@ "designer": "David Praznik", "fontFamily": "SN-icon-font", "fontURL": "https://www.scinote.net", - "majorVersion": 1, - "minorVersion": 17, - "version": "Version 1.17", + "majorVersion": 2, + "minorVersion": 0, + "version": "Version 2.0", "fontId": "SN-icon-font", "psName": "SN-icon-font", "subFamily": "Regular", @@ -233,4 +233,6 @@