From a0d8b30ef2f2f91a41855adf88ccb73947011022 Mon Sep 17 00:00:00 2001 From: Sebastian Serth Date: Mon, 10 Dec 2018 16:53:43 +0100 Subject: [PATCH] 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 --- app/assets/javascripts/base.js | 2 +- app/assets/javascripts/editor/editor.js.erb | 12 ++++++- app/assets/javascripts/editor/evaluation.js | 16 ++++++--- .../editor/participantsupport.js.erb | 3 +- app/assets/javascripts/editor/submissions.js | 3 +- app/controllers/application_controller.rb | 12 ++++++- app/controllers/concerns/lti.rb | 21 +++++++++++ .../concerns/submission_scoring.rb | 5 +++ app/controllers/exercises_controller.rb | 10 +++++- app/controllers/flowr_controller.rb | 2 +- .../request_for_comments_controller.rb | 4 +++ app/controllers/sessions_controller.rb | 2 +- app/controllers/submissions_controller.rb | 11 ++++++ app/views/application/_breadcrumbs.html.slim | 35 ++++++++++--------- app/views/application/_flash.html.slim | 2 +- app/views/exercises/_editor.html.slim | 23 ++++++------ app/views/exercises/_editor_output.html.slim | 32 +++++++++-------- app/views/exercises/implement.html.slim | 19 +++++----- app/views/layouts/application.html.slim | 31 ++++++++-------- app/views/submissions/show.json.jbuilder | 14 ++++---- lib/assets/javascripts/flash.js | 2 +- lib/assets/stylesheets/flash.css.scss | 1 - spec/concerns/lti_spec.rb | 1 + .../submissions_controller_spec.rb | 4 +-- .../exercises/implement.html.slim_spec.rb | 1 + 25 files changed, 178 insertions(+), 90 deletions(-) diff --git a/app/assets/javascripts/base.js b/app/assets/javascripts/base.js index b7263d48..08e9afd1 100644 --- a/app/assets/javascripts/base.js +++ b/app/assets/javascripts/base.js @@ -11,7 +11,7 @@ window.CodeOcean = { var ANIMATION_DURATION = 500; $.isController = function(name) { - return $('.container[data-controller="' + name + '"]').isPresent(); + return $('div[data-controller="' + name + '"]').isPresent(); }; $.fn.isPresent = function() { diff --git a/app/assets/javascripts/editor/editor.js.erb b/app/assets/javascripts/editor/editor.js.erb index c4cb46f9..33271a44 100644 --- a/app/assets/javascripts/editor/editor.js.erb +++ b/app/assets/javascripts/editor/editor.js.erb @@ -110,6 +110,10 @@ configureEditors: function () { // The event ready.jstree is fired too early and thus doesn't work. 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')) { filetree.jstree("deselect_all"); filetree.jstree().select_node(file_id); @@ -224,6 +228,11 @@ configureEditors: function () { // remove last (empty) that is there by default line document.removeLines(document.getLength() - 1, document.getLength() - 1); 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.setTheme(this.THEME); @@ -458,7 +467,7 @@ configureEditors: function () { var editor = this.editor_for_file.get(file); editor.gotoLine(line, 0); - + event.preventDefault(); }, augmentStacktraceInOutput: function () { @@ -722,6 +731,7 @@ configureEditors: function () { this.initPrompt(); this.renderScore(); this.showFirstFile(); + this.resizeAceEditors(); $(window).on("beforeunload", this.unloadAutoSave.bind(this)); // create autosave when the editor is opened the first time diff --git a/app/assets/javascripts/editor/evaluation.js b/app/assets/javascripts/editor/evaluation.js index 5e631b2b..74a77ce3 100644 --- a/app/assets/javascripts/editor/evaluation.js +++ b/app/assets/javascripts/editor/evaluation.js @@ -27,8 +27,11 @@ CodeOceanEditorEvaluation = { printScoringResult: function (result, index) { $('#results').show(); var card = $('#dummies').children().first().clone(); - this.populateCard(card, result, index); - $('#results ul').first().append(card); + if (card.isPresent()) { + // 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) { @@ -141,14 +144,19 @@ CodeOceanEditorEvaluation = { }, 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); if (!colorize) { - if (output.stdout != undefined && output.stdout != '') { + if (output.stdout !== undefined && output.stdout !== '') { //element.append(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.text('StdErr: ' + element.text() + output.stderr); } diff --git a/app/assets/javascripts/editor/participantsupport.js.erb b/app/assets/javascripts/editor/participantsupport.js.erb index 9a8e54f8..23e98009 100644 --- a/app/assets/javascripts/editor/participantsupport.js.erb +++ b/app/assets/javascripts/editor/participantsupport.js.erb @@ -172,8 +172,9 @@ CodeOceanEditorRequestForComments = { this.createSubmission($('#requestComments'), null, createRequestForComments.bind(this)); $('#comment-modal').modal('hide'); + $('#question').val(''); // we disabled the button to prevent that the user spams RFCs, but decided against this now. //var button = $('#requestComments'); //button.prop('disabled', true); - }, + } }; diff --git a/app/assets/javascripts/editor/submissions.js b/app/assets/javascripts/editor/submissions.js index f55bdbb5..20911535 100644 --- a/app/assets/javascripts/editor/submissions.js +++ b/app/assets/javascripts/editor/submissions.js @@ -10,6 +10,7 @@ CodeOceanEditorSubmissions = { */ createSubmission: function (initiator, filter, callback) { this.showSpinner(initiator); + var url = $(initiator).data('url') || $('#editor').data('submissions-url'); var jqxhr = this.ajax({ data: { submission: { @@ -21,7 +22,7 @@ CodeOceanEditorSubmissions = { }, dataType: 'json', method: 'POST', - url: $(initiator).data('url') || $('#editor').data('submissions-url') + url: url + '.json' }); jqxhr.always(this.hideSpinner.bind(this)); jqxhr.done(this.createSubmissionCallback.bind(this)); diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c58303ba..2fcece66 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -5,7 +5,7 @@ class ApplicationController < ActionController::Base MEMBER_ACTIONS = [:destroy, :edit, :show, :update] 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) rescue_from Pundit::NotAuthorizedError, with: :render_not_authorized @@ -38,4 +38,14 @@ class ApplicationController < ActionController::Base def allow_iframe_requests response.headers.delete('X-Frame-Options') 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 diff --git a/app/controllers/concerns/lti.rb b/app/controllers/concerns/lti.rb index e8241af3..8ca57be9 100644 --- a/app/controllers/concerns/lti.rb +++ b/app/controllers/concerns/lti.rb @@ -22,6 +22,7 @@ module Lti if (exercise_id.nil?) session.delete(:consumer_id) session.delete(:external_user_id) + session.delete(:embed_options) else LtiParameter.where(consumers_id: consumer_id, external_users_id: user_id, @@ -133,6 +134,26 @@ module Lti end 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 = {}) lti_parameters = LtiParameter.find_or_create_by(consumers_id: options[:consumer].id, external_users_id: @current_user.id, diff --git a/app/controllers/concerns/submission_scoring.rb b/app/controllers/concerns/submission_scoring.rb index 06bba11c..7436ac51 100644 --- a/app/controllers/concerns/submission_scoring.rb +++ b/app/controllers/concerns/submission_scoring.rb @@ -55,6 +55,11 @@ module SubmissionScoring } 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 end end diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index 839aef4c..79a669cf 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -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_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 diff --git a/app/controllers/flowr_controller.rb b/app/controllers/flowr_controller.rb index e9f742b7..310d91d2 100644 --- a/app/controllers/flowr_controller.rb +++ b/app/controllers/flowr_controller.rb @@ -8,7 +8,7 @@ class FlowrController < ApplicationController .order('testruns.created_at DESC').first # Return if no submission was found - if submission.blank? + if submission.blank? || @embed_options[:disable_hints] || @embed_options[:hide_test_results] skip_authorization render json: [], status: :ok return diff --git a/app/controllers/request_for_comments_controller.rb b/app/controllers/request_for_comments_controller.rb index 29c5a6ea..39f1ebb8 100644 --- a/app/controllers/request_for_comments_controller.rb +++ b/app/controllers/request_for_comments_controller.rb @@ -96,6 +96,10 @@ class RequestForCommentsController < ApplicationController # POST /request_for_comments # POST /request_for_comments.json 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) respond_to do |format| if @request_for_comment.save diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 8f698d1a..bd1a16ec 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,7 +1,7 @@ class SessionsController < ApplicationController 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) end diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 039efa02..d6a92055 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -129,6 +129,11 @@ class SubmissionsController < ApplicationController # end hijack do |tubesock| + if @embed_options[:disable_run] + kill_socket(tubesock) + return + end + # probably add: # ensure # #guarantee that the thread is releasing the DB connection after it is done @@ -291,6 +296,11 @@ class SubmissionsController < ApplicationController def score 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? # tubesock is the socket to the client @@ -308,6 +318,7 @@ class SubmissionsController < ApplicationController end def send_hints(tubesock, errors) + return if @embed_options[:disable_hints] errors = errors.to_a.uniq { |e| e.hint} errors.each do | error | tubesock.send_data JSON.dump({cmd: 'hint', hint: error.hint, description: error.error_template.description}) diff --git a/app/views/application/_breadcrumbs.html.slim b/app/views/application/_breadcrumbs.html.slim index 7899ae0e..570f5624 100644 --- a/app/views/application/_breadcrumbs.html.slim +++ b/app/views/application/_breadcrumbs.html.slim @@ -1,19 +1,20 @@ - if current_user.try(:internal_user?) - ul.breadcrumb - - if model = Kernel.const_get(controller_path.classify) rescue nil - - object = model.find_by(id: params[:id]) - - if model.try(:nested_resource?) - li.breadcrumb-item = model.model_name.human(count: 2) - - if object - li.breadcrumb-item = object - - else - 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]}") + .container + ul.breadcrumb + - if model = Kernel.const_get(controller_path.classify) rescue nil + - object = model.find_by(id: params[:id]) + - if model.try(:nested_resource?) + li.breadcrumb-item = model.model_name.human(count: 2) + - if object + li.breadcrumb-item = object - else - = t("#{controller_name}.index.#{params[:action]}") - - else - li.breadcrumb-item.active = t("breadcrumbs.#{controller_name}.#{params[:action]}") + 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 + = t("#{controller_name}.index.#{params[:action]}") + - else + li.breadcrumb-item.active = t("breadcrumbs.#{controller_name}.#{params[:action]}") diff --git a/app/views/application/_flash.html.slim b/app/views/application/_flash.html.slim index 4a78c032..4675a913 100644 --- a/app/views/application/_flash.html.slim +++ b/app/views/application/_flash.html.slim @@ -1,4 +1,4 @@ -#flash-container +#flash-container.container #flash.container.fixed_error_messages data-message-failure=t('shared.message_failure') - %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" diff --git a/app/views/exercises/_editor.html.slim b/app/views/exercises/_editor.html.slim index 700a9855..7323f21e 100644 --- a/app/views/exercises/_editor.html.slim +++ b/app/views/exercises/_editor.html.slim @@ -5,26 +5,29 @@ - show_rfc_interventions = @show_rfc_interventions || "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) - 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' #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', icon: 'fa fa-desktop', id: 'render', label: t('exercises.editor.render')) - = 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-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-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-trophy', id: 'assess', label: t('exercises.editor.score'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + s')) + = 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')) 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')) 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')) 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')) unless @embed_options[:disable_score] // 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')) - @files.each do |file| + - file.read_only = true if @embed_options[:read_only] = render('editor_frame', exercise: exercise, file: file) #autosave-label = t('exercises.editor.lastsaved') span 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: 'break-intervention-modal', title: t('exercises.implement.break_intervention.title'), template: 'interventions/_break_intervention_modal') += 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') unless @embed_options[:disable_interventions] diff --git a/app/views/exercises/_editor_output.html.slim b/app/views/exercises/_editor_output.html.slim index dd6849da..91f289d8 100644 --- a/app/views/exercises/_editor_output.html.slim +++ b/app/views/exercises/_editor_output.html.slim @@ -10,17 +10,18 @@ div.h-100 id='output_sidebar_uncollapsed' class='d-none col-sm-12 enforce-bottom #results h2 = t('exercises.implement.results') p.test-count == t('exercises.implement.test_count', count: 0) - ul.list-unstyled - ul#dummies.d-none.list-unstyled - li.card.mt-2 - .card-header.py-2 - h5.card-title.m-0 == t('exercises.implement.file', filename: '', number: 0) - .card-body.bg-white.text-dark - = row(label: 'exercises.implement.passed_tests', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe) - = row(label: 'activerecord.attributes.submission.score', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe) - = row(label: 'exercises.implement.feedback') - = row(label: 'exercises.implement.error_messages') - /= row(label: 'exercises.implement.output', value: link_to(t('shared.show'), '#')) + - unless @embed_options[:hide_test_results] + ul.list-unstyled + ul#dummies.d-none.list-unstyled + li.card.mt-2 + .card-header.py-2 + h5.card-title.m-0 == t('exercises.implement.file', filename: '', number: 0) + .card-body.bg-white.text-dark + = row(label: 'exercises.implement.passed_tests', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe) + = row(label: 'activerecord.attributes.submission.score', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe) + = row(label: 'exercises.implement.feedback') + = 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) h4 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' span.input-group-btn button#prompt-submit.btn.btn-primary type="button" = t('exercises.editor.send') - #error-hints - .heading = t('exercises.implement.error_hints.heading') - ul.body + - unless @embed_options[:disable_hints] + #error-hints + .heading = t('exercises.implement.error_hints.heading') + ul.body #output.mt-2 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' .card-header = t('exercises.implement.flowr.heading') .card-body.text-dark.bg-white diff --git a/app/views/exercises/implement.html.slim b/app/views/exercises/implement.html.slim index 1e872cbf..96f4ff74 100644 --- a/app/views/exercises/implement.html.slim +++ b/app/views/exercises/implement.html.slim @@ -1,17 +1,18 @@ .row #editor-column.col-md-12 - .exercise.clearfix - div - span.badge.badge-pill.badge-primary.float-right.score + - unless @embed_options[:hide_exercise_description] + .exercise.clearfix + div + span.badge.badge-pill.badge-primary.float-right.score - h1 id="exercise-headline" - i class="fa fa-chevron-down" id="description-symbol" - = @exercise.title + h1 id="exercise-headline" + i class="fa fa-chevron-down" id="description-symbol" + = @exercise.title - #description-card.lead.description-card - = render_markdown(@exercise.description) + #description-card.lead.description-card + = 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' h4 = t('.alert.title') diff --git a/app/views/layouts/application.html.slim b/app/views/layouts/application.html.slim index 64b5549f..cdc3eb4e 100644 --- a/app/views/layouts/application.html.slim +++ b/app/views/layouts/application.html.slim @@ -13,22 +13,23 @@ html lang='en' = yield(:head) = csrf_meta_tags body - nav.navbar.navbar-dark.bg-dark.navbar-expand-md.mb-4.py-1 role='navigation' - .container - .navbar-brand - i.fa.fa-code - = application_name - button.navbar-toggler data-target='#navbar-collapse' data-toggle='collapse' type='button' aria-expanded='false' aria-label='Toggle navigation' - span.navbar-toggler-icon - #navbar-collapse.collapse.navbar-collapse - = render('navigation', cached: true) - ul.nav.navbar-nav.ml-auto - = render('locale_selector', cached: true) - li.nav-item.mr-3 = link_to(t('shared.help.link'), '#modal-help', data: {toggle: 'modal'}, class: 'nav-link') - = render('session') - .container data-controller=controller_name + - unless @embed_options[:hide_navbar] + nav.navbar.navbar-dark.bg-dark.navbar-expand-md.mb-4.py-1 role='navigation' + .container + .navbar-brand + i.fa.fa-code + = application_name + button.navbar-toggler data-target='#navbar-collapse' data-toggle='collapse' type='button' aria-expanded='false' aria-label='Toggle navigation' + span.navbar-toggler-icon + #navbar-collapse.collapse.navbar-collapse + = render('navigation', cached: true) + ul.nav.navbar-nav.ml-auto + = render('locale_selector', cached: true) + li.nav-item.mr-3 = link_to(t('shared.help.link'), '#modal-help', data: {toggle: 'modal'}, class: 'nav-link') + = render('session') + div data-controller=controller_name = 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") .container-fluid = yield diff --git a/app/views/submissions/show.json.jbuilder b/app/views/submissions/show.json.jbuilder index 07e199f4..b66919dc 100644 --- a/app/views/submissions/show.json.jbuilder +++ b/app/views/submissions/show.json.jbuilder @@ -1,8 +1,8 @@ json.extract! @submission, :id, :files -json.download_url download_submission_path(@submission) -json.score_url score_submission_path(@submission) -json.stop_url stop_submission_path(@submission) -json.download_file_url download_file_submission_path(@submission, 'a.').gsub(/a\.$/, '{filename}') -json.render_url render_submission_path(@submission, 'a.').gsub(/a\.$/, '{filename}') -json.run_url run_submission_path(@submission, 'a.').gsub(/a\.$/, '{filename}') -json.test_url test_submission_path(@submission, 'a.').gsub(/a\.$/, '{filename}') +json.download_url download_submission_path(@submission, format: :json) +json.score_url score_submission_path(@submission, format: :json) +json.stop_url stop_submission_path(@submission, format: :json) +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.', format: :json).gsub(/a\.\.json$/, '{filename}.json') +json.run_url run_submission_path(@submission, 'a.', format: :json).gsub(/a\.\.json$/, '{filename}.json') +json.test_url test_submission_path(@submission, 'a.', format: :json).gsub(/a\.\.json$/, '{filename}.json') diff --git a/lib/assets/javascripts/flash.js b/lib/assets/javascripts/flash.js index d7273864..7e7669a2 100644 --- a/lib/assets/javascripts/flash.js +++ b/lib/assets/javascripts/flash.js @@ -1,5 +1,5 @@ $( document ).on('turbolinks:load', function() { - var DURATION = 10000; + var DURATION = 5000; var SEVERITIES = ['danger', 'info', 'success', 'warning']; var buildFlash = function(options) { diff --git a/lib/assets/stylesheets/flash.css.scss b/lib/assets/stylesheets/flash.css.scss index 0abb6dc9..d339aff2 100644 --- a/lib/assets/stylesheets/flash.css.scss +++ b/lib/assets/stylesheets/flash.css.scss @@ -1,6 +1,5 @@ #flash-container { position: relative; - top: -21px; } .flash { diff --git a/spec/concerns/lti_spec.rb b/spec/concerns/lti_spec.rb index bdc5901e..37bb170f 100644 --- a/spec/concerns/lti_spec.rb +++ b/spec/concerns/lti_spec.rb @@ -19,6 +19,7 @@ describe Lti do it 'clears the session' do 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(:embed_options) controller.send(:clear_lti_session_data) end end diff --git a/spec/controllers/submissions_controller_spec.rb b/spec/controllers/submissions_controller_spec.rb index 6aaf8555..97dd2eb3 100644 --- a/spec/controllers/submissions_controller_spec.rb +++ b/spec/controllers/submissions_controller_spec.rb @@ -186,7 +186,7 @@ describe SubmissionsController do end 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 @@ -196,7 +196,7 @@ describe SubmissionsController do let(:url) { JSON.parse(response.body).with_indifferent_access.fetch("#{action}_url") } 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 diff --git a/spec/views/exercises/implement.html.slim_spec.rb b/spec/views/exercises/implement.html.slim_spec.rb index fe9ab0d0..1944aa31 100644 --- a/spec/views/exercises/implement.html.slim_spec.rb +++ b/spec/views/exercises/implement.html.slim_spec.rb @@ -10,6 +10,7 @@ describe 'exercises/implement.html.slim' do assign(:exercise, exercise) assign(:files, files) assign(:paths, []) + assign(:embed_options, {}) render end