Implement support for some basic embed options for work sheets via LTI
This commit also fixes an issue with the flash messages being positioned too high and displayed for too long
This commit is contained in:
@ -11,7 +11,7 @@ window.CodeOcean = {
|
|||||||
var ANIMATION_DURATION = 500;
|
var ANIMATION_DURATION = 500;
|
||||||
|
|
||||||
$.isController = function(name) {
|
$.isController = function(name) {
|
||||||
return $('.container[data-controller="' + name + '"]').isPresent();
|
return $('div[data-controller="' + name + '"]').isPresent();
|
||||||
};
|
};
|
||||||
|
|
||||||
$.fn.isPresent = function() {
|
$.fn.isPresent = function() {
|
||||||
|
@ -110,6 +110,10 @@ configureEditors: function () {
|
|||||||
|
|
||||||
// The event ready.jstree is fired too early and thus doesn't work.
|
// The event ready.jstree is fired too early and thus doesn't work.
|
||||||
selectFileInJsTree: function(filetree, file_id) {
|
selectFileInJsTree: function(filetree, file_id) {
|
||||||
|
if (!filetree.is(':visible'))
|
||||||
|
// The left sidebar is not shown and thus the filetree is not rendered.
|
||||||
|
return;
|
||||||
|
|
||||||
if (!filetree.hasClass('jstree-loading')) {
|
if (!filetree.hasClass('jstree-loading')) {
|
||||||
filetree.jstree("deselect_all");
|
filetree.jstree("deselect_all");
|
||||||
filetree.jstree().select_node(file_id);
|
filetree.jstree().select_node(file_id);
|
||||||
@ -224,6 +228,11 @@ configureEditors: function () {
|
|||||||
// remove last (empty) that is there by default line
|
// remove last (empty) that is there by default line
|
||||||
document.removeLines(document.getLength() - 1, document.getLength() - 1);
|
document.removeLines(document.getLength() - 1, document.getLength() - 1);
|
||||||
editor.setReadOnly($(element).data('read-only') !== undefined);
|
editor.setReadOnly($(element).data('read-only') !== undefined);
|
||||||
|
if (editor.getReadOnly()) {
|
||||||
|
editor.setHighlightActiveLine(false);
|
||||||
|
editor.setHighlightGutterLine(false);
|
||||||
|
editor.renderer.$cursorLayer.element.style.opacity = 0;
|
||||||
|
}
|
||||||
editor.setShowPrintMargin(false);
|
editor.setShowPrintMargin(false);
|
||||||
editor.setTheme(this.THEME);
|
editor.setTheme(this.THEME);
|
||||||
|
|
||||||
@ -458,7 +467,7 @@ configureEditors: function () {
|
|||||||
|
|
||||||
var editor = this.editor_for_file.get(file);
|
var editor = this.editor_for_file.get(file);
|
||||||
editor.gotoLine(line, 0);
|
editor.gotoLine(line, 0);
|
||||||
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
|
|
||||||
augmentStacktraceInOutput: function () {
|
augmentStacktraceInOutput: function () {
|
||||||
@ -722,6 +731,7 @@ configureEditors: function () {
|
|||||||
this.initPrompt();
|
this.initPrompt();
|
||||||
this.renderScore();
|
this.renderScore();
|
||||||
this.showFirstFile();
|
this.showFirstFile();
|
||||||
|
this.resizeAceEditors();
|
||||||
|
|
||||||
$(window).on("beforeunload", this.unloadAutoSave.bind(this));
|
$(window).on("beforeunload", this.unloadAutoSave.bind(this));
|
||||||
// create autosave when the editor is opened the first time
|
// create autosave when the editor is opened the first time
|
||||||
|
@ -27,8 +27,11 @@ CodeOceanEditorEvaluation = {
|
|||||||
printScoringResult: function (result, index) {
|
printScoringResult: function (result, index) {
|
||||||
$('#results').show();
|
$('#results').show();
|
||||||
var card = $('#dummies').children().first().clone();
|
var card = $('#dummies').children().first().clone();
|
||||||
this.populateCard(card, result, index);
|
if (card.isPresent()) {
|
||||||
$('#results ul').first().append(card);
|
// the card won't be present if @embed_options[::hide_test_results] == true
|
||||||
|
this.populateCard(card, result, index);
|
||||||
|
$('#results ul').first().append(card);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
printScoringResults: function (response) {
|
printScoringResults: function (response) {
|
||||||
@ -141,14 +144,19 @@ CodeOceanEditorEvaluation = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
printOutput: function (output, colorize, index) {
|
printOutput: function (output, colorize, index) {
|
||||||
|
if (output.stderr === undefined && output.stdout === undefined) {
|
||||||
|
// Prevent empty element with no text at all
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var element = this.findOrCreateOutputElement(index);
|
var element = this.findOrCreateOutputElement(index);
|
||||||
if (!colorize) {
|
if (!colorize) {
|
||||||
if (output.stdout != undefined && output.stdout != '') {
|
if (output.stdout !== undefined && output.stdout !== '') {
|
||||||
//element.append(output.stdout)
|
//element.append(output.stdout)
|
||||||
element.text(element.text() + output.stdout)
|
element.text(element.text() + output.stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output.stderr != undefined && output.stderr != '') {
|
if (output.stderr !== undefined && output.stderr !== '') {
|
||||||
//element.append('StdErr: ' + output.stderr);
|
//element.append('StdErr: ' + output.stderr);
|
||||||
element.text('StdErr: ' + element.text() + output.stderr);
|
element.text('StdErr: ' + element.text() + output.stderr);
|
||||||
}
|
}
|
||||||
|
@ -172,8 +172,9 @@ CodeOceanEditorRequestForComments = {
|
|||||||
this.createSubmission($('#requestComments'), null, createRequestForComments.bind(this));
|
this.createSubmission($('#requestComments'), null, createRequestForComments.bind(this));
|
||||||
|
|
||||||
$('#comment-modal').modal('hide');
|
$('#comment-modal').modal('hide');
|
||||||
|
$('#question').val('');
|
||||||
// we disabled the button to prevent that the user spams RFCs, but decided against this now.
|
// we disabled the button to prevent that the user spams RFCs, but decided against this now.
|
||||||
//var button = $('#requestComments');
|
//var button = $('#requestComments');
|
||||||
//button.prop('disabled', true);
|
//button.prop('disabled', true);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
@ -10,6 +10,7 @@ CodeOceanEditorSubmissions = {
|
|||||||
*/
|
*/
|
||||||
createSubmission: function (initiator, filter, callback) {
|
createSubmission: function (initiator, filter, callback) {
|
||||||
this.showSpinner(initiator);
|
this.showSpinner(initiator);
|
||||||
|
var url = $(initiator).data('url') || $('#editor').data('submissions-url');
|
||||||
var jqxhr = this.ajax({
|
var jqxhr = this.ajax({
|
||||||
data: {
|
data: {
|
||||||
submission: {
|
submission: {
|
||||||
@ -21,7 +22,7 @@ CodeOceanEditorSubmissions = {
|
|||||||
},
|
},
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: $(initiator).data('url') || $('#editor').data('submissions-url')
|
url: url + '.json'
|
||||||
});
|
});
|
||||||
jqxhr.always(this.hideSpinner.bind(this));
|
jqxhr.always(this.hideSpinner.bind(this));
|
||||||
jqxhr.done(this.createSubmissionCallback.bind(this));
|
jqxhr.done(this.createSubmissionCallback.bind(this));
|
||||||
|
@ -5,7 +5,7 @@ class ApplicationController < ActionController::Base
|
|||||||
MEMBER_ACTIONS = [:destroy, :edit, :show, :update]
|
MEMBER_ACTIONS = [:destroy, :edit, :show, :update]
|
||||||
|
|
||||||
after_action :verify_authorized, except: [:help, :welcome]
|
after_action :verify_authorized, except: [:help, :welcome]
|
||||||
before_action :set_locale, :allow_iframe_requests
|
before_action :set_locale, :allow_iframe_requests, :load_embed_options
|
||||||
protect_from_forgery(with: :exception, prepend: true)
|
protect_from_forgery(with: :exception, prepend: true)
|
||||||
rescue_from Pundit::NotAuthorizedError, with: :render_not_authorized
|
rescue_from Pundit::NotAuthorizedError, with: :render_not_authorized
|
||||||
|
|
||||||
@ -38,4 +38,14 @@ class ApplicationController < ActionController::Base
|
|||||||
def allow_iframe_requests
|
def allow_iframe_requests
|
||||||
response.headers.delete('X-Frame-Options')
|
response.headers.delete('X-Frame-Options')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def load_embed_options
|
||||||
|
if session[:embed_options].present? && session[:embed_options].is_a?(Hash)
|
||||||
|
@embed_options = session[:embed_options].symbolize_keys
|
||||||
|
else
|
||||||
|
@embed_options = {}
|
||||||
|
end
|
||||||
|
@embed_options
|
||||||
|
end
|
||||||
|
private :load_embed_options
|
||||||
end
|
end
|
||||||
|
@ -22,6 +22,7 @@ module Lti
|
|||||||
if (exercise_id.nil?)
|
if (exercise_id.nil?)
|
||||||
session.delete(:consumer_id)
|
session.delete(:consumer_id)
|
||||||
session.delete(:external_user_id)
|
session.delete(:external_user_id)
|
||||||
|
session.delete(:embed_options)
|
||||||
else
|
else
|
||||||
LtiParameter.where(consumers_id: consumer_id,
|
LtiParameter.where(consumers_id: consumer_id,
|
||||||
external_users_id: user_id,
|
external_users_id: user_id,
|
||||||
@ -133,6 +134,26 @@ module Lti
|
|||||||
end
|
end
|
||||||
private :set_current_user
|
private :set_current_user
|
||||||
|
|
||||||
|
def set_embedding_options
|
||||||
|
@embed_options = {}
|
||||||
|
[:hide_navbar,
|
||||||
|
:hide_exercise_description,
|
||||||
|
:disable_run,
|
||||||
|
:disable_score,
|
||||||
|
:disable_rfc,
|
||||||
|
:disable_interventions,
|
||||||
|
:hide_sidebar,
|
||||||
|
:read_only,
|
||||||
|
:hide_test_results,
|
||||||
|
:disable_hints].each do |option|
|
||||||
|
value = params["custom_embed_options_#{option}".to_sym] == 'true'
|
||||||
|
# Optimize storage and save only those that are true, the session cookie is limited to 4KB
|
||||||
|
@embed_options[option] = value if value.present?
|
||||||
|
end
|
||||||
|
session[:embed_options] = @embed_options
|
||||||
|
end
|
||||||
|
private :set_embedding_options
|
||||||
|
|
||||||
def store_lti_session_data(options = {})
|
def store_lti_session_data(options = {})
|
||||||
lti_parameters = LtiParameter.find_or_create_by(consumers_id: options[:consumer].id,
|
lti_parameters = LtiParameter.find_or_create_by(consumers_id: options[:consumer].id,
|
||||||
external_users_id: @current_user.id,
|
external_users_id: @current_user.id,
|
||||||
|
@ -55,6 +55,11 @@ module SubmissionScoring
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if @embed_options.present? && @embed_options[:hide_test_results] && outputs.present?
|
||||||
|
outputs.each do |output|
|
||||||
|
output.except!(:error_messages, :count, :failed, :filename, :message, :passed, :stderr, :stdout)
|
||||||
|
end
|
||||||
|
end
|
||||||
outputs
|
outputs
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -188,7 +188,15 @@ class ExercisesController < ApplicationController
|
|||||||
user_got_intervention_in_exercise = UserExerciseIntervention.where(user: current_user, exercise: @exercise).size >= max_intervention_count_per_exercise
|
user_got_intervention_in_exercise = UserExerciseIntervention.where(user: current_user, exercise: @exercise).size >= max_intervention_count_per_exercise
|
||||||
user_got_enough_interventions = count_interventions_today >= max_intervention_count_per_day or user_got_intervention_in_exercise
|
user_got_enough_interventions = count_interventions_today >= max_intervention_count_per_day or user_got_intervention_in_exercise
|
||||||
|
|
||||||
@show_rfc_interventions = (not user_solved_exercise and not user_got_enough_interventions).to_s
|
unless @embed_options[:disable_interventions]
|
||||||
|
@show_rfc_interventions = (not user_solved_exercise and not user_got_enough_interventions).to_s
|
||||||
|
@show_break_interventions = false
|
||||||
|
else
|
||||||
|
@show_rfc_interventions = false
|
||||||
|
@show_break_interventions = false
|
||||||
|
end
|
||||||
|
|
||||||
|
@hide_rfc_button = @embed_options[:disable_rfc]
|
||||||
|
|
||||||
|
|
||||||
@search = Search.new
|
@search = Search.new
|
||||||
|
@ -8,7 +8,7 @@ class FlowrController < ApplicationController
|
|||||||
.order('testruns.created_at DESC').first
|
.order('testruns.created_at DESC').first
|
||||||
|
|
||||||
# Return if no submission was found
|
# Return if no submission was found
|
||||||
if submission.blank?
|
if submission.blank? || @embed_options[:disable_hints] || @embed_options[:hide_test_results]
|
||||||
skip_authorization
|
skip_authorization
|
||||||
render json: [], status: :ok
|
render json: [], status: :ok
|
||||||
return
|
return
|
||||||
|
@ -96,6 +96,10 @@ class RequestForCommentsController < ApplicationController
|
|||||||
# POST /request_for_comments
|
# POST /request_for_comments
|
||||||
# POST /request_for_comments.json
|
# POST /request_for_comments.json
|
||||||
def create
|
def create
|
||||||
|
# Consider all requests as JSON
|
||||||
|
request.format = 'json'
|
||||||
|
raise Pundit::NotAuthorizedError if @embed_options[:disable_rfc]
|
||||||
|
|
||||||
@request_for_comment = RequestForComment.new(request_for_comment_params)
|
@request_for_comment = RequestForComment.new(request_for_comment_params)
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
if @request_for_comment.save
|
if @request_for_comment.save
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
class SessionsController < ApplicationController
|
class SessionsController < ApplicationController
|
||||||
include Lti
|
include Lti
|
||||||
|
|
||||||
[:require_oauth_parameters, :require_valid_consumer_key, :require_valid_oauth_signature, :require_unique_oauth_nonce, :set_current_user, :require_valid_exercise_token].each do |method_name|
|
[:require_oauth_parameters, :require_valid_consumer_key, :require_valid_oauth_signature, :require_unique_oauth_nonce, :set_current_user, :require_valid_exercise_token, :set_embedding_options].each do |method_name|
|
||||||
before_action(method_name, only: :create_through_lti)
|
before_action(method_name, only: :create_through_lti)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -129,6 +129,11 @@ class SubmissionsController < ApplicationController
|
|||||||
# end
|
# end
|
||||||
|
|
||||||
hijack do |tubesock|
|
hijack do |tubesock|
|
||||||
|
if @embed_options[:disable_run]
|
||||||
|
kill_socket(tubesock)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
# probably add:
|
# probably add:
|
||||||
# ensure
|
# ensure
|
||||||
# #guarantee that the thread is releasing the DB connection after it is done
|
# #guarantee that the thread is releasing the DB connection after it is done
|
||||||
@ -291,6 +296,11 @@ class SubmissionsController < ApplicationController
|
|||||||
|
|
||||||
def score
|
def score
|
||||||
hijack do |tubesock|
|
hijack do |tubesock|
|
||||||
|
if @embed_options[:disable_score]
|
||||||
|
kill_socket(tubesock)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
Thread.new { EventMachine.run } unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive?
|
Thread.new { EventMachine.run } unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive?
|
||||||
# tubesock is the socket to the client
|
# tubesock is the socket to the client
|
||||||
|
|
||||||
@ -308,6 +318,7 @@ class SubmissionsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def send_hints(tubesock, errors)
|
def send_hints(tubesock, errors)
|
||||||
|
return if @embed_options[:disable_hints]
|
||||||
errors = errors.to_a.uniq { |e| e.hint}
|
errors = errors.to_a.uniq { |e| e.hint}
|
||||||
errors.each do | error |
|
errors.each do | error |
|
||||||
tubesock.send_data JSON.dump({cmd: 'hint', hint: error.hint, description: error.error_template.description})
|
tubesock.send_data JSON.dump({cmd: 'hint', hint: error.hint, description: error.error_template.description})
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
- if current_user.try(:internal_user?)
|
- if current_user.try(:internal_user?)
|
||||||
ul.breadcrumb
|
.container
|
||||||
- if model = Kernel.const_get(controller_path.classify) rescue nil
|
ul.breadcrumb
|
||||||
- object = model.find_by(id: params[:id])
|
- if model = Kernel.const_get(controller_path.classify) rescue nil
|
||||||
- if model.try(:nested_resource?)
|
- object = model.find_by(id: params[:id])
|
||||||
li.breadcrumb-item = model.model_name.human(count: 2)
|
- if model.try(:nested_resource?)
|
||||||
- if object
|
li.breadcrumb-item = model.model_name.human(count: 2)
|
||||||
li.breadcrumb-item = object
|
- if object
|
||||||
- else
|
li.breadcrumb-item = object
|
||||||
li.breadcrumb-item = link_to(model.model_name.human(count: 2), send(:"#{model.model_name.collection}_path"))
|
|
||||||
- if object
|
|
||||||
li.breadcrumb-item = link_to(object, send(:"#{model.model_name.singular}_path", object))
|
|
||||||
li.breadcrumb-item.active
|
|
||||||
- if I18n.translation_present?("shared.#{params[:action]}")
|
|
||||||
= t("shared.#{params[:action]}")
|
|
||||||
- else
|
- else
|
||||||
= t("#{controller_name}.index.#{params[:action]}")
|
li.breadcrumb-item = link_to(model.model_name.human(count: 2), send(:"#{model.model_name.collection}_path"))
|
||||||
- else
|
- if object
|
||||||
li.breadcrumb-item.active = t("breadcrumbs.#{controller_name}.#{params[:action]}")
|
li.breadcrumb-item = link_to(object, send(:"#{model.model_name.singular}_path", object))
|
||||||
|
li.breadcrumb-item.active
|
||||||
|
- if I18n.translation_present?("shared.#{params[:action]}")
|
||||||
|
= t("shared.#{params[:action]}")
|
||||||
|
- else
|
||||||
|
= t("#{controller_name}.index.#{params[:action]}")
|
||||||
|
- else
|
||||||
|
li.breadcrumb-item.active = t("breadcrumbs.#{controller_name}.#{params[:action]}")
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#flash-container
|
#flash-container.container
|
||||||
#flash.container.fixed_error_messages data-message-failure=t('shared.message_failure')
|
#flash.container.fixed_error_messages data-message-failure=t('shared.message_failure')
|
||||||
- %w[alert danger info notice success warning].each do |severity|
|
- %w[alert danger info notice success warning].each do |severity|
|
||||||
div.alert.flash class="alert-#{{'alert' => 'warning', 'notice' => 'success'}.fetch(severity, severity)} alert-dismissible fade show"
|
div.alert.flash class="alert-#{{'alert' => 'warning', 'notice' => 'success'}.fetch(severity, severity)} alert-dismissible fade show"
|
||||||
|
@ -5,26 +5,29 @@
|
|||||||
- show_rfc_interventions = @show_rfc_interventions || "false"
|
- show_rfc_interventions = @show_rfc_interventions || "false"
|
||||||
- hide_rfc_button = @hide_rfc_button || false
|
- hide_rfc_button = @hide_rfc_button || false
|
||||||
#editor.row data-exercise-id=@exercise.id data-message-depleted=t('exercises.editor.depleted') data-message-timeout=t('exercises.editor.timeout', permitted_execution_time: @exercise.execution_environment.permitted_execution_time) data-submissions-url=submissions_path data-user-id=@current_user.id data-user-external-id=external_user_external_id data-working-times-url=working_times_exercise_path(@exercise) data-intervention-save-url=intervention_exercise_path(@exercise) data-rfc-interventions=show_rfc_interventions data-break-interventions=show_break_interventions data-course_token=@course_token data-search-save-url=search_exercise_path(@exercise)
|
#editor.row data-exercise-id=@exercise.id data-message-depleted=t('exercises.editor.depleted') data-message-timeout=t('exercises.editor.timeout', permitted_execution_time: @exercise.execution_environment.permitted_execution_time) data-submissions-url=submissions_path data-user-id=@current_user.id data-user-external-id=external_user_external_id data-working-times-url=working_times_exercise_path(@exercise) data-intervention-save-url=intervention_exercise_path(@exercise) data-rfc-interventions=show_rfc_interventions data-break-interventions=show_break_interventions data-course_token=@course_token data-search-save-url=search_exercise_path(@exercise)
|
||||||
div id="sidebar" class=(@exercise.hide_file_tree ? 'sidebar-col-collapsed' : 'sidebar-col') = render('editor_file_tree', exercise: @exercise, files: @files)
|
- unless @embed_options[:hide_sidebar]
|
||||||
|
div id="sidebar" class=(@exercise.hide_file_tree ? 'sidebar-col-collapsed' : 'sidebar-col') = render('editor_file_tree', exercise: @exercise, files: @files)
|
||||||
div.editor-col.col.p-0 id='frames'
|
div.editor-col.col.p-0 id='frames'
|
||||||
#editor-buttons.btn-group.enforce-bottom-margin
|
#editor-buttons.btn-group.enforce-bottom-margin
|
||||||
= render('editor_button', disabled: true, icon: 'fa fa-ban', id: 'dummy', label: t('exercises.editor.dummy'))
|
= render('editor_button', disabled: true, icon: 'fa fa-ban', id: 'dummy', label: t('exercises.editor.dummy'))
|
||||||
= render('editor_button', icon: 'fa fa-desktop', id: 'render', label: t('exercises.editor.render'))
|
= render('editor_button', icon: 'fa fa-desktop', id: 'render', label: t('exercises.editor.render')) unless @embed_options[:hide_run_button]
|
||||||
= render('editor_button', data: {:'data-message-failure' => t('exercises.editor.run_failure'), :'data-message-network' => t('exercises.editor.network'), :'data-message-success' => t('exercises.editor.run_success'), :'data-placement' => 'top', :'data-toggle' => 'tooltip', :'data-container' => 'body'}, icon: 'fa fa-play', id: 'run', label: t('exercises.editor.run'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + r'))
|
= render('editor_button', data: {:'data-message-failure' => t('exercises.editor.run_failure'), :'data-message-network' => t('exercises.editor.network'), :'data-message-success' => t('exercises.editor.run_success'), :'data-placement' => 'top', :'data-toggle' => 'tooltip', :'data-container' => 'body'}, icon: 'fa fa-play', id: 'run', label: t('exercises.editor.run'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + r')) unless @embed_options[:disable_run]
|
||||||
= render('editor_button', data: {:'data-placement' => 'top', :'data-toggle' => 'tooltip', :'data-container' => 'body'}, icon: 'fa fa-stop', id: 'stop', label: t('exercises.editor.stop'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + r'))
|
= render('editor_button', data: {:'data-placement' => 'top', :'data-toggle' => 'tooltip', :'data-container' => 'body'}, icon: 'fa fa-stop', id: 'stop', label: t('exercises.editor.stop'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + r')) unless @embed_options[:disable_run]
|
||||||
= render('editor_button', data: {:'data-placement' => 'top', :'data-toggle' => 'tooltip', :'data-container' => 'body'}, icon: 'fa fa-rocket', id: 'test', label: t('exercises.editor.test'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + t'))
|
= render('editor_button', data: {:'data-placement' => 'top', :'data-toggle' => 'tooltip', :'data-container' => 'body'}, icon: 'fa fa-rocket', id: 'test', label: t('exercises.editor.test'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + t')) unless @embed_options[:disable_run]
|
||||||
= render('editor_button', data: {:'data-placement' => 'top', :'data-toggle' => 'tooltip', :'data-container' => 'body'}, icon: 'fa fa-trophy', id: 'assess', label: t('exercises.editor.score'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + s'))
|
= render('editor_button', data: {:'data-placement' => 'top', :'data-toggle' => 'tooltip', :'data-container' => 'body'}, icon: 'fa fa-trophy', id: 'assess', label: t('exercises.editor.score'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + s')) unless @embed_options[:disable_score]
|
||||||
// todo: check this
|
// todo: check this
|
||||||
- if not hide_rfc_button
|
- unless hide_rfc_button
|
||||||
= render('editor_button', icon: 'fa fa-comment', id: 'requestComments', label: t('exercises.editor.requestComments'), title: t('exercises.editor.requestCommentsTooltip'))
|
= render('editor_button', icon: 'fa fa-comment', id: 'requestComments', label: t('exercises.editor.requestComments'), title: t('exercises.editor.requestCommentsTooltip'))
|
||||||
- @files.each do |file|
|
- @files.each do |file|
|
||||||
|
- file.read_only = true if @embed_options[:read_only]
|
||||||
= render('editor_frame', exercise: exercise, file: file)
|
= render('editor_frame', exercise: exercise, file: file)
|
||||||
#autosave-label
|
#autosave-label
|
||||||
= t('exercises.editor.lastsaved')
|
= t('exercises.editor.lastsaved')
|
||||||
span
|
span
|
||||||
button style="display:none" id="autosave"
|
button style="display:none" id="autosave"
|
||||||
div id='output_sidebar' class='output-col-collapsed' = render('exercises/editor_output', external_user_id: external_user_id, consumer_id: consumer_id )
|
- unless @embed_options[:disable_run] && @embed_options[:disable_score]
|
||||||
|
div id='output_sidebar' class='output-col-collapsed' = render('exercises/editor_output', external_user_id: external_user_id, consumer_id: consumer_id )
|
||||||
|
|
||||||
|
|
||||||
= render('shared/modal', id: 'comment-modal', title: t('exercises.implement.comment.request'), template: 'exercises/_request_comment_dialogcontent')
|
= render('shared/modal', id: 'comment-modal', title: t('exercises.implement.comment.request'), template: 'exercises/_request_comment_dialogcontent') unless @embed_options[:disable_rfc]
|
||||||
= render('shared/modal', id: 'break-intervention-modal', title: t('exercises.implement.break_intervention.title'), template: 'interventions/_break_intervention_modal')
|
= render('shared/modal', id: 'break-intervention-modal', title: t('exercises.implement.break_intervention.title'), template: 'interventions/_break_intervention_modal') unless @embed_options[:disable_interventions]
|
||||||
|
@ -10,17 +10,18 @@ div.h-100 id='output_sidebar_uncollapsed' class='d-none col-sm-12 enforce-bottom
|
|||||||
#results
|
#results
|
||||||
h2 = t('exercises.implement.results')
|
h2 = t('exercises.implement.results')
|
||||||
p.test-count == t('exercises.implement.test_count', count: 0)
|
p.test-count == t('exercises.implement.test_count', count: 0)
|
||||||
ul.list-unstyled
|
- unless @embed_options[:hide_test_results]
|
||||||
ul#dummies.d-none.list-unstyled
|
ul.list-unstyled
|
||||||
li.card.mt-2
|
ul#dummies.d-none.list-unstyled
|
||||||
.card-header.py-2
|
li.card.mt-2
|
||||||
h5.card-title.m-0 == t('exercises.implement.file', filename: '', number: 0)
|
.card-header.py-2
|
||||||
.card-body.bg-white.text-dark
|
h5.card-title.m-0 == t('exercises.implement.file', filename: '', number: 0)
|
||||||
= row(label: 'exercises.implement.passed_tests', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe)
|
.card-body.bg-white.text-dark
|
||||||
= row(label: 'activerecord.attributes.submission.score', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe)
|
= row(label: 'exercises.implement.passed_tests', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe)
|
||||||
= row(label: 'exercises.implement.feedback')
|
= row(label: 'activerecord.attributes.submission.score', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe)
|
||||||
= row(label: 'exercises.implement.error_messages')
|
= row(label: 'exercises.implement.feedback')
|
||||||
/= row(label: 'exercises.implement.output', value: link_to(t('shared.show'), '#'))
|
= row(label: 'exercises.implement.error_messages')
|
||||||
|
/= row(label: 'exercises.implement.output', value: link_to(t('shared.show'), '#'))
|
||||||
#score data-maximum-score=@exercise.maximum_score data-score=@submission.try(:score)
|
#score data-maximum-score=@exercise.maximum_score data-score=@submission.try(:score)
|
||||||
h4
|
h4
|
||||||
span == "#{t('activerecord.attributes.submission.score')}: "
|
span == "#{t('activerecord.attributes.submission.score')}: "
|
||||||
@ -44,12 +45,13 @@ div.h-100 id='output_sidebar_uncollapsed' class='d-none col-sm-12 enforce-bottom
|
|||||||
input#prompt-input.form-control type='text'
|
input#prompt-input.form-control type='text'
|
||||||
span.input-group-btn
|
span.input-group-btn
|
||||||
button#prompt-submit.btn.btn-primary type="button" = t('exercises.editor.send')
|
button#prompt-submit.btn.btn-primary type="button" = t('exercises.editor.send')
|
||||||
#error-hints
|
- unless @embed_options[:disable_hints]
|
||||||
.heading = t('exercises.implement.error_hints.heading')
|
#error-hints
|
||||||
ul.body
|
.heading = t('exercises.implement.error_hints.heading')
|
||||||
|
ul.body
|
||||||
#output.mt-2
|
#output.mt-2
|
||||||
pre = t('exercises.implement.no_output_yet')
|
pre = t('exercises.implement.no_output_yet')
|
||||||
- if CodeOcean::Config.new(:code_ocean).read[:flowr][:enabled]
|
- if CodeOcean::Config.new(:code_ocean).read[:flowr][:enabled] && !@embed_options[:disable_hints] && !@embed_options[:hide_test_results]
|
||||||
#flowrHint.card.text-white.bg-info data-url=CodeOcean::Config.new(:code_ocean).read[:flowr][:url] role='tab'
|
#flowrHint.card.text-white.bg-info data-url=CodeOcean::Config.new(:code_ocean).read[:flowr][:url] role='tab'
|
||||||
.card-header = t('exercises.implement.flowr.heading')
|
.card-header = t('exercises.implement.flowr.heading')
|
||||||
.card-body.text-dark.bg-white
|
.card-body.text-dark.bg-white
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
.row
|
.row
|
||||||
#editor-column.col-md-12
|
#editor-column.col-md-12
|
||||||
.exercise.clearfix
|
- unless @embed_options[:hide_exercise_description]
|
||||||
div
|
.exercise.clearfix
|
||||||
span.badge.badge-pill.badge-primary.float-right.score
|
div
|
||||||
|
span.badge.badge-pill.badge-primary.float-right.score
|
||||||
|
|
||||||
h1 id="exercise-headline"
|
h1 id="exercise-headline"
|
||||||
i class="fa fa-chevron-down" id="description-symbol"
|
i class="fa fa-chevron-down" id="description-symbol"
|
||||||
= @exercise.title
|
= @exercise.title
|
||||||
|
|
||||||
#description-card.lead.description-card
|
#description-card.lead.description-card
|
||||||
= render_markdown(@exercise.description)
|
= render_markdown(@exercise.description)
|
||||||
|
|
||||||
a#toggle href="#" data-show=t('shared.show') data-hide=t('shared.hide') = t('shared.hide')
|
a#toggle href="#" data-show=t('shared.show') data-hide=t('shared.hide') = t('shared.hide')
|
||||||
|
|
||||||
#alert.alert.alert-danger role='alert'
|
#alert.alert.alert-danger role='alert'
|
||||||
h4 = t('.alert.title')
|
h4 = t('.alert.title')
|
||||||
|
@ -13,22 +13,23 @@ html lang='en'
|
|||||||
= yield(:head)
|
= yield(:head)
|
||||||
= csrf_meta_tags
|
= csrf_meta_tags
|
||||||
body
|
body
|
||||||
nav.navbar.navbar-dark.bg-dark.navbar-expand-md.mb-4.py-1 role='navigation'
|
- unless @embed_options[:hide_navbar]
|
||||||
.container
|
nav.navbar.navbar-dark.bg-dark.navbar-expand-md.mb-4.py-1 role='navigation'
|
||||||
.navbar-brand
|
.container
|
||||||
i.fa.fa-code
|
.navbar-brand
|
||||||
= application_name
|
i.fa.fa-code
|
||||||
button.navbar-toggler data-target='#navbar-collapse' data-toggle='collapse' type='button' aria-expanded='false' aria-label='Toggle navigation'
|
= application_name
|
||||||
span.navbar-toggler-icon
|
button.navbar-toggler data-target='#navbar-collapse' data-toggle='collapse' type='button' aria-expanded='false' aria-label='Toggle navigation'
|
||||||
#navbar-collapse.collapse.navbar-collapse
|
span.navbar-toggler-icon
|
||||||
= render('navigation', cached: true)
|
#navbar-collapse.collapse.navbar-collapse
|
||||||
ul.nav.navbar-nav.ml-auto
|
= render('navigation', cached: true)
|
||||||
= render('locale_selector', cached: true)
|
ul.nav.navbar-nav.ml-auto
|
||||||
li.nav-item.mr-3 = link_to(t('shared.help.link'), '#modal-help', data: {toggle: 'modal'}, class: 'nav-link')
|
= render('locale_selector', cached: true)
|
||||||
= render('session')
|
li.nav-item.mr-3 = link_to(t('shared.help.link'), '#modal-help', data: {toggle: 'modal'}, class: 'nav-link')
|
||||||
.container data-controller=controller_name
|
= render('session')
|
||||||
|
div data-controller=controller_name
|
||||||
= render('flash')
|
= render('flash')
|
||||||
= render('breadcrumbs') if current_user.try(:internal_user?)
|
= render('breadcrumbs') if current_user.try(:internal_user?) && !@embed_options[:hide_navbar]
|
||||||
- if (controller_name == "exercises" && action_name == "implement")
|
- if (controller_name == "exercises" && action_name == "implement")
|
||||||
.container-fluid
|
.container-fluid
|
||||||
= yield
|
= yield
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
json.extract! @submission, :id, :files
|
json.extract! @submission, :id, :files
|
||||||
json.download_url download_submission_path(@submission)
|
json.download_url download_submission_path(@submission, format: :json)
|
||||||
json.score_url score_submission_path(@submission)
|
json.score_url score_submission_path(@submission, format: :json)
|
||||||
json.stop_url stop_submission_path(@submission)
|
json.stop_url stop_submission_path(@submission, format: :json)
|
||||||
json.download_file_url download_file_submission_path(@submission, 'a.').gsub(/a\.$/, '{filename}')
|
json.download_file_url download_file_submission_path(@submission, 'a.', format: :json).gsub(/a\.\.json$/, '{filename}.json')
|
||||||
json.render_url render_submission_path(@submission, 'a.').gsub(/a\.$/, '{filename}')
|
json.render_url render_submission_path(@submission, 'a.', format: :json).gsub(/a\.\.json$/, '{filename}.json')
|
||||||
json.run_url run_submission_path(@submission, 'a.').gsub(/a\.$/, '{filename}')
|
json.run_url run_submission_path(@submission, 'a.', format: :json).gsub(/a\.\.json$/, '{filename}.json')
|
||||||
json.test_url test_submission_path(@submission, 'a.').gsub(/a\.$/, '{filename}')
|
json.test_url test_submission_path(@submission, 'a.', format: :json).gsub(/a\.\.json$/, '{filename}.json')
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
$( document ).on('turbolinks:load', function() {
|
$( document ).on('turbolinks:load', function() {
|
||||||
var DURATION = 10000;
|
var DURATION = 5000;
|
||||||
var SEVERITIES = ['danger', 'info', 'success', 'warning'];
|
var SEVERITIES = ['danger', 'info', 'success', 'warning'];
|
||||||
|
|
||||||
var buildFlash = function(options) {
|
var buildFlash = function(options) {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#flash-container {
|
#flash-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -21px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.flash {
|
.flash {
|
||||||
|
@ -19,6 +19,7 @@ describe Lti do
|
|||||||
it 'clears the session' do
|
it 'clears the session' do
|
||||||
expect(controller.session).to receive(:delete).with(:consumer_id)
|
expect(controller.session).to receive(:delete).with(:consumer_id)
|
||||||
expect(controller.session).to receive(:delete).with(:external_user_id)
|
expect(controller.session).to receive(:delete).with(:external_user_id)
|
||||||
|
expect(controller.session).to receive(:delete).with(:embed_options)
|
||||||
controller.send(:clear_lti_session_data)
|
controller.send(:clear_lti_session_data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -186,7 +186,7 @@ describe SubmissionsController do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'ends with a placeholder' do
|
it 'ends with a placeholder' do
|
||||||
expect(url).to end_with(Submission::FILENAME_URL_PLACEHOLDER)
|
expect(url).to end_with(Submission::FILENAME_URL_PLACEHOLDER + '.json')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -196,7 +196,7 @@ describe SubmissionsController do
|
|||||||
let(:url) { JSON.parse(response.body).with_indifferent_access.fetch("#{action}_url") }
|
let(:url) { JSON.parse(response.body).with_indifferent_access.fetch("#{action}_url") }
|
||||||
|
|
||||||
it "corresponds to the #{action} path" do
|
it "corresponds to the #{action} path" do
|
||||||
expect(url).to eq(Rails.application.routes.url_helpers.send(:"#{action}_submission_path", submission))
|
expect(url).to eq(Rails.application.routes.url_helpers.send(:"#{action}_submission_path", submission, format: :json))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -10,6 +10,7 @@ describe 'exercises/implement.html.slim' do
|
|||||||
assign(:exercise, exercise)
|
assign(:exercise, exercise)
|
||||||
assign(:files, files)
|
assign(:files, files)
|
||||||
assign(:paths, [])
|
assign(:paths, [])
|
||||||
|
assign(:embed_options, {})
|
||||||
render
|
render
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user