Merge branch 'develop' into ml-sci-4841

This commit is contained in:
Mojca Lorber 2020-10-12 14:43:03 +02:00
commit cf367baefa
35 changed files with 499 additions and 175 deletions

View file

@ -17,7 +17,7 @@ GIT
GIT
remote: https://github.com/biosistemika/yomu
revision: 8845246f3e6a6cbc49b902cd4b908ba70553cbdd
revision: 063b855d672e9dd9de1e6e585b349a9b63e120c3
branch: master
specs:
yomu (0.2.4)
@ -338,9 +338,9 @@ GEM
marcel (0.3.3)
mimemagic (~> 0.3.2)
method_source (0.9.2)
mime-types (3.3)
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.0904)
mime-types-data (3.2020.0512)
mimemagic (0.3.5)
mini_magick (4.9.5)
mini_mime (1.0.2)

View file

@ -1 +1 @@
1.19.7
1.20.1

View file

@ -22,8 +22,12 @@ var ProtocolRepositoryHeader = (function() {
function initEditDescription() {
var viewObject = $('#protocol_description_view');
viewObject.on('click', function() {
viewObject.on('click', function(e) {
if ($(e.target).hasClass('record-info-link')) return;
TinyMCE.init('#protocol_description_textarea');
}).on('click', 'a', function(e) {
if ($(this).hasClass('record-info-link')) return;
e.stopPropagation();
});
TinyMCE.initIfHasDraft(viewObject);
}

View file

@ -1,5 +1,3 @@
@import url(https://fonts.googleapis.com/css?family=Lato:400,400i,700&subset=latin-ext);
//==============================================================================
// Colors
//==============================================================================

View file

@ -31,7 +31,9 @@ module ActiveStorage
unless processing
ActiveStorage::PreviewJob.perform_later(@blob.id)
@blob.attachments.take.record.update(file_processing: true)
ActiveRecord::Base.no_touching do
@blob.attachments.take.record.update(file_processing: true)
end
end
false

View file

@ -23,14 +23,14 @@ module Api
raise PermissionError.new(Asset, :create) unless can_manage_protocol_in_module?(@protocol)
if @form_multipart_upload
asset = @step.assets.new(asset_params)
asset = @step.assets.new(asset_params.merge({ team_id: @team.id }))
else
blob = ActiveStorage::Blob.create_after_upload!(
io: StringIO.new(Base64.decode64(asset_params[:file_data])),
filename: asset_params[:file_name],
content_type: asset_params[:file_type]
)
asset = @step.assets.new(file: blob)
asset = @step.assets.new(file: blob, team: @team)
end
asset.save!(context: :on_api_upload)

View file

@ -104,10 +104,10 @@ module Api
Result.transaction do
@result = @task.results.create!(result_params.merge(user_id: current_user.id))
if @form_multipart_upload
asset = Asset.create!(result_file_params)
asset = Asset.create!(result_file_params.merge({ team_id: @team.id }))
else
blob = create_blob_from_params
asset = Asset.create!(file: blob)
asset = Asset.create!(file: blob, team: @team)
end
ResultAsset.create!(asset: asset, result: @result)
end

View file

@ -6,6 +6,7 @@ module Api
before_action :load_team
before_action :load_project
before_action :load_user_project, only: :show
before_action :load_user_project_for_managing, only: %i(update destroy)
def index
user_projects = @project.user_projects
@ -23,11 +24,44 @@ module Api
include: :user
end
def create
raise PermissionError.new(Project, :manage) unless can_manage_project?(@project)
user_project = @project.user_projects.create!(user_project_params.merge!(assigned_by: current_user))
render jsonapi: user_project, serializer: UserProjectSerializer, status: :created
end
def update
@user_project.role = user_project_params[:role]
return render body: nil, status: :no_content unless @user_project.changed?
@user_project.assigned_by = current_user
@user_project.save!
render jsonapi: @user_project, serializer: UserProjectSerializer, status: :ok
end
def destroy
@user_project.destroy!
render body: nil
end
private
def load_user_project
@user_project = @project.user_projects.find(params.require(:id))
end
def load_user_project_for_managing
@user_project = @project.user_projects.find(params.require(:id))
raise PermissionError.new(Project, :manage) unless can_manage_project?(@project)
end
def user_project_params
raise TypeError unless params.require(:data).require(:type) == 'user_projects'
params.require(:data).require(:attributes).permit(:user_id, :role)
end
end
end
end

View file

@ -37,7 +37,7 @@ class ApplicationController < ActionController::Base
# Sets current team for all controllers
def current_team
Team.find_by_id(current_user.current_team_id)
@current_team ||= current_user.teams.find_by(id: current_user.current_team_id)
end
def to_user_date_format
@ -83,13 +83,12 @@ class ApplicationController < ActionController::Base
private
def update_current_team
current_team = Team.find_by_id(current_user.current_team_id)
if (current_team.nil? || !current_user.is_member_of_team?(current_team)) &&
current_user.teams.count.positive?
return if current_team.present? && current_team.id == current_user.current_team_id
current_user.update(
current_team_id: current_user.teams.first.id
)
if current_user.current_team_id
@current_team = current_user.teams.find_by(id: current_user.current_team_id)
elsif current_user.teams.any?
current_user.update(current_team_id: current_user.teams.first.id)
end
end

View file

@ -1,5 +1,7 @@
# frozen_string_literal: true
class DashboardsController < ApplicationController
def show; end
def show
@my_module_status_flows = MyModuleStatusFlow.all.preload(my_module_statuses: :my_module_status_consequences)
end
end

View file

@ -5,7 +5,10 @@ class MyModuleStatusFlowController < ApplicationController
before_action :check_view_permissions
def show
my_module_statuses = @my_module.my_module_status_flow.my_module_statuses.sort_by_position
my_module_statuses = @my_module.my_module_status_flow
.my_module_statuses
.preload(:my_module_status_implications, next_status: :my_module_status_conditions)
.sort_by_position
render json: { html: render_to_string(partial: 'my_modules/modals/status_flow_modal_body.html.erb',
locals: { my_module_statuses: my_module_statuses }) }
end

View file

@ -158,10 +158,6 @@ class MyModuleTagsController < ApplicationController
render_403 unless can_manage_module?(@my_module)
end
def init_gui
@tags = @my_module.unassigned_tags
end
def mt_params
params.require(:my_module_tag).permit(:my_module_id, :tag_id)
end

View file

@ -4,6 +4,7 @@ class ProjectsController < ApplicationController
include TeamsHelper
include InputSanitizeHelper
before_action :switch_team_with_param, only: :index
before_action :load_vars, only: %i(show edit update
notifications reports
samples experiment_archive
@ -37,7 +38,6 @@ class ProjectsController < ApplicationController
}
end
format.html do
current_team_switch(Team.find_by_id(params[:team])) if params[:team]
@teams = current_user.teams
# New project for create new project modal
@project = Project.new

View file

@ -325,7 +325,7 @@ class ProtocolsController < ApplicationController
@protocol.unlink
rescue Exception
transaction_error = true
raise ActiveRecord:: Rollback
raise ActiveRecord::Rollback
end
end
@ -353,13 +353,11 @@ class ProtocolsController < ApplicationController
if @protocol.can_destroy?
transaction_error = false
Protocol.transaction do
begin
# Revert is basically update from parent
@protocol.update_from_parent(current_user)
rescue Exception
transaction_error = true
raise ActiveRecord:: Rollback
end
# Revert is basically update from parent
@protocol.update_from_parent(current_user)
rescue StandardError
transaction_error = true
raise ActiveRecord::Rollback
end
if transaction_error
@ -397,12 +395,10 @@ class ProtocolsController < ApplicationController
if @protocol.parent.can_destroy?
transaction_error = false
Protocol.transaction do
begin
@protocol.update_parent(current_user)
rescue Exception
transaction_error = true
raise ActiveRecord:: Rollback
end
@protocol.update_parent(current_user)
rescue StandardError
transaction_error = true
raise ActiveRecord::Rollback
end
if transaction_error
@ -440,12 +436,10 @@ class ProtocolsController < ApplicationController
if @protocol.can_destroy?
transaction_error = false
Protocol.transaction do
begin
@protocol.update_from_parent(current_user)
rescue Exception
transaction_error = true
raise ActiveRecord:: Rollback
end
@protocol.update_from_parent(current_user)
rescue StandardError
transaction_error = true
raise ActiveRecord::Rollback
end
if transaction_error
@ -483,12 +477,10 @@ class ProtocolsController < ApplicationController
if @protocol.can_destroy?
transaction_error = false
Protocol.transaction do
begin
@protocol.load_from_repository(@source, current_user)
rescue Exception
transaction_error = true
raise ActiveRecord:: Rollback
end
@protocol.load_from_repository(@source, current_user)
rescue StandardError
transaction_error = true
raise ActiveRecord::Rollback
end
if transaction_error

View file

@ -121,10 +121,6 @@ class UserMyModulesController < ApplicationController
render_403 unless can_manage_users_in_module?(@my_module)
end
def init_gui
@users = @my_module.unassigned_users
end
def um_params
params.require(:user_my_module).permit(:user_id, :my_module_id)
end

View file

@ -3,7 +3,7 @@ class UserProjectsController < ApplicationController
include InputSanitizeHelper
before_action :load_vars
before_action :load_up_var, only: %i(update destroy)
before_action :load_user_project, only: %i(update destroy)
before_action :check_view_permissions, only: :index
before_action :check_manage_users_permissions, only: :index_edit
before_action :check_create_permissions, only: :create
@ -26,9 +26,9 @@ class UserProjectsController < ApplicationController
end
def index_edit
@users = @project.user_projects
@user_projects = @project.user_projects
@unassigned_users = @project.unassigned_users
@up = UserProject.new(project: @project)
@new_user_project = UserProject.new(project: @project)
respond_to do |format|
format.json do
@ -48,10 +48,10 @@ class UserProjectsController < ApplicationController
end
def create
@up = UserProject.new(up_params.merge(project: @project))
@up.assigned_by = current_user
@user_project = @project.user_projects.new(user_project_params)
@user_project.assigned_by = current_user
if @up.save
if @user_project.save
log_activity(:assign_user_to_project)
respond_to do |format|
@ -61,23 +61,23 @@ class UserProjectsController < ApplicationController
end
else
error = t('user_projects.create.can_add_user_to_project')
error = t('user_projects.create.select_user_role') unless @up.role
error = t('user_projects.create.select_user_role') unless @user_project.role
respond_to do |format|
format.json {
render :json => {
format.json do
render json: {
status: 'error',
error: error
}
}
end
end
end
end
def update
@up.role = up_params[:role]
@user_project.role = user_project_params[:role]
if @up.save
if @user_project.save
log_activity(:change_user_role_on_project)
respond_to do |format|
@ -90,7 +90,7 @@ class UserProjectsController < ApplicationController
format.json do
render json: {
status: 'error',
errors: @up.errors
errors: @user_project.errors
}
end
end
@ -98,20 +98,20 @@ class UserProjectsController < ApplicationController
end
def destroy
if @up.destroy
if @user_project.destroy
log_activity(:unassign_user_from_project)
respond_to do |format|
format.json do
redirect_to project_users_edit_path(format: :json),
turbolinks: false,
status: 303
status: :see_other
end
end
else
respond_to do |format|
format.json do
render json: {
errors: @up.errors
errors: @user_project.errors
}
end
end
@ -121,13 +121,13 @@ class UserProjectsController < ApplicationController
private
def load_vars
@project = Project.find_by_id(params[:project_id])
@project = Project.find_by(id: params[:project_id])
render_404 unless @project
end
def load_up_var
@up = UserProject.find(params[:id])
render_404 unless @up
def load_user_project
@user_project = @project.user_projects.find(params[:id])
render_404 unless @user_project
end
def check_view_permissions
@ -139,19 +139,14 @@ class UserProjectsController < ApplicationController
end
def check_create_permissions
render_403 unless can_create_projects?(current_team)
render_403 unless can_manage_project?(@project)
end
def check_manage_permissions
render_403 unless can_manage_project?(@project) &&
@up.user_id != current_user.id
render_403 unless can_manage_project?(@project) && @user_project.user_id != current_user.id
end
def init_gui
@users = @project.unassigned_users
end
def up_params
def user_project_params
params.require(:user_project).permit(:user_id, :project_id, :role)
end
@ -163,7 +158,7 @@ class UserProjectsController < ApplicationController
team: @project.team,
project: @project,
message_items: { project: @project.id,
user_target: @up.user.id,
role: @up.role_str })
user_target: @user_project.user.id,
role: @user_project.role_str })
end
end

View file

@ -1,9 +1,10 @@
module TeamsHelper
# resets the current team if needed
def current_team_switch(team)
if team != current_team
if team != current_team && current_user.is_member_of_team?(team)
current_user.current_team_id = team.id
current_user.save
update_current_team
end
end
@ -17,11 +18,7 @@ module TeamsHelper
end
end
def team_created_by(team)
User.find_by_id(team.created_by_id)
end
def switch_team_with_param
current_team_switch(Team.find_by(id: params[:team])) if params[:team]
current_team_switch(current_user.teams.find_by(id: params[:team])) if params[:team]
end
end

View file

@ -0,0 +1 @@
require('typeface-lato');

View file

@ -6,7 +6,9 @@ class ActiveStorage::PreviewJob < ActiveStorage::BaseJob
discard_on StandardError do |job, error|
blob = ActiveStorage::Blob.find_by(id: job.arguments.first)
blob&.attachments&.take&.record&.update(file_processing: false)
ActiveRecord::Base.no_touching do
blob&.attachments&.take&.record&.update(file_processing: false)
end
Rails.logger.error "Couldn't generate preview for Blob with id: #{job.arguments.first}. Error:\n #{error}"
end
@ -24,6 +26,8 @@ class ActiveStorage::PreviewJob < ActiveStorage::BaseJob
Rails.logger.info "Preview for the Blod with id: #{blob.id} - successfully generated.\n" \
"Transformations applied: #{preview.variation.transformations}"
blob.attachments.take.record.update(file_processing: false)
ActiveRecord::Base.no_touching do
blob.attachments.take.record.update(file_processing: false)
end
end
end

View file

@ -32,17 +32,15 @@ class Asset < ApplicationRecord
optional: true
belongs_to :team, optional: true
has_one :step_asset, inverse_of: :asset, dependent: :destroy
has_one :step, through: :step_asset, dependent: :nullify
has_one :step, through: :step_asset, touch: true, dependent: :nullify
has_one :result_asset, inverse_of: :asset, dependent: :destroy
has_one :result, through: :result_asset, dependent: :nullify
has_one :result, through: :result_asset, touch: true, dependent: :nullify
has_one :repository_asset_value, inverse_of: :asset, dependent: :destroy
has_one :repository_cell, through: :repository_asset_value,
dependent: :nullify
has_many :report_elements, inverse_of: :asset, dependent: :destroy
has_one :asset_text_datum, inverse_of: :asset, dependent: :destroy
after_save { result&.touch; step&.touch }
attr_accessor :file_content, :file_info, :in_template
def self.search(
@ -222,8 +220,7 @@ class Asset < ApplicationRecord
Rails.logger.info "Asset #{id}: Creating extract text job"
# The extract_asset_text also includes
# estimated size calculation
Asset.delay(queue: :assets, run_at: 20.minutes.from_now)
.extract_asset_text_delayed(id, in_template)
Asset.delay(queue: :assets).extract_asset_text_delayed(id, in_template)
elsif marvinjs?
extract_asset_text
else

View file

@ -229,14 +229,16 @@ class Protocol < ApplicationRecord
# Deep-clone given array of assets
def self.deep_clone_assets(assets_to_clone)
assets_to_clone.each do |src_id, dest_id|
src = Asset.find_by(id: src_id)
dest = Asset.find_by(id: dest_id)
dest.destroy! if src.blank? && dest.present?
next unless src.present? && dest.present?
ActiveRecord::Base.no_touching do
assets_to_clone.each do |src_id, dest_id|
src = Asset.find_by(id: src_id)
dest = Asset.find_by(id: dest_id)
dest.destroy! if src.blank? && dest.present?
next unless src.present? && dest.present?
# Clone file
src.duplicate_file(dest)
# Clone file
src.duplicate_file(dest)
end
end
end
@ -524,12 +526,14 @@ class Protocol < ApplicationRecord
end
def update_parent(current_user)
# First, destroy parent's step contents
parent.destroy_contents
parent.reload
ActiveRecord::Base.no_touching do
# First, destroy parent's step contents
parent.destroy_contents
parent.reload
# Now, clone step contents
Protocol.clone_contents(self, parent, current_user, false)
# Now, clone step contents
Protocol.clone_contents(self, parent, current_user, false)
end
# Lastly, update the metadata
parent.reload
@ -542,11 +546,13 @@ class Protocol < ApplicationRecord
end
def update_from_parent(current_user)
# First, destroy step contents
destroy_contents
ActiveRecord::Base.no_touching do
# First, destroy step contents
destroy_contents
# Now, clone parent's step contents
Protocol.clone_contents(parent, self, current_user, false)
# Now, clone parent's step contents
Protocol.clone_contents(parent, self, current_user, false)
end
# Lastly, update the metadata
reload
@ -558,11 +564,13 @@ class Protocol < ApplicationRecord
end
def load_from_repository(source, current_user)
# First, destroy step contents
destroy_contents
ActiveRecord::Base.no_touching do
# First, destroy step contents
destroy_contents
# Now, clone source's step contents
Protocol.clone_contents(source, self, current_user, false)
# Now, clone source's step contents
Protocol.clone_contents(source, self, current_user, false)
end
# Lastly, update the metadata
reload
@ -588,12 +596,14 @@ class Protocol < ApplicationRecord
# Don't proceed further if clone is invalid
return clone if clone.invalid?
# Okay, clone seems to be valid: let's clone it
clone = deep_clone(clone, current_user)
ActiveRecord::Base.no_touching do
# Okay, clone seems to be valid: let's clone it
clone = deep_clone(clone, current_user)
# If the above operation went well, update published_on
# timestamp
clone.update(published_on: Time.now) if clone.in_repository_public?
# If the above operation went well, update published_on
# timestamp
clone.update(published_on: Time.zone.now) if clone.in_repository_public?
end
# Link protocols if neccesary
if link_protocols

View file

@ -26,7 +26,10 @@ Canaid::Permissions.register_for(Experiment) do
# assign/reassign/unassign tags
can :manage_experiment do |user, experiment|
user.is_user_or_higher_of_project?(experiment.project) &&
MyModule.joins(:experiment).where(experiment: experiment).all? do |my_module|
MyModule.joins(:experiment)
.where(experiment: experiment)
.preload(my_module_status: :my_module_status_implications)
.all? do |my_module|
if my_module.my_module_status
my_module.my_module_status.my_module_status_implications.all? { |implication| implication.call(my_module) }
else

View file

@ -38,7 +38,10 @@ Canaid::Permissions.register_for(Project) do
# project: update/delete, assign/reassign/unassign users
can :manage_project do |user, project|
user.is_owner_of_project?(project) &&
MyModule.joins(experiment: :project).where(experiments: { project: project }).all? do |my_module|
MyModule.joins(experiment: :project)
.where(experiments: { project: project })
.preload(my_module_status: :my_module_status_implications)
.all? do |my_module|
if my_module.my_module_status
my_module.my_module_status.my_module_status_implications.all? { |implication| implication.call(my_module) }
else

View file

@ -27,10 +27,10 @@
data-select-multiple-name="<%= t("dashboard.current_tasks.filter.statuses.selected") %>"
multiple
>
<% MyModuleStatusFlow.find_each do |status_flow| %>
<% @my_module_status_flows.each do |status_flow| %>
<% status_flow.my_module_statuses.each do |status| %>
<option value="<%= status.id %>"
data-completion-consequence="<%= status.my_module_status_consequences.where(type: "MyModuleStatusConsequences::Completion").any? %>">
data-completion-consequence="<%= status.my_module_status_consequences.any? { |c| c.type == 'MyModuleStatusConsequences::Completion'} %>">
<%= status.name %>
</option>
<% end %>

View file

@ -1,8 +1,10 @@
<% provide :head_title, t('nav.label.dashboard') %>
<div class="dashboard-container">
<%= render "calendar" %>
<%= render "current_tasks" %>
<%= render "recent_work" %>
<%= render "quick_start" %>
</div>
<% if current_team %>
<div class="dashboard-container">
<%= render "calendar" %>
<%= render "current_tasks" %>
<%= render "recent_work" %>
<%= render "quick_start" %>
</div>
<% end %>

View file

@ -17,6 +17,7 @@
<%= favicon_link_tag "favicon-16.png", type: "image/png", size: "16x16" %>
<%= favicon_link_tag "favicon-32.png", type: "image/png", size: "32x32" %>
<%= favicon_link_tag "favicon-48.png", type: "image/png", size: "48x48" %>
<%= stylesheet_pack_tag 'fonts' %>
<%= stylesheet_pack_tag 'fontawesome' %>
<%= csrf_meta_tags %>

View file

@ -1,6 +1,6 @@
<% if project.active_experiments.present? %>
<ul class="tree-child hidden" data-branch-id="pro<%= project.id %>">
<% project.sorted_active_experiments('atoz').each do |experiment| %>
<% project.sorted_active_experiments('atoz').preload(:active_my_modules).each do |experiment| %>
<% cache [action_name, current_user, experiment] do %>
<li data-parent="candidate" class="branch">
<span class="tree-link first-indent" title="<%= experiment.name %>">

View file

@ -27,7 +27,7 @@
</a>
</div>
<div class="panel-options pull-right">
<% unless preview %>
<% if !preview && @protocol.my_module %>
<% if step.completed? %>
<button data-action="uncomplete-step"
data-link-url="<%= toggle_step_state_step_path(step)%>"

View file

@ -1,11 +1,13 @@
<ul class="no-style">
<% if @users.size == 0 then %>
<li><em><%= t 'projects.index.modal_manage_users.no_users' %></em></li>
<% else %>
<% @users.each_with_index do |user_proj, i| user = user_proj.user %>
<% if @user_projects.blank? %>
<li>
<% if i > 0 %><hr><% end %>
<em><%= t('projects.index.modal_manage_users.no_users') %></em>
</li>
<% else %>
<% @user_projects.each_with_index do |user_project, i| user = user_project.user %>
<li>
<% if i > 0 %><hr><% end %>
<div class="row">
<div class="col-xs-2">
@ -16,15 +18,20 @@
<div class="col-xs-10 col-sm-4" style="line-height: 15px">
<span><%= user.full_name %></span>
<br><span class="text-muted"><%= t('user_projects.enums.role.'<< user_proj.role) %></span>
<br><span class="text-muted"><%= t('user_projects.enums.role.'<< user_project.role) %></span>
</div>
<% unless user.id == current_user.id %>
<div class="visible-xs">&nbsp;</div>
<div class="col-xs-7 col-xs-offset-2 col-sm-4 col-sm-offset-0">
<%= form_for @up, url: project_user_project_path(@project, user_proj.id, method: :put, format: :json), format: :json, method: 'put', remote: true, html: { class: 'update-user-form' } do |f| %>
<%= form_for user_project,
url: project_user_project_path(@project, user_project.id),
format: :json,
method: 'put',
remote: true,
html: { class: 'update-user-form' } do |f| %>
<% # TODO replace with form helper %>
<input type="hidden" name="user_project[user_id]" value="<%= user_proj.id %>">
<input type="hidden" name="user_project[user_id]" value="<%= user_project.id %>">
<% # TODO replace hardcoded select html with rails helper %>
<select class="selectpicker" name="user_project[role]">
<option value=""><%= t('user_projects.edit.update_role') %></option>
@ -36,7 +43,7 @@
<% end %>
</div>
<div class="col-xs-3 col-sm-2">
<%= link_to project_user_project_path(@project, user_proj, format: :json), method: :delete, remote: true, class: 'btn btn-link remove-user-link' do %>
<%= link_to project_user_project_path(@project, user_project, format: :json), method: :delete, remote: true, class: 'btn btn-link remove-user-link' do %>
<span class="fas fa-times"></span>
<% end %>
</div>
@ -51,7 +58,7 @@
<li>
<hr>
<div class="row">
<%= bootstrap_form_for [@project, @up], remote: true, format: :json, html: { class: 'add-user-form' } do |f| %>
<%= bootstrap_form_for [@project, @new_user_project], remote: true, format: :json, html: { class: 'add-user-form' } do |f| %>
<%= hidden_field_tag :project_id, @project.id %>
<div class="col-xs-12 col-sm-4">
<%= collection_select(:user_project, :user_id, @unassigned_users, :id, :full_name, {}, { class: 'selectpicker' }) %>

View file

@ -44,7 +44,9 @@
<div class="created-by grid-block">
<span class="fas fa-user fa-lg"></span>
<span class="hidden-xs hidden-sm"><%= t("users.settings.teams.edit.header_created_by") %></span>
<b><%= t("users.settings.teams.edit.header_created_by_name_email", name: team_created_by(@team).name, email: team_created_by(@team).email) %></b>
<b>
<%= t('users.settings.teams.edit.header_created_by_name_email', name: @team.created_by.full_name, email: @team.created_by.email) if @team.created_by %>
</b>
</div>
<div class="space-usage grid-block">

View file

@ -318,7 +318,7 @@ class Constants
WHITELISTED_ATTRIBUTES = [
'href', 'src', 'width', 'height', 'alt', 'cite', 'datetime', 'title',
'class', 'name', 'xml:lang', 'abbr', 'style', 'target', :data, 'border'
'class', 'name', 'xml:lang', 'abbr', 'style', 'target', :data, 'border', 'contenteditable'
].freeze
WHITELISTED_CSS_ATTRIBUTES = {

View file

@ -637,8 +637,7 @@ Rails.application.routes.draw do
if Rails.configuration.x.core_api_v1_enabled
namespace :v1 do
resources :teams, only: %i(index show) do
resources :inventories,
only: %i(index create show update destroy) do
resources :inventories, only: %i(index create show update destroy) do
resources :inventory_columns,
only: %i(index create show update destroy),
path: 'columns',
@ -667,13 +666,10 @@ Rails.application.routes.draw do
end
end
resources :projects, only: %i(index show create update) do
resources :user_projects, only: %i(index show),
path: 'users', as: :users
resources :project_comments, only: %i(index show),
path: 'comments', as: :comments
resources :user_projects, only: %i(index show create update destroy), path: 'users', as: :users
resources :project_comments, only: %i(index show), path: 'comments', as: :comments
get 'activities', to: 'projects#activities'
resources :reports, only: %i(index show),
path: 'reports', as: :reports
resources :reports, only: %i(index show), path: 'reports', as: :reports
resources :experiments, only: %i(index show create update) do
resources :task_groups, only: %i(index show)
resources :connections, only: %i(index show)
@ -688,12 +684,15 @@ Rails.application.routes.draw do
path: 'tags',
as: :tags
resources :protocols, only: %i(index show) do
resources :steps do
resources :steps, only: %i(index show create update destroy) do
resources :assets, only: %i(index show create), path: 'attachments'
resources :checklists, path: 'checklists' do
resources :checklist_items, as: :items, path: 'items'
resources :checklists, only: %i(index show create update destroy), path: 'checklists' do
resources :checklist_items,
only: %i(index show create update destroy),
as: :items,
path: 'items'
end
resources :tables, path: 'tables'
resources :tables, only: %i(index show create update destroy), path: 'tables'
end
end
resources :results, only: %i(index create show update)

View file

@ -51,6 +51,7 @@
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@fortawesome/fontawesome-free": "^5.2.0",
"@joeattardi/emoji-button": "^2.5.4",
"@rails/webpacker": "^4.0.7",
"autoprefixer": "^7.2.6",
"axios": "0.18.1",
@ -62,7 +63,6 @@
"compression-webpack-plugin": "^1.1.11",
"croppie": "^2.6.4",
"css-loader": "2.1.1",
"@joeattardi/emoji-button": "^2.5.4",
"extract-text-webpack-plugin": "^3.0.2",
"fabric": "1.6.7",
"file-loader": "^4.0.0",
@ -110,6 +110,7 @@
"tui-color-picker": "^2.2.0",
"tui-image-editor": "git://github.com/biosistemika/tui.image-editor",
"twemoji": "^12.1.4",
"typeface-lato": "^0.0.75",
"webpack": "^4.35.2",
"webpack-cli": "^3.3.5",
"webpack-manifest-plugin": "^1.3.2",

View file

@ -0,0 +1,273 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe "Api::V1::UserProjectsController", type: :request do
before :all do
@user = create(:user)
@another_user = create(:user)
@team = create(:team, created_by: @user)
create(:user_team, user: @user, team: @team, role: :normal_user)
create(:user_team, user: @another_user, team: @team, role: :normal_user)
@own_project = create(:project, name: Faker::Name.unique.name, created_by: @user, team: @team)
@invalid_project =
create(:project, name: Faker::Name.unique.name, created_by: @another_user, team: @team, visibility: :hidden)
create(:user_project, role: :owner, user: @user, project: @own_project)
@valid_headers = { 'Authorization': 'Bearer ' + generate_token(@user.id) }
end
describe 'GET user_projects, #index' do
it 'Response with correct user project roles' do
hash_body = nil
get api_v1_team_project_users_path(team_id: @team.id, project_id: @own_project.id),
headers: @valid_headers
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
ActiveModelSerializers::SerializableResource
.new(@own_project.user_projects, each_serializer: Api::V1::UserProjectSerializer)
.as_json[:data]
)
end
it 'When invalid request, user in not an owner of the project' do
hash_body = nil
get api_v1_team_project_users_path(team_id: @team.id, project_id: @invalid_project.id),
headers: @valid_headers
expect(response).to have_http_status(403)
expect { hash_body = json }.not_to raise_exception
expect(hash_body['errors'][0]).to include('status': 403)
end
it 'When invalid request, non existing project' do
hash_body = nil
get api_v1_team_project_users_path(team_id: @team.id, project_id: -1), headers: @valid_headers
expect(response).to have_http_status(404)
expect { hash_body = json }.not_to raise_exception
expect(hash_body['errors'][0]).to include('status': 404)
end
end
describe 'GET user_project, #show' do
it 'When valid request, user can read project users' do
hash_body = nil
get api_v1_team_project_user_path(
team_id: @team.id, project_id: @own_project.id, id: @own_project.user_projects.first.id
), headers: @valid_headers
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]).to match(
ActiveModelSerializers::SerializableResource
.new(@own_project.user_projects.first, serializer: Api::V1::UserProjectSerializer)
.as_json[:data]
)
end
it 'When invalid request, user in not member of the project' do
hash_body = nil
get api_v1_team_project_user_path(
team_id: @team.id, project_id: @invalid_project.id, id: -1
), headers: @valid_headers
expect(response).to have_http_status(403)
expect { hash_body = json }.not_to raise_exception
expect(hash_body['errors'][0]).to include('status': 403)
end
it 'When invalid request, non existing project' do
hash_body = nil
get api_v1_team_project_user_path(team_id: @team.id, project_id: -1, id: -1), headers: @valid_headers
expect(response).to have_http_status(404)
expect { hash_body = json }.not_to raise_exception
expect(hash_body['errors'][0]).to include('status': 404)
end
end
describe 'POST user_project, #create' do
before :all do
@valid_headers['Content-Type'] = 'application/json'
end
let(:action) do
post(api_v1_team_project_users_path(team_id: @team.id, project_id: @own_project.id),
params: request_body.to_json, headers: @valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'user_projects',
attributes: {
user_id: @another_user.id,
role: 'normal_user'
}
}
}
end
it 'creates new user_project' do
expect { action }.to change { UserProject.count }.by(1)
end
it 'returns status 201' do
action
expect(response).to have_http_status 201
end
it 'returns well formated response' do
action
expect(json).to match(
hash_including(
data: hash_including(
type: 'user_projects',
attributes: hash_including(role: 'normal_user'),
relationships: hash_including(user: hash_including(data: hash_including(id: @another_user.id.to_s)))
)
)
)
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'user_projects',
attributes: {}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
context 'when user is not an owner of the project' do
let(:request_body) do
{
data: {
type: 'user_projects',
attributes: {
user_id: @another_user.id,
role: 'normal_user'
}
}
}
end
it 'renders 403' do
post(
api_v1_team_project_users_path(
team_id: @invalid_project.team.id,
project_id: @invalid_project.id
),
params: request_body.to_json,
headers: @valid_headers
)
expect(response).to have_http_status(403)
end
end
end
describe 'PATCH user_project, #update' do
before :all do
@valid_headers['Content-Type'] = 'application/json'
@user_project = create(:user_project, role: :normal_user, user: @another_user, project: @own_project)
end
let(:action) do
patch(
api_v1_team_project_user_path(
team_id: @own_project.team.id,
project_id: @own_project.id,
id: @user_project.id
),
params: request_body.to_json,
headers: @valid_headers
)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'user_projects',
attributes: {
role: :technician
}
}
}
end
it 'returns status 200' do
action
expect(response).to have_http_status 200
end
it 'returns well formated response' do
action
expect(json).to match(
hash_including(
data: hash_including(
type: 'user_projects',
attributes: hash_including(role: 'technician')
)
)
)
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'user_projects',
attributes: {
}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
context 'when user is not an owner of the project' do
let(:request_body) do
{
data: {
type: 'user_projects',
attributes: {
role: :technician
}
}
}
end
it 'renders 403' do
user_project = create(:user_project, role: :normal_user, user: @another_user, project: @invalid_project)
patch(
api_v1_team_project_user_path(
team_id: @invalid_project.team.id,
project_id: @invalid_project.id,
id: user_project.id
),
params: request_body.to_json,
headers: @valid_headers
)
expect(response).to have_http_status(403)
end
end
end
end

View file

@ -3587,10 +3587,10 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
eventemitter3@^3.0.0:
version "3.1.2"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==
eventemitter3@^4.0.0:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
events@^3.0.0:
version "3.0.0"
@ -4023,11 +4023,9 @@ follow-redirects@1.5.10:
debug "=3.1.0"
follow-redirects@^1.0.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76"
integrity sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==
dependencies:
debug "^3.2.6"
version "1.13.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
for-in@^0.1.3:
version "0.1.8"
@ -4636,11 +4634,11 @@ http-proxy-middleware@^0.19.1:
micromatch "^3.1.10"
http-proxy@^1.17.0:
version "1.17.0"
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a"
integrity sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==
version "1.18.1"
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
dependencies:
eventemitter3 "^3.0.0"
eventemitter3 "^4.0.0"
follow-redirects "^1.0.0"
requires-port "^1.0.0"
@ -9768,6 +9766,11 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typeface-lato@^0.0.75:
version "0.0.75"
resolved "https://registry.yarnpkg.com/typeface-lato/-/typeface-lato-0.0.75.tgz#8aa11c5611dc38416837c089b265a2abd1f7dd31"
integrity sha512-iA5uJD4PSTyIE4BDiSOexQeXkDkiJuX4Hu3wh3saJ06EB2TvJayab1Lbbmqq2je/LQv7KCQZHZmC0k4hedd8sw==
typescript-eslint-parser@^16.0.0:
version "16.0.1"
resolved "https://registry.yarnpkg.com/typescript-eslint-parser/-/typescript-eslint-parser-16.0.1.tgz#b40681c7043b222b9772748b700a000b241c031b"