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..6c90abd7 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 0988d8f0..99ce4289 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 @@ -39,4 +39,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 ff5e6ccf..2e193167 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, @@ -148,6 +149,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 e60797d5..079500cc 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/proxy_exercises_controller.rb b/app/controllers/proxy_exercises_controller.rb index 171fe061..b703488a 100644 --- a/app/controllers/proxy_exercises_controller.rb +++ b/app/controllers/proxy_exercises_controller.rb @@ -9,7 +9,7 @@ class ProxyExercisesController < ApplicationController private :authorize! def clone - proxy_exercise = @proxy_exercise.duplicate(token: nil, exercises: @proxy_exercise.exercises) + proxy_exercise = @proxy_exercise.duplicate(public: false, token: nil, exercises: @proxy_exercise.exercises, user: current_user) proxy_exercise.send(:generate_token) if proxy_exercise.save redirect_to(proxy_exercise, notice: t('shared.object_cloned', model: ProxyExercise.model_name.human)) @@ -39,7 +39,7 @@ class ProxyExercisesController < ApplicationController end def proxy_exercise_params - params[:proxy_exercise].permit(:description, :title, :exercise_ids => []) if params[:proxy_exercise].present? + params[:proxy_exercise].permit(:description, :title, :public, :exercise_ids => []).merge(user_id: current_user.id, user_type: current_user.class.name) if params[:proxy_exercise].present? end private :proxy_exercise_params 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..bc3ec4a6 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -122,13 +122,18 @@ class SubmissionsController < ApplicationController def run # TODO reimplement SSEs with websocket commands # with_server_sent_events do |server_sent_event| - # output = @docker_client.execute_run_command(@submission, params[:filename]) + # output = @docker_client.execute_run_command(@submission, sanitize_filename) # server_sent_event.write({stdout: output[:stdout]}, event: 'output') if output[:stdout] # server_sent_event.write({stderr: output[:stderr]}, event: 'output') if output[:stderr] # 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 @@ -142,7 +147,7 @@ class SubmissionsController < ApplicationController # give the docker_client the tubesock object, so that it can send messages (timeout) @docker_client.tubesock = tubesock - result = @docker_client.execute_run_command(@submission, params[:filename]) + result = @docker_client.execute_run_command(@submission, sanitize_filename) tubesock.send_data JSON.dump({'cmd' => 'status', 'status' => result[:status]}) if result[:status] == :container_running @@ -195,6 +200,10 @@ class SubmissionsController < ApplicationController # save the output of this "run" as a "testrun" (scoring runs are saved in submission_scoring.rb) save_run_output + if @run_output.blank? + parse_message t('exercises.implement.no_output', timestamp: l(Time.now, format: :short)), 'stdout', tubesock + end + # Hijacked connection needs to be notified correctly tubesock.send_data JSON.dump({'cmd' => 'exit'}) tubesock.close @@ -214,8 +223,8 @@ class SubmissionsController < ApplicationController @run_output = 'timeout: ' + @run_output # add information that this run timed out to the buffer else # Filter out information about run_command, test_command, user or working directory - run_command = @submission.execution_environment.run_command % command_substitutions(params[:filename]) - test_command = @submission.execution_environment.test_command % command_substitutions(params[:filename]) + run_command = @submission.execution_environment.run_command % command_substitutions(sanitize_filename) + test_command = @submission.execution_environment.test_command % command_substitutions(sanitize_filename) unless /root|workspace|#{run_command}|#{test_command}/.match(message) parse_message(message, 'stdout', tubesock) end @@ -291,6 +300,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 +322,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}) @@ -320,7 +335,7 @@ class SubmissionsController < ApplicationController private :set_docker_client def set_file - @file = @files.detect { |file| file.name_with_extension == params[:filename] } + @file = @files.detect { |file| file.name_with_extension == sanitize_filename } head :not_found unless @file end private :set_file @@ -361,7 +376,7 @@ class SubmissionsController < ApplicationController hijack do |tubesock| Thread.new { EventMachine.run } unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive? - output = @docker_client.execute_test_command(@submission, params[:filename]) + output = @docker_client.execute_test_command(@submission, sanitize_filename) # tubesock is the socket to the client tubesock.send_data JSON.dump(output) @@ -405,4 +420,8 @@ class SubmissionsController < ApplicationController end path end + + def sanitize_filename + params[:filename].gsub(/\.json$/, '') + end end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 708baf03..1f88c014 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -1,4 +1,10 @@ class UserMailer < ActionMailer::Base + + def mail(*args) + # used to prevent the delivery to pseudonymous users without a valid email address + super unless args.first[:to].blank? + end + def activation_needed_email(user) @activation_url = activate_internal_user_url(user, token: user.activation_token) mail(subject: t('mailers.user_mailer.activation_needed.subject'), to: user.email) diff --git a/app/models/external_user.rb b/app/models/external_user.rb index b877f08d..fbd3cd3d 100644 --- a/app/models/external_user.rb +++ b/app/models/external_user.rb @@ -5,11 +5,10 @@ class ExternalUser < ApplicationRecord validates :external_id, presence: true def displayname - result = name - if(result == nil || result == "") - result = "User " + id.to_s + if name.blank? + "User " + id.to_s + else + name end - result end - end diff --git a/app/models/proxy_exercise.rb b/app/models/proxy_exercise.rb index 328fffa6..bb17b30a 100644 --- a/app/models/proxy_exercise.rb +++ b/app/models/proxy_exercise.rb @@ -1,11 +1,16 @@ class ProxyExercise < ApplicationRecord + include Creation + include DefaultValues after_initialize :generate_token after_initialize :set_reason + after_initialize :set_default_values has_and_belongs_to_many :exercises has_many :user_proxy_exercise_exercises + validates :public, boolean_presence: true + def count_files exercises.count end @@ -19,6 +24,11 @@ class ProxyExercise < ApplicationRecord end private :generate_token + def set_default_values + set_default_values_if_present(public: false) + end + private :set_default_values + def duplicate(attributes = {}) proxy_exercise = dup proxy_exercise.attributes = attributes diff --git a/app/views/application/_breadcrumbs.html.slim b/app/views/application/_breadcrumbs.html.slim index fb3a656e..4240834f 100644 --- a/app/views/application/_breadcrumbs.html.slim +++ b/app/views/application/_breadcrumbs.html.slim @@ -1,19 +1,20 @@ - if current_user.try(:admin?) or current_user.try(:teacher?) - 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_if(policy(model).show?, model.model_name.human(count: 2), send(:"#{model.model_name.collection}_path")) - - if object - li.breadcrumb-item = link_to_if(policy(object).show?, 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_if(policy(model).show?, model.model_name.human(count: 2), send(:"#{model.model_name.collection}_path")) + - if object + li.breadcrumb-item = link_to_if(policy(object).show?, 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/application/welcome.html.slim b/app/views/application/welcome.html.slim index eb8ebac3..0eee6f95 100644 --- a/app/views/application/welcome.html.slim +++ b/app/views/application/welcome.html.slim @@ -1,7 +1,7 @@ h1 = t('.title', application_name: application_name) - if current_user.try(:admin?) or current_user.try(:teacher?) - p = t('.text_signed_in_as_internal_user', user_name: current_user.name) + p = t('.text_signed_in_as_internal_user', user_name: current_user.displayname) - elsif current_user.try(:external_user?) p = t('.text_signed_in_as_external_user', application_name: application_name) - else diff --git a/app/views/execution_environments/shell.html.slim b/app/views/execution_environments/shell.html.slim index 5b499a35..ae35c7b8 100644 --- a/app/views/execution_environments/shell.html.slim +++ b/app/views/execution_environments/shell.html.slim @@ -4,5 +4,5 @@ h1 = @execution_environment .form-group label for='command' = t('.command') input#command.form-control type='text' - pre#output data-message-no-output=t('exercises.implement.no_output') + pre#output data-message-no-output=t('exercises.implement.no_output', timestamp: l(Time.now, format: :short)) p = t('exercises.implement.no_output_yet') diff --git a/app/views/exercise_collections/show.html.slim b/app/views/exercise_collections/show.html.slim index fb1eded4..592935d4 100644 --- a/app/views/exercise_collections/show.html.slim +++ b/app/views/exercise_collections/show.html.slim @@ -3,7 +3,7 @@ h1 = render('shared/edit_button', object: @exercise_collection) = row(label: 'exercise_collections.name', value: @exercise_collection.name) -= row(label: 'exercise_collections.user', value: link_to_if(policy(@exercise_collection.user).show?, @exercise_collection.user.name, @exercise_collection.user)) unless @exercise_collection.user.nil? += row(label: 'exercise_collections.user', value: link_to_if(policy(@exercise_collection.user).show?, @exercise_collection.user.displayname, @exercise_collection.user)) unless @exercise_collection.user.nil? = row(label: 'exercise_collections.use_anomaly_detection', value: @exercise_collection.use_anomaly_detection) = row(label: 'exercise_collections.updated_at', value: @exercise_collection.updated_at) @@ -24,5 +24,5 @@ h4.mt-4 = t('activerecord.attributes.exercise_collections.exercises') td = exercise_collection_item.position td = link_to_if(policy(exercise).show?, exercise.title, exercise) td = link_to_if(exercise.execution_environment && policy(exercise.execution_environment).show?, exercise.execution_environment, exercise.execution_environment) - td = link_to_if(exercise.user && policy(exercise.user).show?, exercise.user.name, exercise.user) + td = link_to_if(exercise.user && policy(exercise.user).show?, exercise.user.displayname, exercise.user) td = link_to(t('shared.statistics'), statistics_exercise_path(exercise), 'data-turbolinks' => "false") if policy(exercise).statistics? 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..7948e548 100644 --- a/app/views/exercises/_editor_output.html.slim +++ b/app/views/exercises/_editor_output.html.slim @@ -1,6 +1,6 @@ div id='output_sidebar_collapsed' = render('editor_button', classes: 'btn-block btn-primary btn', data: {:'data-toggle' => 'tooltip', :'data-placement' => 'left'}, title: t('exercises.editor.expand_output_sidebar'), icon: 'fa fa-plus-square', id: 'toggle-sidebar-output-collapsed', label: '') -div.h-100 id='output_sidebar_uncollapsed' class='d-none col-sm-12 enforce-bottom-margin' data-message-no-output=t('exercises.implement.no_output') +div.h-100 id='output_sidebar_uncollapsed' class='d-none col-sm-12 enforce-bottom-margin' data-message-no-output=t('exercises.implement.no_output_yet') .row = render('editor_button', classes: 'btn-block btn-primary btn', icon: 'fa fa-minus-square', id: 'toggle-sidebar-output', label: t('exercises.editor.collapse_output_sidebar')) @@ -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/feedback.html.slim b/app/views/exercises/feedback.html.slim index a00e1be6..3405a542 100644 --- a/app/views/exercises/feedback.html.slim +++ b/app/views/exercises/feedback.html.slim @@ -13,7 +13,7 @@ h1 = link_to_if(policy(@exercise).show?, @exercise, exercise_path(@exercise)) li.card.mt-2 .card-header role="tab" id="heading" div.clearfix.feedback-header - span.username = link_to(feedback.user.name, statistics_external_user_exercise_path(id: @exercise.id, external_user_id: feedback.user.id)) + span.username = link_to(feedback.user.displayname, statistics_external_user_exercise_path(id: @exercise.id, external_user_id: feedback.user.id)) - if feedback.anomaly_notification i class="fa fa-envelope-o" data-placement="top" data-toggle="tooltip" data-container="body" title=feedback.anomaly_notification.reason span.date = feedback.created_at 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/external_users/index.html.slim b/app/views/external_users/index.html.slim index 1b24fa4d..ea6d7a48 100644 --- a/app/views/external_users/index.html.slim +++ b/app/views/external_users/index.html.slim @@ -10,7 +10,7 @@ h1 = ExternalUser.model_name.human(count: 2) tbody - @users.each do |user| tr - td = link_to_if(policy(user).show?, user.name) + td = link_to_if(policy(user).show?, user.displayname) td = link_to_if(policy(user.consumer).show?, user.consumer, user.consumer) td = link_to(t('shared.show'), user) if policy(user).show? diff --git a/app/views/external_users/show.html.slim b/app/views/external_users/show.html.slim index ea26d4d2..4e3aa620 100644 --- a/app/views/external_users/show.html.slim +++ b/app/views/external_users/show.html.slim @@ -1,4 +1,4 @@ -h1 = @user.name +h1 = @user.displayname = row(label: 'external_user.name', value: @user.name) //= row(label: 'external_user.email', value: @user.email) diff --git a/app/views/layouts/application.html.slim b/app/views/layouts/application.html.slim index 7ba2cdd3..6714534d 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(:admin?) or current_user.try(:teacher?) + = render('breadcrumbs') if (current_user.try(:admin?) or current_user.try(:teacher?)) && !@embed_options[:hide_navbar] - if (controller_name == "exercises" && action_name == "implement") .container-fluid = yield diff --git a/app/views/proxy_exercises/_form.html.slim b/app/views/proxy_exercises/_form.html.slim index fce092ba..acc54ccc 100644 --- a/app/views/proxy_exercises/_form.html.slim +++ b/app/views/proxy_exercises/_form.html.slim @@ -6,6 +6,10 @@ .form-group = f.label(:description) = f.pagedown :description, input_html: { preview: true, rows: 10 } + .form-check.mb-3 + label.form-check-label + = f.check_box(:public, class: 'form-check-input') + = t('activerecord.attributes.exercise.public') h3 Exercises .table-responsive diff --git a/app/views/proxy_exercises/index.html.slim b/app/views/proxy_exercises/index.html.slim index 2cc84454..af569e4e 100644 --- a/app/views/proxy_exercises/index.html.slim +++ b/app/views/proxy_exercises/index.html.slim @@ -11,13 +11,17 @@ h1 = ProxyExercise.model_name.human(count: 2) tr th.p-1 = sort_link(@search, :title, t('activerecord.attributes.proxy_exercise.title')) th.p-1 = t('activerecord.attributes.exercise.token') + th.p-1 = t('activerecord.attributes.exercise.public') th.p-1 = t('activerecord.attributes.proxy_exercise.files_count') th.p-1 colspan=2 = t('shared.actions') tbody - @proxy_exercises.each do |proxy_exercise| tr data-id=proxy_exercise.id td.p-1.pt-2 = link_to_if(policy(proxy_exercise).show?, proxy_exercise.title, proxy_exercise) - td.p-1.pt-2 = proxy_exercise.token + td.p-1.pt-2 + code + = proxy_exercise.token + td.p-1.pt-2.public data-value=proxy_exercise.public? = symbol_for(proxy_exercise.public?) td.p-1.pt-2 = proxy_exercise.count_files td.p-1.pt-2 = link_to(t('shared.edit'), edit_proxy_exercise_path(proxy_exercise)) if policy(proxy_exercise).edit? diff --git a/app/views/proxy_exercises/show.html.slim b/app/views/proxy_exercises/show.html.slim index ec8f3212..544dbeda 100644 --- a/app/views/proxy_exercises/show.html.slim +++ b/app/views/proxy_exercises/show.html.slim @@ -10,9 +10,12 @@ h1 = render('shared/edit_button', object: @proxy_exercise) = row(label: 'exercise.title', value: @proxy_exercise.title) += row(label: 'exercise.user', value: link_to_if(policy(@proxy_exercise.author).show?, @proxy_exercise.author, @proxy_exercise.author)) = row(label: 'proxy_exercise.files_count', value: @exercises.count) += row(label: 'exercise.public', value: @proxy_exercise.public?) = row(label: 'exercise.description', value: @proxy_exercise.description) -= row(label: 'exercise.token', value: @proxy_exercise.token) += row(label: 'exercise.embedding_parameters', class: 'mb-4') do + = content_tag(:input, nil, class: 'form-control mb-4', readonly: true, value: embedding_parameters(@proxy_exercise)) h2.mt-4 Exercises .table-responsive 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/app/views/user_exercise_feedbacks/index.html.slim b/app/views/user_exercise_feedbacks/index.html.slim index ba69183f..85c7f6dc 100644 --- a/app/views/user_exercise_feedbacks/index.html.slim +++ b/app/views/user_exercise_feedbacks/index.html.slim @@ -18,7 +18,7 @@ h1 = UserExerciseFeedback.model_name.human(count: 2) tbody - @uefs.each do |uef| tr - td = link_to_if(policy(uef.user).show?, uef.user.name) + td = link_to_if(policy(uef.user).show?, uef.user.displayname) td = link_to_if(policy(uef.exercise).show?, uef.exercise.title, uef.exercise) td = link_to(t('shared.show'), uef) if policy(uef).show? td = link_to(t('shared.destroy'), uef, data: {confirm: t('shared.confirm_destroy')}, method: :delete) if policy(uef).destroy? diff --git a/app/views/user_mailer/send_thank_you_note.slim b/app/views/user_mailer/send_thank_you_note.slim index 6215ed54..4b2a8b73 100644 --- a/app/views/user_mailer/send_thank_you_note.slim +++ b/app/views/user_mailer/send_thank_you_note.slim @@ -1,5 +1,5 @@ == t('mailers.user_mailer.send_thank_you_note.body', receiver_displayname: @receiver_displayname, link_to_comment: link_to(@rfc_link, @rfc_link), - author: @author, + author: @author.displayname, thank_you_note: @thank_you_note ) diff --git a/config/locales/de.yml b/config/locales/de.yml index 1c001661..d33d587b 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -301,7 +301,7 @@ de: file: 'Test-Datei %{number} (%{filename})' hint: Tipp no_files: Die Aufgabe umfasst noch keine sichtbaren Dateien. - no_output: Die letzte Code-Ausführung hat keine Ausgabe erzeugt. + no_output: Die letzte Code-Ausführung terminierte am %{timestamp} ohne Ausgabe. no_output_yet: Bisher existiert noch keine Ausgabe. output: Programm-Ausgabe passed_tests: Erfolgreiche Tests diff --git a/config/locales/en.yml b/config/locales/en.yml index c82839d4..4b43cdf4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -301,7 +301,7 @@ en: file: 'Test File %{number} (%{filename})' hint: Hint no_files: The exercise does not comprise visible files yet. - no_output: The last code run has not generated any output. + no_output: The last code run finished on %{timestamp} without any output. no_output_yet: There is no output yet. output: Program Output passed_tests: Passed Tests diff --git a/config/routes.rb b/config/routes.rb index 21972b23..d8a62acc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -150,7 +150,7 @@ Rails.application.routes.draw do post '/lti/launch', as: 'lti_launch', to: 'sessions#create_through_lti' get '/lti/return', as: 'lti_return', to: 'sessions#destroy_through_lti' get '/sign_in', as: 'sign_in', to: 'sessions#new' - delete '/sign_out', as: 'sign_out', to: 'sessions#destroy' + match '/sign_out', as: 'sign_out', to: 'sessions#destroy', via: [:get, :delete] resources :submissions, only: [:create, :index, :show] do member do diff --git a/db/migrate/20181119161514_add_user_to_proxy_exercise.rb b/db/migrate/20181119161514_add_user_to_proxy_exercise.rb new file mode 100644 index 00000000..983ce778 --- /dev/null +++ b/db/migrate/20181119161514_add_user_to_proxy_exercise.rb @@ -0,0 +1,9 @@ +class AddUserToProxyExercise < ActiveRecord::Migration[5.2] + def change + add_reference :proxy_exercises, :user, polymorphic: true, index: true + add_column :proxy_exercises, :public, :boolean, null: false, default: false + + internal_user = InternalUser.find_by(id: 46) || InternalUser.first + ProxyExercise.update_all(user_id: internal_user.id, user_type: internal_user.class.name) + end +end diff --git a/db/schema.rb b/db/schema.rb index b2fe9d07..73cf1e9d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -264,6 +264,10 @@ ActiveRecord::Schema.define(version: 2018_11_29_093207) do t.string "token" t.datetime "created_at" t.datetime "updated_at" + t.string "user_type" + t.bigint "user_id" + t.boolean "public" + t.index ["user_type", "user_id"], name: "index_proxy_exercises_on_user_type_and_user_id" end create_table "remote_evaluation_mappings", force: :cascade do |t| 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/factories/proxy_exercise.rb b/spec/factories/proxy_exercise.rb index 3927541f..3118da81 100644 --- a/spec/factories/proxy_exercise.rb +++ b/spec/factories/proxy_exercise.rb @@ -1,5 +1,6 @@ FactoryBot.define do factory :proxy_exercise, class: ProxyExercise do + created_by_teacher token { 'dummytoken' } title { 'Dummy' } end diff --git a/spec/features/authentication_spec.rb b/spec/features/authentication_spec.rb index 2a777dab..1cd08727 100644 --- a/spec/features/authentication_spec.rb +++ b/spec/features/authentication_spec.rb @@ -38,8 +38,8 @@ describe 'Authentication' do visit(root_path) end - it "displays the user's name" do - expect(page).to have_content(user.name) + it "displays the user's displayname" do + expect(page).to have_content(user.displayname) end it 'displays a sign out link' do 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