Merge branch 'ur-SCI-2926'

This commit is contained in:
Urban Rotnik 2019-01-28 11:58:35 +01:00
commit ed388db723
17 changed files with 306 additions and 77 deletions

View file

@ -350,6 +350,9 @@ Style/WhenThen:
Metrics/AbcSize:
Enabled: false
Metrics/BlockLength:
ExcludedMethods: ['describe', 'context']
Metrics/ClassLength:
Enabled: false

View file

@ -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?

View file

@ -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)

View file

@ -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,

View 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

View file

@ -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

View file

@ -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

View file

@ -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
View 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

View file

@ -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

View file

@ -1,5 +0,0 @@
FactoryBot.define do
factory :user_project do
role 'owner'
end
end

View 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

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'rails_helper'
describe Experiment, type: :model do

View file

@ -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

View file

@ -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 }

View file

@ -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)

View 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