mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-08 22:24:23 +08:00
Merge pull request #3682 from okriuchykhin/ok_SCI_6088
Add API endpoint for Cloning Experiments [SCI-6088]
This commit is contained in:
commit
ebfd2f7924
8 changed files with 233 additions and 25 deletions
87
app/controllers/api/service/base_controller.rb
Normal file
87
app/controllers/api/service/base_controller.rb
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api
|
||||||
|
module Service
|
||||||
|
class BaseController < ApiController
|
||||||
|
class TypeError < StandardError; end
|
||||||
|
|
||||||
|
class PermissionError < StandardError
|
||||||
|
attr_reader :klass, :mode
|
||||||
|
|
||||||
|
def initialize(klass, mode)
|
||||||
|
@klass = klass
|
||||||
|
@mode = mode
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue_from StandardError do |e|
|
||||||
|
logger.error e.message
|
||||||
|
logger.error e.backtrace.join("\n")
|
||||||
|
render_error(I18n.t('api.core.errors.general.title'),
|
||||||
|
I18n.t('api.core.errors.general.detail'),
|
||||||
|
:bad_request)
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue_from PermissionError do |e|
|
||||||
|
model = e.klass.name.underscore
|
||||||
|
render_error(
|
||||||
|
I18n.t("api.core.errors.#{e.mode}_permission.title"),
|
||||||
|
I18n.t("api.core.errors.#{e.mode}_permission.detail", model: model),
|
||||||
|
:forbidden
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue_from ActionController::ParameterMissing do |e|
|
||||||
|
render_error(
|
||||||
|
I18n.t('api.core.errors.parameter.title'), e.message, :bad_request
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue_from ActiveRecord::RecordNotFound do |e|
|
||||||
|
render_error(
|
||||||
|
I18n.t('api.core.errors.record_not_found.title'),
|
||||||
|
I18n.t('api.core.errors.record_not_found.detail',
|
||||||
|
model: e.model,
|
||||||
|
id: e.id),
|
||||||
|
:not_found
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue_from ActiveRecord::RecordInvalid do |e|
|
||||||
|
render_error(
|
||||||
|
I18n.t('api.core.errors.validation.title'), e.message, :bad_request
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue_from JWT::DecodeError,
|
||||||
|
JWT::InvalidPayload,
|
||||||
|
JWT::VerificationError,
|
||||||
|
JWT::ExpiredSignature do |e|
|
||||||
|
render_error(
|
||||||
|
I18n.t('api.core.invalid_token'), e.message, :unauthorized
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def load_team
|
||||||
|
@team = current_user.teams.find(params.require(:team_id))
|
||||||
|
raise PermissionError.new(Team, :read) unless can_read_team?(@team)
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_error(title, message, status)
|
||||||
|
logger.error message
|
||||||
|
render json: {
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
id: request.uuid,
|
||||||
|
status: Rack::Utils.status_code(status),
|
||||||
|
title: title,
|
||||||
|
detail: message
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, status: status
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
27
app/controllers/api/service/experiments_controller.rb
Normal file
27
app/controllers/api/service/experiments_controller.rb
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api
|
||||||
|
module Service
|
||||||
|
class ExperimentsController < BaseController
|
||||||
|
before_action :load_team
|
||||||
|
|
||||||
|
def clone
|
||||||
|
@project = @team.projects.find(params.require(:clone_experiment).require(:to_project_id))
|
||||||
|
raise PermissionError.new(Project, :create_project_experiments) unless can_create_project_experiments?(@project)
|
||||||
|
|
||||||
|
@experiment = Experiment.find(params.require(:clone_experiment).require(:experiment_id))
|
||||||
|
raise PermissionError.new(Experiment, :manage) unless can_clone_experiment?(@experiment)
|
||||||
|
|
||||||
|
service = Experiments::CopyExperimentAsTemplateService.call(experiment: @experiment,
|
||||||
|
project: @project,
|
||||||
|
user: current_user)
|
||||||
|
|
||||||
|
if service.succeed?
|
||||||
|
render jsonapi: service.cloned_experiment, serializer: Api::V1::ExperimentSerializer
|
||||||
|
else
|
||||||
|
render json: service.errors, status: :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,8 +4,11 @@ module Api
|
||||||
module V1
|
module V1
|
||||||
class BaseController < ApiController
|
class BaseController < ApiController
|
||||||
class TypeError < StandardError; end
|
class TypeError < StandardError; end
|
||||||
|
|
||||||
class IDMismatchError < StandardError; end
|
class IDMismatchError < StandardError; end
|
||||||
|
|
||||||
class IncludeNotSupportedError < StandardError; end
|
class IncludeNotSupportedError < StandardError; end
|
||||||
|
|
||||||
class PermissionError < StandardError
|
class PermissionError < StandardError
|
||||||
attr_reader :klass, :mode
|
attr_reader :klass, :mode
|
||||||
|
|
||||||
|
|
|
@ -200,10 +200,12 @@ class ExperimentsController < ApplicationController
|
||||||
|
|
||||||
# POST: clone_experiment(id)
|
# POST: clone_experiment(id)
|
||||||
def clone
|
def clone
|
||||||
service = Experiments::CopyExperimentAsTemplateService
|
project = current_team.projects.find(move_experiment_param)
|
||||||
.call(experiment_id: @experiment.id,
|
return render_403 unless can_create_project_experiments?(project)
|
||||||
project_id: move_experiment_param,
|
|
||||||
user_id: current_user.id)
|
service = Experiments::CopyExperimentAsTemplateService.call(experiment: @experiment,
|
||||||
|
project: project,
|
||||||
|
user: current_user)
|
||||||
|
|
||||||
if service.succeed?
|
if service.succeed?
|
||||||
flash[:success] = t('experiments.clone.success_flash',
|
flash[:success] = t('experiments.clone.success_flash',
|
||||||
|
|
|
@ -7,11 +7,11 @@ module Experiments
|
||||||
attr_reader :errors, :c_exp
|
attr_reader :errors, :c_exp
|
||||||
alias cloned_experiment c_exp
|
alias cloned_experiment c_exp
|
||||||
|
|
||||||
def initialize(experiment_id:, project_id:, user_id:)
|
def initialize(experiment:, project:, user:)
|
||||||
@exp = Experiment.find experiment_id
|
@exp = experiment
|
||||||
@project = Project.find project_id
|
@project = project
|
||||||
@user = User.find user_id
|
@user = user
|
||||||
@original_project = @exp&.project
|
@original_project = @exp.project
|
||||||
@c_exp = nil
|
@c_exp = nil
|
||||||
@errors = {}
|
@errors = {}
|
||||||
end
|
end
|
||||||
|
@ -66,23 +66,16 @@ module Experiments
|
||||||
def valid?
|
def valid?
|
||||||
unless @exp && @project && @user
|
unless @exp && @project && @user
|
||||||
@errors[:invalid_arguments] =
|
@errors[:invalid_arguments] =
|
||||||
{ 'experiment': @exp,
|
{ experiment: @exp,
|
||||||
'project': @project,
|
project: @project,
|
||||||
'user': @user }
|
user: @user }
|
||||||
.map do |key, value|
|
.map do |key, value|
|
||||||
"Can't find #{key.capitalize}" if value.nil?
|
"Can't find #{key.capitalize}" if value.nil?
|
||||||
end.compact
|
end.compact
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
if @exp.project.team.projects
|
|
||||||
.with_user_permission(@user, ProjectPermissions::EXPERIMENTS_CREATE).include?(@project)
|
|
||||||
true
|
|
||||||
else
|
|
||||||
@errors[:user_without_permissions] =
|
|
||||||
['You are not allowed to copy this experiment to this project']
|
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def track_activity
|
def track_activity
|
||||||
|
|
|
@ -668,6 +668,11 @@ Rails.application.routes.draw do
|
||||||
namespace :api, defaults: { format: 'json' } do
|
namespace :api, defaults: { format: 'json' } do
|
||||||
get 'health', to: 'api#health'
|
get 'health', to: 'api#health'
|
||||||
get 'status', to: 'api#status'
|
get 'status', to: 'api#status'
|
||||||
|
namespace :service do
|
||||||
|
resources :teams, except: %i(index new create show edit update destroy) do
|
||||||
|
post 'clone_experiment' => 'experiments#clone'
|
||||||
|
end
|
||||||
|
end
|
||||||
if Rails.configuration.x.core_api_v1_enabled
|
if Rails.configuration.x.core_api_v1_enabled
|
||||||
namespace :v1 do
|
namespace :v1 do
|
||||||
resources :teams, only: %i(index show) do
|
resources :teams, only: %i(index show) do
|
||||||
|
|
94
spec/requests/api/service/experiments_controller_spec.rb
Normal file
94
spec/requests/api/service/experiments_controller_spec.rb
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe "Api::Service::ExperimentsController", type: :request do
|
||||||
|
before :all do
|
||||||
|
@user = create(:user)
|
||||||
|
@team = create(:team, created_by: @user)
|
||||||
|
create(:user_team, user: @user, team: @team, role: 2)
|
||||||
|
|
||||||
|
@valid_project = create(:project, name: Faker::Name.unique.name, created_by: @user, team: @team)
|
||||||
|
|
||||||
|
create(:user_assignment,
|
||||||
|
assignable: @valid_project,
|
||||||
|
user: @user,
|
||||||
|
user_role: UserRole.find_by(name: I18n.t('user_roles.predefined.owner')),
|
||||||
|
assigned_by: @user)
|
||||||
|
|
||||||
|
@unaccessible_project = create(:project, name: Faker::Name.unique.name, created_by: @user, team: @team)
|
||||||
|
@unaccessible_project.user_assignments.destroy_all
|
||||||
|
|
||||||
|
@experiment = create(:experiment, created_by: @user, last_modified_by: @user, project: @valid_project)
|
||||||
|
|
||||||
|
@valid_headers =
|
||||||
|
{ 'Authorization': 'Bearer ' + generate_token(@user.id) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST clone experiment, #clone' do
|
||||||
|
before :all do
|
||||||
|
@valid_headers['Content-Type'] = 'application/json'
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:action) do
|
||||||
|
post(
|
||||||
|
api_service_team_clone_experiment_path(team_id: @valid_project.team.id),
|
||||||
|
params: request_body.to_json,
|
||||||
|
headers: @valid_headers
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when has valid params' do
|
||||||
|
let(:request_body) do
|
||||||
|
{
|
||||||
|
clone_experiment: {
|
||||||
|
experiment_id: @experiment.id,
|
||||||
|
to_project_id: @valid_project.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates new experiment' do
|
||||||
|
expect { action }.to change { Experiment.count }.by(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns status 200' do
|
||||||
|
action
|
||||||
|
expect(response).to have_http_status 200
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when has missing param' do
|
||||||
|
let(:request_body) do
|
||||||
|
{
|
||||||
|
clone_experiment: {
|
||||||
|
experiment_id: @experiment.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders 400' do
|
||||||
|
action
|
||||||
|
|
||||||
|
expect(response).to have_http_status(400)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when has wrong project' do
|
||||||
|
let(:request_body) do
|
||||||
|
{
|
||||||
|
clone_experiment: {
|
||||||
|
experiment_id: @experiment.id,
|
||||||
|
to_project_id: @unaccessible_project.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders 403' do
|
||||||
|
action
|
||||||
|
|
||||||
|
expect(response).to have_http_status(403)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -17,10 +17,7 @@ describe Experiments::CopyExperimentAsTemplateService do
|
||||||
end
|
end
|
||||||
let(:user) { create :user }
|
let(:user) { create :user }
|
||||||
let(:service_call) do
|
let(:service_call) do
|
||||||
Experiments::CopyExperimentAsTemplateService
|
Experiments::CopyExperimentAsTemplateService.call(experiment: experiment, project: new_project, user: user)
|
||||||
.call(experiment_id: experiment.id,
|
|
||||||
project_id: new_project.id,
|
|
||||||
user_id: user.id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when service call is successful' do
|
context 'when service call is successful' do
|
||||||
|
|
Loading…
Add table
Reference in a new issue