implementation of import wip

This commit is contained in:
Karol
2019-08-20 18:37:17 +02:00
parent 818064267c
commit 017644c4a5
15 changed files with 212 additions and 52 deletions

View File

@ -36,6 +36,7 @@ gem 'webpacker'
gem 'rest-client' gem 'rest-client'
gem 'rubyzip' gem 'rubyzip'
gem 'mnemosyne-ruby' gem 'mnemosyne-ruby'
gem 'proforma', path: '../proforma'
gem 'whenever', require: false gem 'whenever', require: false
gem 'rails-timeago' gem 'rails-timeago'

View File

@ -7,6 +7,15 @@ GIT
rack (>= 1.5.0) rack (>= 1.5.0)
websocket (>= 1.1.0) websocket (>= 1.1.0)
PATH
remote: ../proforma
specs:
proforma (0.1.0)
activemodel (~> 5.2.3)
activesupport (~> 5.2.3)
nokogiri (~> 1.10.2)
rubyzip (~> 1.2.2)
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
@ -430,6 +439,7 @@ DEPENDENCIES
nyan-cat-formatter nyan-cat-formatter
pagedown-bootstrap-rails pagedown-bootstrap-rails
pg pg
proforma!
pry-byebug pry-byebug
puma puma
pundit pundit

View File

@ -108,27 +108,34 @@ class ExercisesController < ApplicationController
end end
def import_proforma_xml def import_proforma_xml
begin # begin
user = user_for_oauth2_request() # user = user_for_oauth2_request
exercise = Exercise.new # exercise = Exercise.new
request_body = request.body.read # request_body = request.body.read # needs to be some kind of a zip file
exercise.from_proforma_xml(request_body)
exercise.user = user tempfile = Tempfile.new('codeharbor_import.zip')
saved = exercise.save tempfile.write request.body.read.force_encoding('UTF-8')
if saved tempfile.rewind
render :text => 'SUCCESS', :status => 200
else exercise = ProformaService::Import.call(zip: tempfile, user: user_for_oauth2_request)
logger.info(exercise.errors.full_messages) # exercise.from_proforma_xml(request_body)
render :text => 'Invalid exercise', :status => 400 # exercise.user = user
end # saved = exercise.save
rescue => error if exercise.save
if error.class == Hash # render text: 'SUCCESS', status: 200
render :text => error.message, :status => error.status render json: {status: 201}
else else
raise error logger.info(exercise.errors.full_messages)
render :text => '', :status => 500 render json: {status: 400}
end
end end
# rescue => error
# if error.class == Hash
# render :text => error.message, :status => error.status
# else
# raise error
# render :text => '', :status => 500
# end
# end
end end
def user_for_oauth2_request def user_for_oauth2_request
@ -142,7 +149,7 @@ class ExercisesController < ApplicationController
raise ({status: 401, message: 'No token in Authorization header'}) raise ({status: 401, message: 'No token in Authorization header'})
end end
user = user_by_code_harbor_token(oauth2Token) user = user_by_codeharbor_token(oauth2Token)
if user == nil if user == nil
raise ({status: 401, message: 'Unknown OAuth2 token'}) raise ({status: 401, message: 'Unknown OAuth2 token'})
end end
@ -151,13 +158,11 @@ class ExercisesController < ApplicationController
end end
private :user_for_oauth2_request private :user_for_oauth2_request
def user_by_code_harbor_token(oauth2Token) def user_by_codeharbor_token(oauth2_token)
link = CodeHarborLink.where(:oauth2token => oauth2Token)[0] link = CodeharborLink.where(oauth2token: oauth2_token)[0]
if link != nil link&.user
return link.user
end
end end
private :user_by_code_harbor_token private :user_by_codeharbor_token
def exercise_params def exercise_params
params[:exercise].permit(:description, :execution_environment_id, :file_id, :instructions, :public, :hide_file_tree, :allow_file_creation, :allow_auto_completion, :title, :expected_difficulty, files_attributes: file_attributes, :tag_ids => []).merge(user_id: current_user.id, user_type: current_user.class.name) if params[:exercise].present? params[:exercise].permit(:description, :execution_environment_id, :file_id, :instructions, :public, :hide_file_tree, :allow_file_creation, :allow_auto_completion, :title, :expected_difficulty, files_attributes: file_attributes, :tag_ids => []).merge(user_id: current_user.id, user_type: current_user.class.name) if params[:exercise].present?

View File

@ -1,2 +0,0 @@
module CodeHarborLinksHelper
end

View File

@ -1,13 +0,0 @@
class CodeHarborLink < ApplicationRecord
validates :oauth2token, presence: true
validates :user_id, presence: true
belongs_to :internal_user, foreign_key: :user_id
alias_method :user, :internal_user
alias_method :user=, :internal_user=
def to_s
oauth2token
end
end

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
class CodeharborLink < ApplicationRecord
validates :oauth2token, presence: true
validates :user_id, presence: true
belongs_to :user, foreign_key: :user_id, class_name: 'InternalUser'
def to_s
oauth2token
end
end

View File

@ -31,7 +31,7 @@ class Exercise < ApplicationRecord
validate :valid_main_file? validate :valid_main_file?
validates :description, presence: true validates :description, presence: true
validates :execution_environment_id, presence: true # validates :execution_environment_id, presence: true # TODO make this conditional - but based on what?
validates :public, boolean_presence: true validates :public, boolean_presence: true
validates :title, presence: true validates :title, presence: true
validates :token, presence: true, uniqueness: true validates :token, presence: true, uniqueness: true
@ -49,7 +49,7 @@ class Exercise < ApplicationRecord
0 0
end end
end end
def finishers_percentage def finishers_percentage
if users.distinct.count != 0 if users.distinct.count != 0
(100.0 / users.distinct.count * finishers.count).round(2) (100.0 / users.distinct.count * finishers.count).round(2)

View File

@ -1,3 +0,0 @@
class CodeHarborLinkPolicy < AdminOnlyPolicy
end

View File

@ -0,0 +1,3 @@
class CodeharborLinkPolicy < AdminOnlyPolicy
end

View File

@ -0,0 +1,82 @@
# frozen_string_literal: true
module ProformaService
class ConvertTaskToExercise < ServiceBase
def initialize(task:, user:, exercise: nil)
@task = task
@user = user
@exercise = exercise || Exercise.new
end
def execute
import_exercise
@exercise
end
private
def import_exercise
@exercise.assign_attributes(
user: @user,
title: @task.title,
description: @task.description,
instructions: @task.internal_description,
# exercise_files: task_files.values,
# execution_environment: execution_environment,
# tests: tests,
# state_list: @exercise.persisted? ? 'updated' : 'new'
)
end
def task_files
@task_files ||= Hash[
@task.all_files.reject { |file| file.id == 'ms-placeholder-file' }.map do |task_file|
[task_file.id, exercise_file_from_task_file(task_file)]
end
]
end
def exercise_file_from_task_file(task_file)
ExerciseFile.new({
full_file_name: task_file.filename,
read_only: task_file.usage_by_lms.in?(%w[display download]),
hidden: task_file.visible == 'no',
role: task_file.internal_description
}.tap do |params|
if task_file.binary
params[:attachment] = file_base64(task_file)
params[:attachment_file_name] = task_file.filename
params[:attachment_content_type] = task_file.mimetype
else
params[:content] = task_file.content
end
end)
end
def file_base64(file)
"data:#{file.mimetype || 'image/jpeg'};base64,#{Base64.encode64(file.content)}"
end
def tests
@task.tests.map do |test_object|
Test.new(
feedback_message: test_object.meta_data['feedback-message'],
testing_framework: TestingFramework.where(
name: test_object.meta_data['testing-framework'],
version: test_object.meta_data['testing-framework-version']
).first_or_initialize,
exercise_file: test_file(test_object)
)
end
end
def test_file(test_object)
task_files.delete(test_object.files.first.id).tap { |file| file.purpose = 'test' }
end
def execution_environment
ExecutionEnvironment.last
# ExecutionEnvironment.where(language: @task.proglang[:name], version: @task.proglang[:version]).first_or_initialize
end
end
end

View File

@ -0,0 +1,58 @@
# frozen_string_literal: true
module ProformaService
class Import < ServiceBase
def initialize(zip:, user:)
@zip = zip
@user = user
end
def execute
if single_task?
importer = Proforma::Importer.new(@zip)
@task = importer.perform
exercise = ConvertTaskToExercise.call(task: @task, user: @user)
exercise.save!
exercise
else
import_multi
end
end
private
def import_multi
Zip::File.open(@zip.path) do |zip_file|
zip_files = zip_file.filter { |entry| entry.name.match?(/\.zip$/) }
begin
zip_files.map! do |entry|
store_zip_entry_in_tempfile entry
end
zip_files.map do |proforma_file|
Import.call(zip: proforma_file, user: @user)
end
ensure
zip_files.each(&:unlink)
end
end
end
def store_zip_entry_in_tempfile(entry)
tempfile = Tempfile.new(entry.name)
tempfile.write entry.get_input_stream.read.force_encoding('UTF-8')
tempfile.rewind
tempfile
end
def single_task?
filenames = Zip::File.open(@zip.path) do |zip_file|
zip_file.map(&:name)
end
filenames.select { |f| f[/\.xml$/] }.any?
# rescue Zip::Error
# raise Proforma::InvalidZip
end
end
end

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class ServiceBase
def self.call(*args)
new(*args).execute
end
end

View File

@ -19,5 +19,5 @@
models: [ErrorTemplate, ErrorTemplateAttribute], cached: true) models: [ErrorTemplate, ErrorTemplateAttribute], cached: true)
= render('navigation_submenu', title: t('navigation.sections.files'), models: [FileType, FileTemplate], = render('navigation_submenu', title: t('navigation.sections.files'), models: [FileType, FileTemplate],
cached: true) cached: true)
= render('navigation_submenu', title: t('navigation.sections.integrations'), models: [Consumer, CodeHarborLink], = render('navigation_submenu', title: t('navigation.sections.integrations'), models: [Consumer],
cached: true) cached: true)

View File

@ -3,15 +3,15 @@ Rails.application.configure do
config.webpacker.check_yarn_integrity = true config.webpacker.check_yarn_integrity = true
# Settings specified here will take precedence over those in config/application.rb. # Settings specified here will take precedence over those in config/application.rb.
config.web_console.whitelisted_ips = '192.168.0.0/16' config.web_console.whitelisted_ips = '192.168.0.0/16'
# In the development environment your application's code is reloaded on # In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development # every request. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes. # since you don't have to restart the web server when you make code changes.
config.cache_classes = false config.cache_classes = false
# Do not eager load code on boot. # Do not eager load code on boot.
config.eager_load = false config.eager_load = true
# Show full error reports. # Show full error reports.
config.consider_all_requests_local = true config.consider_all_requests_local = true

View File

@ -13,7 +13,7 @@ Rails.application.routes.draw do
get 'by_file_type/:file_type_id', as: :by_file_type, action: :by_file_type get 'by_file_type/:file_type_id', as: :by_file_type, action: :by_file_type
end end
end end
resources :code_harbor_links # resources :code_harbor_links
resources :request_for_comments do resources :request_for_comments do
member do member do
get :mark_as_solved, defaults: { format: :json } get :mark_as_solved, defaults: { format: :json }