mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-12-31 04:32:06 +08:00
Merge branch 'ur-SCI-2926'
This commit is contained in:
commit
ed388db723
17 changed files with 306 additions and 77 deletions
|
@ -350,6 +350,9 @@ Style/WhenThen:
|
|||
Metrics/AbcSize:
|
||||
Enabled: false
|
||||
|
||||
Metrics/BlockLength:
|
||||
ExcludedMethods: ['describe', 'context']
|
||||
|
||||
Metrics/ClassLength:
|
||||
Enabled: false
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ExperimentsController < ApplicationController
|
||||
include SampleActions
|
||||
include TeamsHelper
|
||||
|
@ -237,49 +239,23 @@ class ExperimentsController < ApplicationController
|
|||
|
||||
# POST: move_experiment(id)
|
||||
def move
|
||||
project = Project.find_by_id(params[:experiment].try(:[], :project_id))
|
||||
old_project = @experiment.project
|
||||
|
||||
# Try to move the experiment
|
||||
success = true
|
||||
if @experiment.moveable_projects(current_user).include?(project)
|
||||
success = @experiment.move_to_project(project)
|
||||
else
|
||||
success = false
|
||||
end
|
||||
|
||||
if success
|
||||
Activity.create(
|
||||
type_of: :move_experiment,
|
||||
project: project,
|
||||
experiment: @experiment,
|
||||
user: current_user,
|
||||
message: I18n.t(
|
||||
'activities.move_experiment',
|
||||
user: current_user.full_name,
|
||||
experiment: @experiment.name,
|
||||
project_new: project.name,
|
||||
project_original: old_project.name
|
||||
)
|
||||
)
|
||||
service = Experiments::MoveToProjectService
|
||||
.call(experiment_id: @experiment.id,
|
||||
project_id: move_experiment_param,
|
||||
user_id: current_user.id)
|
||||
|
||||
if service.succeed?
|
||||
flash[:success] = t('experiments.move.success_flash',
|
||||
experiment: @experiment.name)
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { path: canvas_experiment_url(@experiment) }, status: :ok
|
||||
end
|
||||
end
|
||||
path = canvas_experiment_url(@experiment)
|
||||
status = :ok
|
||||
else
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { message: t('experiments.move.error_flash',
|
||||
experiment:
|
||||
escape_input(@experiment.name)) },
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
message = t('experiments.move.error_flash',
|
||||
experiment: escape_input(@experiment.name))
|
||||
status = :unprocessable_entity
|
||||
end
|
||||
|
||||
render json: { message: message, path: path }, status: status
|
||||
end
|
||||
|
||||
def module_archive
|
||||
|
@ -348,6 +324,10 @@ class ExperimentsController < ApplicationController
|
|||
params.require(:experiment).permit(:name, :description, :archived)
|
||||
end
|
||||
|
||||
def move_experiment_param
|
||||
params.require(:experiment).require(:project_id)
|
||||
end
|
||||
|
||||
def load_projects_tree
|
||||
# Switch to correct team
|
||||
current_team_switch(@experiment.project.team) unless @experiment.project.nil?
|
||||
|
|
|
@ -255,24 +255,6 @@ class Experiment < ApplicationRecord
|
|||
clone
|
||||
end
|
||||
|
||||
def move_to_project(project)
|
||||
self.project = project
|
||||
|
||||
my_modules.each do |m|
|
||||
new_tags = []
|
||||
m.tags.each do |t|
|
||||
new_tags << t.deep_clone_to_project(project)
|
||||
end
|
||||
m.my_module_tags.destroy_all
|
||||
|
||||
project.tags << new_tags
|
||||
m.tags << new_tags
|
||||
end
|
||||
result = save
|
||||
touch(:workflowimg_updated_at) if result
|
||||
result
|
||||
end
|
||||
|
||||
# Get projects where user is either owner or user in the same team
|
||||
# as this experiment
|
||||
def projects_with_role_above_user(current_user)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Tag < ApplicationRecord
|
||||
include SearchableModel
|
||||
|
||||
|
@ -48,7 +50,10 @@ class Tag < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def deep_clone_to_project(project)
|
||||
def clone_to_project_or_return_existing(project)
|
||||
tag = Tag.find_by(project: project, name: name, color: color)
|
||||
return tag if tag
|
||||
|
||||
Tag.create(
|
||||
name: name,
|
||||
color: color,
|
||||
|
|
89
app/services/experiments/move_to_project_service.rb
Normal file
89
app/services/experiments/move_to_project_service.rb
Normal file
|
@ -0,0 +1,89 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Experiments
|
||||
class MoveToProjectService
|
||||
extend Service
|
||||
|
||||
attr_reader :errors
|
||||
|
||||
def initialize(experiment_id:, project_id:, user_id:)
|
||||
@exp = Experiment.find experiment_id
|
||||
@project = Project.find project_id
|
||||
@user = User.find user_id
|
||||
@original_project = @exp&.project
|
||||
@errors = {}
|
||||
end
|
||||
|
||||
def call
|
||||
return self unless valid?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
@exp.project = @project
|
||||
|
||||
@exp.my_modules.each do |m|
|
||||
new_tags = m.tags.map do |t|
|
||||
t.clone_to_project_or_return_existing(@project)
|
||||
end
|
||||
m.my_module_tags.delete_all
|
||||
m.tags = new_tags
|
||||
end
|
||||
|
||||
raise ActiveRecord::Rollback unless @exp.save
|
||||
end
|
||||
|
||||
@errors.merge!(@exp.errors.to_hash) unless @exp.valid?
|
||||
|
||||
track_activity if succeed?
|
||||
self
|
||||
end
|
||||
|
||||
def succeed?
|
||||
@errors.none?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid?
|
||||
unless @exp && @project && @user
|
||||
@errors[:invalid_arguments] =
|
||||
{ 'experiment': @exp,
|
||||
'project': @project,
|
||||
'user': @user }
|
||||
.map do |key, value|
|
||||
"Can't find #{key.capitalize}" if value.nil?
|
||||
end.compact
|
||||
return false
|
||||
end
|
||||
|
||||
e = Experiment.find_by(name: @exp.name, project: @project)
|
||||
|
||||
if e
|
||||
@errors[:project_with_exp] =
|
||||
['Project already contains experiment with this name']
|
||||
false
|
||||
elsif !@exp.moveable_projects(@user).include?(@project)
|
||||
@errors[:target_project_not_valid] =
|
||||
['Experiment cannot be moved to this project']
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def track_activity
|
||||
Activity.create(
|
||||
type_of: :move_experiment,
|
||||
project: @project,
|
||||
experiment: @exp,
|
||||
user: @user,
|
||||
message: I18n.t(
|
||||
'activities.move_experiment',
|
||||
user: @user,
|
||||
experiment: @exp.name,
|
||||
project_new: @project.name,
|
||||
project_original: @original_project.name
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -54,11 +54,15 @@ describe ProjectsController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
# rubocop:disable Security/Eval
|
||||
# rubocop:disable Style/EvalWithLocation
|
||||
(1..PROJECTS_CNT).each do |i|
|
||||
let!("user_projects_#{i}") do
|
||||
create :user_project, project: eval("project_#{i}"), user: user
|
||||
create :user_project, :owner, project: eval("project_#{i}"), user: user
|
||||
end
|
||||
end
|
||||
# rubocop:enable Security/Eval
|
||||
# rubocop:enable Style/EvalWithLocation
|
||||
|
||||
describe '#index' do
|
||||
context 'in JSON format' do
|
||||
|
|
|
@ -12,7 +12,7 @@ FactoryBot.define do
|
|||
project { create :project, created_by: user }
|
||||
factory :experiment_with_tasks do
|
||||
after(:create) do |e|
|
||||
create_list :my_module, 3, experiment: e
|
||||
create_list :my_module, 3, :with_tag, experiment: e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,5 +8,8 @@ FactoryBot.define do
|
|||
workflow_order { MyModule.where(experiment_id: experiment.id).count + 1 }
|
||||
experiment
|
||||
my_module_group { create :my_module_group, experiment: experiment }
|
||||
trait :with_tag do
|
||||
tags { create_list :tag, 3, project: experiment.project }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
9
spec/factories/tags.rb
Normal file
9
spec/factories/tags.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :tag do
|
||||
sequence(:name) { |n| "My tag-#{n}" }
|
||||
project
|
||||
color { Faker::Color.hex_color }
|
||||
end
|
||||
end
|
|
@ -6,5 +6,8 @@ FactoryBot.define do
|
|||
sequence(:name) { |n| "My team-#{n}" }
|
||||
description { Faker::Lorem.sentence }
|
||||
space_taken { 1048576 }
|
||||
trait :with_members do
|
||||
users { create_list :user, 3 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
FactoryBot.define do
|
||||
factory :user_project do
|
||||
role 'owner'
|
||||
end
|
||||
end
|
21
spec/factories/user_projects.rb
Normal file
21
spec/factories/user_projects.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :user_project do
|
||||
user
|
||||
project
|
||||
assigned_by { user }
|
||||
trait :owner do
|
||||
role { UserProject.roles[:owner] }
|
||||
end
|
||||
trait :normal_user do
|
||||
role { UserProject.roles[:normal_user] }
|
||||
end
|
||||
trait :technician do
|
||||
role { UserProject.roles[:technician] }
|
||||
end
|
||||
trait :viewer do
|
||||
role { UserProject.roles[:viewer] }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Experiment, type: :model do
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Tag, type: :model do
|
||||
|
@ -23,15 +25,46 @@ describe Tag, type: :model do
|
|||
it { should have_many :my_modules }
|
||||
end
|
||||
|
||||
describe 'Should be a valid object' do
|
||||
it { should validate_presence_of :name }
|
||||
it { should validate_presence_of :color }
|
||||
it { should validate_presence_of :project }
|
||||
it do
|
||||
should validate_length_of(:name).is_at_most(Constants::NAME_MAX_LENGTH)
|
||||
describe 'Validations' do
|
||||
describe '#name' do
|
||||
it { should validate_presence_of :name }
|
||||
it do
|
||||
should validate_length_of(:name)
|
||||
.is_at_most(Constants::NAME_MAX_LENGTH)
|
||||
end
|
||||
end
|
||||
it do
|
||||
should validate_length_of(:color).is_at_most(Constants::COLOR_MAX_LENGTH)
|
||||
|
||||
describe '#color' do
|
||||
it { should validate_presence_of :color }
|
||||
it do
|
||||
should validate_length_of(:color)
|
||||
.is_at_most(Constants::COLOR_MAX_LENGTH)
|
||||
end
|
||||
end
|
||||
describe '#projects' do
|
||||
it { should validate_presence_of :project }
|
||||
end
|
||||
end
|
||||
describe '.clone_to_project_or_return_existing' do
|
||||
let(:project) { create :project }
|
||||
let(:tag) { create :tag }
|
||||
|
||||
context 'when tag does not exits' do
|
||||
it 'does create new tag for project' do
|
||||
expect do
|
||||
tag.clone_to_project_or_return_existing(project)
|
||||
end.to(change { Tag.where(project_id: project.id).count })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when tag already exists' do
|
||||
it 'return existing tag for project' do
|
||||
Tag.create(name: tag.name, color: tag.color, project: project)
|
||||
|
||||
expect do
|
||||
tag.clone_to_project_or_return_existing(project)
|
||||
end.to_not(change { Tag.where(project_id: project.id).count })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -187,7 +187,9 @@ describe User, type: :model do
|
|||
describe '#last_activities' do
|
||||
let!(:user) { create :user }
|
||||
let!(:project) { create :project }
|
||||
let!(:user_projects) { create :user_project, project: project, user: user }
|
||||
let!(:user_projects) do
|
||||
create :user_project, :viewer, project: project, user: user
|
||||
end
|
||||
let!(:activity_one) { create :activity, user: user, project: project }
|
||||
let!(:activity_two) { create :activity, user: user, project: project }
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@ require 'rails_helper'
|
|||
|
||||
RSpec.describe 'Api::V1::UsersController', type: :request do
|
||||
before :all do
|
||||
@user1 = create(:user, email: Faker::Internet.unique.email)
|
||||
@user2 = create(:user, email: Faker::Internet.unique.email)
|
||||
@user3 = create(:user, email: Faker::Internet.unique.email)
|
||||
@user1 = create(:user)
|
||||
@user2 = create(:user)
|
||||
@user3 = create(:user)
|
||||
@team1 = create(:team, created_by: @user1)
|
||||
@team2 = create(:team, created_by: @user2)
|
||||
@team3 = create(:team, created_by: @user3)
|
||||
|
@ -40,7 +40,7 @@ RSpec.describe 'Api::V1::UsersController', type: :request do
|
|||
|
||||
it 'When invalid request, non existing user' do
|
||||
hash_body = nil
|
||||
get api_v1_user_path(id: 123), headers: @valid_headers
|
||||
get api_v1_user_path(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)
|
||||
|
|
98
spec/services/experiments/move_to_project_service_spec.rb
Normal file
98
spec/services/experiments/move_to_project_service_spec.rb
Normal file
|
@ -0,0 +1,98 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Experiments::MoveToProjectService do
|
||||
let(:team) { create :team, :with_members }
|
||||
let(:project) do
|
||||
create :project, team: team, user_projects: []
|
||||
end
|
||||
let(:new_project) do
|
||||
create :project, team: team, user_projects: [user_project2]
|
||||
end
|
||||
let(:experiment) do
|
||||
create :experiment_with_tasks, name: 'MyExp', project: project
|
||||
end
|
||||
let(:user) { create :user }
|
||||
let(:user_project2) { create :user_project, :normal_user, user: user }
|
||||
let(:service_call) do
|
||||
Experiments::MoveToProjectService.call(experiment_id: experiment.id,
|
||||
project_id: new_project.id,
|
||||
user_id: user.id)
|
||||
end
|
||||
|
||||
context 'when call with valid params' do
|
||||
it 'unnasigns experiment from project' do
|
||||
service_call
|
||||
|
||||
expect(project.experiments.pluck(:id)).to_not include(experiment.id)
|
||||
end
|
||||
|
||||
it 'assigns experiment to new_project' do
|
||||
service_call
|
||||
|
||||
expect(new_project.experiments.pluck(:id)).to include(experiment.id)
|
||||
end
|
||||
|
||||
it 'copies tags to new project' do
|
||||
expect { service_call }.to(change { new_project.tags.count })
|
||||
end
|
||||
|
||||
it 'leaves tags on an old project' do
|
||||
experiment # explicit call to create tags
|
||||
expect { service_call }.not_to(change { project.tags.count })
|
||||
end
|
||||
|
||||
it 'sets new project tags to modules' do
|
||||
service_call
|
||||
new_tags = experiment.my_modules.map { |m| m.tags.map { |t| t } }.flatten
|
||||
tags_project_id = new_tags.map(&:project_id).uniq.first
|
||||
|
||||
expect(tags_project_id).to be == new_project.id
|
||||
end
|
||||
|
||||
it 'adds Activity record' do
|
||||
expect { service_call }.to(change { Activity.all.count })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when call with invalid params' do
|
||||
it 'returns an error when can\'t find user and project' do
|
||||
allow(Project).to receive(:find).and_return(nil)
|
||||
allow(User).to receive(:find).and_return(nil)
|
||||
|
||||
expect(service_call.errors).to have_key(:invalid_arguments)
|
||||
end
|
||||
|
||||
it 'returns an error on validate' do
|
||||
FactoryBot.create :experiment, project: new_project, name: 'MyExp'
|
||||
expect(service_call.succeed?).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns an error on saving' do
|
||||
expect_any_instance_of(Experiments::MoveToProjectService)
|
||||
.to receive(:valid?)
|
||||
.and_return(true)
|
||||
FactoryBot.create :experiment, project: new_project, name: 'MyExp'
|
||||
|
||||
expect(service_call.succeed?).to be_falsey
|
||||
end
|
||||
|
||||
it 'rollbacks cloned tags after unsucessful save' do
|
||||
expect_any_instance_of(Experiments::MoveToProjectService)
|
||||
.to receive(:valid?)
|
||||
.and_return(true)
|
||||
FactoryBot.create :experiment, project: new_project, name: 'MyExp'
|
||||
experiment
|
||||
|
||||
expect { service_call }.not_to(change { Tag.count })
|
||||
end
|
||||
|
||||
it 'returns error if teams is not the same' do
|
||||
t = create :team, :with_members
|
||||
project.update(team: t)
|
||||
|
||||
expect(service_call.errors).to have_key(:target_project_not_valid)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue