diff --git a/app/assets/javascripts/exercises.js.erb b/app/assets/javascripts/exercises.js.erb
index 26feb6aa..89e5cf3a 100644
--- a/app/assets/javascripts/exercises.js.erb
+++ b/app/assets/javascripts/exercises.js.erb
@@ -208,6 +208,7 @@ $(document).on('turbolinks:load', function() {
}
});
};
+
var old_execution_environment = $('#exercise_execution_environment_id').val();
var observeExecutionEnvironment = function() {
$('#exercise_execution_environment_id').on('change', function(){
@@ -238,20 +239,25 @@ $(document).on('turbolinks:load', function() {
};
- var observeExportStartButtons = function(){
+ var observeExportButtons = function(){
$('.export-start').on('click', function(e){
e.preventDefault();
$('#export-modal').modal({
height: 250
});
$('#export-modal').modal('show');
- exportExerciseStart($(this).attr('data-exercise-id'));
+ exportExerciseStart($(this).data().exerciseId);
+ });
+ $('body').on('click', '.export-retry-button', function(){
+ exportExerciseStart($(this).data().exerciseId);
+ });
+ $('body').on('click', '.export-action', function(){
+ exportExerciseConfirm($(this).data().exerciseId, $(this).data().exportType);
});
}
var exportExerciseStart = function(exerciseID) {
var $exerciseDiv = $('#export-exercise');
- // var accountLinkID = $exerciseDiv.attr('data-account-link');
var $messageDiv = $exerciseDiv.children('.export-message');
var $actionsDiv = $exerciseDiv.children('.export-exercise-actions');
@@ -261,9 +267,6 @@ $(document).on('turbolinks:load', function() {
return $.ajax({
type: 'POST',
url: '/exercises/' + exerciseID + '/export_external_check',
- // data: {
- // account_link: accountLinkID
- // },
dataType: 'json',
success: function(response) {
if (response.error) {
@@ -279,6 +282,37 @@ $(document).on('turbolinks:load', function() {
});
};
+ var exportExerciseConfirm = function(exerciseID, pushType) {
+ var $exerciseDiv = $('#export-exercise');
+ var $messageDiv = $exerciseDiv.children('.export-message');
+ var $actionsDiv = $exerciseDiv.children('.export-exercise-actions');
+
+ return $.ajax({
+ type: 'POST',
+ url: '/exercises/' + exerciseID + '/export_external_confirm',
+ data: {
+ push_type: pushType
+ },
+ dataType: 'json',
+ success: function(response) {
+ $messageDiv.html(response.message)
+ $actionsDiv.html(response.actions);
+
+ if(response.status == 'success') {
+ $messageDiv.addClass('export-success');
+ setTimeout((function() {
+ $('#export-modal').modal('hide');
+ $messageDiv.html('').removeClass('export-success');
+ }), 3000);
+ } else {
+ $messageDiv.addClass('export-failure');
+ }
+ },
+ error: function(a, b, c) {
+ return alert('error:' + c);
+ }
+ });
+ };
var overrideTextareaTabBehavior = function() {
$('.form-group textarea[name$="[content]"]').on('keydown', function(event) {
@@ -335,7 +369,7 @@ $(document).on('turbolinks:load', function() {
// ignore tags table since it is in the dom before other tables
if ($('table:not(#tags-table)').isPresent()) {
enableBatchUpdate();
- observeExportStartButtons();
+ observeExportButtons();
} else if ($('.edit_exercise, .new_exercise').isPresent()) {
execution_environments = $('form').data('execution-environments');
file_types = $('form').data('file-types');
diff --git a/app/assets/stylesheets/exercises.css.scss b/app/assets/stylesheets/exercises.css.scss
index bc611d17..1208c846 100644
--- a/app/assets/stylesheets/exercises.css.scss
+++ b/app/assets/stylesheets/exercises.css.scss
@@ -192,6 +192,14 @@ a.file-heading {
flex-grow: 1;
font-size: 12px;
padding-right: 5px;
+ word-wrap: break-word;
+}
+.export-message + :empty {
+ max-width: 100%;
+}
+
+.export-exercise-actions:empty {
+ display: none;
}
.export-exercise-actions {
@@ -203,3 +211,13 @@ a.file-heading {
font-size: 12px;
width: 100%;
}
+
+.export-success {
+ color: darkgreen;
+ font-size: 12pt;
+ font-weight: 600;
+}
+
+.export-failure {
+ color: darkred;
+}
diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb
index 580c8511..17effa54 100644
--- a/app/controllers/exercises_controller.rb
+++ b/app/controllers/exercises_controller.rb
@@ -7,14 +7,14 @@ class ExercisesController < ApplicationController
before_action :handle_file_uploads, only: [:create, :update]
before_action :set_execution_environments, only: [:create, :edit, :new, :update]
- before_action :set_exercise_and_authorize, only: MEMBER_ACTIONS + [:push_proforma_xml, :clone, :implement, :working_times, :intervention, :search, :run, :statistics, :submit, :reload, :feedback, :study_group_dashboard, :export_external_check]
+ before_action :set_exercise_and_authorize, only: MEMBER_ACTIONS + [:push_proforma_xml, :clone, :implement, :working_times, :intervention, :search, :run, :statistics, :submit, :reload, :feedback, :study_group_dashboard, :export_external_check, :export_external_confirm]
before_action :set_external_user_and_authorize, only: [:statistics]
before_action :set_file_types, only: [:create, :edit, :new, :update]
before_action :set_course_token, only: [:implement]
- skip_before_action :verify_authenticity_token, only: [:import_proforma_xml, :import_uuid_check]
- skip_after_action :verify_authorized, only: [:import_proforma_xml, :import_uuid_check]
- skip_after_action :verify_policy_scoped, only: [:import_proforma_xml, :import_uuid_check], raise: false
+ skip_before_action :verify_authenticity_token, only: [:import_proforma_xml, :import_uuid_check, :export_external_confirm]
+ skip_after_action :verify_authorized, only: [:import_proforma_xml, :import_uuid_check, :export_external_confirm]
+ skip_after_action :verify_policy_scoped, only: [:import_proforma_xml, :import_uuid_check, :export_external_confirm], raise: false
def authorize!
authorize(@exercise || @exercises)
@@ -141,8 +141,8 @@ class ExercisesController < ApplicationController
end
response_hash = JSON.parse(response.body, symbolize_names: true)
message = response_hash[:message]
- rescue Faraday::ClientError
- message = 'an error occured'
+ rescue Faraday::Error => e
+ message = t('exercises.export_codeharbor.error', message: e.message)
error = true
end
@@ -154,15 +154,45 @@ class ExercisesController < ApplicationController
exercise: @exercise,
exercise_found: response_hash[:exercise_found],
update_right: response_hash[:update_right],
- error: error
+ error: error,
+ exported: false
}
)
}, status: 200
end
+ def export_external_confirm
+ push_type = params[:push_type]
+
+ return render :fail unless %w[create_new export].include? push_type
+
+ @exercise.uuid = SecureRandom.uuid if push_type == 'create_new'
+
+ error = ExerciseService::PushExternal.call(
+ zip: ProformaService::ExportTask.call(exercise: @exercise),
+ codeharbor_link: current_user.codeharbor_link
+ )
+ if error.nil?
+ render json: {
+ status: 'success',
+ message: t('exercises.export_codeharbor.successfully_exported', id: @exercise.id, title: @exercise.title),
+ actions: render_to_string(partial: 'export_actions', locals: {exercise: @exercise, exported: true, error: error})
+ }
+ # @exercise, notice: t('controllers.exercise.push_external_notice', account_link: account_link.readable)
+ else
+ # logger.debug(error)
+ render json: {
+ status: 'fail',
+ message: t('exercises.export_codeharbor.export_failed', id: @exercise.id, title: @exercise.title, error: error),
+ actions: render_to_string(partial: 'export_actions', locals: {exercise: @exercise, exported: true, error: error})
+ }
+ # redirect_to @exercise, alert: t('controllers.account_links.not_working', account_link: account_link.readable)
+ end
+ end
+
def import_uuid_check
- user = user_for_oauth2_request
+ user = user_from_api_key
return render json: {}, status: 401 if user.nil?
uuid = params[:uuid]
@@ -179,7 +209,7 @@ class ExercisesController < ApplicationController
tempfile.write request.body.read.force_encoding('UTF-8')
tempfile.rewind
- user = user_for_oauth2_request
+ user = user_from_api_key
return render json: {}, status: 401 if user.nil?
exercise = nil
@@ -192,15 +222,15 @@ class ExercisesController < ApplicationController
render json: {}, status: 400
end
- def user_for_oauth2_request
+ def user_from_api_key
authorization_header = request.headers['Authorization']
- oauth2_token = authorization_header&.split(' ')&.second
- user_by_codeharbor_token(oauth2_token)
+ api_key = authorization_header&.split(' ')&.second
+ user_by_codeharbor_token(api_key)
end
- private :user_for_oauth2_request
+ private :user_from_api_key
- def user_by_codeharbor_token(oauth2_token)
- link = CodeharborLink.where(oauth2token: oauth2_token)[0]
+ def user_by_codeharbor_token(api_key)
+ link = CodeharborLink.find_by_api_key(api_key)
link&.user
end
private :user_by_codeharbor_token
diff --git a/app/policies/exercise_policy.rb b/app/policies/exercise_policy.rb
index e0a8d2a5..3d286271 100644
--- a/app/policies/exercise_policy.rb
+++ b/app/policies/exercise_policy.rb
@@ -7,7 +7,7 @@ class ExercisePolicy < AdminOrAuthorPolicy
define_method(action) { admin? || teacher? }
end
- [:clone?, :destroy?, :edit?, :statistics?, :update?, :feedback?, :push_proforma_xml?, :export_external_check?].each do |action|
+ [:clone?, :destroy?, :edit?, :statistics?, :update?, :feedback?, :push_proforma_xml?, :export_external_check?, :export_external_confirm?].each do |action|
define_method(action) { admin? || author? }
end
diff --git a/app/services/exercise_service/push_external.rb b/app/services/exercise_service/push_external.rb
index dfa54468..3c72ac22 100644
--- a/app/services/exercise_service/push_external.rb
+++ b/app/services/exercise_service/push_external.rb
@@ -9,19 +9,22 @@ module ExerciseService
end
def execute
- oauth2_client = OAuth2::Client.new(@codeharbor_link.client_id, @codeharbor_link.client_secret, site: CODEHARBOR_PUSH_LINK)
- oauth2_token = @codeharbor_link[:oauth2token]
- token = OAuth2::AccessToken.from_hash(oauth2_client, access_token: oauth2_token)
body = @zip.string
begin
- token.post(
- CODEHARBOR_PUSH_LINK,
- body: body,
- headers: {'Content-Type' => 'application/zip', 'Content-Length' => body.length.to_s}
- )
- return nil
+ conn = Faraday.new(url: CODEHARBOR_PUSH_LINK) do |faraday|
+ faraday.adapter Faraday.default_adapter
+ end
+
+ response = conn.post do |request|
+ request.headers['Content-Type'] = 'application/zip'
+ request.headers['Content-Length'] = body.length.to_s
+ request.headers['Authorization'] = 'Bearer ' + @codeharbor_link.api_key
+ request.body = body
+ end
+
+ return response.success? ? nil : response.body
rescue StandardError => e
- return e
+ return e.message
end
end
end
diff --git a/app/views/exercises/_export_actions.html.slim b/app/views/exercises/_export_actions.html.slim
index 4274ddc4..6b5a0554 100644
--- a/app/views/exercises/_export_actions.html.slim
+++ b/app/views/exercises/_export_actions.html.slim
@@ -1,20 +1,18 @@
- if error
- = button_tag type: 'button', class:'btn btn-primary pull-right export-button', onclick: "exportExerciseStart(#{exercise.id})" do
+ = button_tag type: 'button', class:'btn btn-primary pull-right export-button export-retry-button', data: {exercise_id: exercise.id} do
i.fa.fa-refresh.confirm-icon
= ' Retry'
- else
- - if exercise_found
+ - unless exported
- if update_right
- = button_tag type: 'button', class:'btn btn-primary pull-right export-action export-button', data: {'export-type' => 'export'} do
+ = button_tag type: 'button', class:'btn btn-primary pull-right export-action export-button', data: {exercise_id: exercise.id, export_type: 'export'} do
i.fa.fa-check.confirm-icon
- = ' Overwrite'
- = button_tag type: 'button', class:'btn btn-primary pull-right export-action export-button', data: {'export-type' => 'create_new'} do
- i.fa.fa-check.confirm-icon-alt
- = ' Create new'
- - else
- = button_tag type: 'button', class:'btn btn-primary pull-right export-action export-button', data: {'export-type' => 'export'} do
- i.fa.fa-check.confirm-icon
- = ' Export'
+ = ' Export'
+ - else
+ = button_tag type: 'button', class:'btn btn-primary pull-right export-action export-button', data: {exercise_id: exercise.id, export_type: 'create_new'} do
+ i.fa.fa-check.confirm-icon
+ = ' Create new'
+
= button_tag type: 'submit', class:'btn btn-secondary pull-right export-button', data: {dismiss: 'modal'} do
i.fa.fa-remove.abort-icon
- = ' Abort'
+ = exported ? ' Close' : ' Abort'
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 8cb2d110..609f6550 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -327,6 +327,9 @@ en:
label: Export to Codeharbor
success: Successfully pushed the exercise to CodeHarbor.
dialogtitle: Export to Codeharbor
+ successfully_exported: 'Exercise has successfully been exported.
ID: %{id}
Title: %{title}'
+ export_failed: 'Export has failed.
ID: %{id}
Title: %{title}
Error: %{error}'
+ error: 'An error occurred while contacting Codeharbor
Error: %{message}'
file_form:
hints:
feedback_message: This message is used as a hint for failing tests.
diff --git a/config/routes.rb b/config/routes.rb
index 388fc175..6b744055 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -87,6 +87,7 @@ Rails.application.routes.draw do
get 'study_group_dashboard/:study_group_id', to: 'exercises#study_group_dashboard'
post :push_proforma_xml
post :export_external_check
+ post :export_external_confirm
end
end