diff --git a/Gemfile.lock b/Gemfile.lock index 3e7dd174..cbebab3f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -110,7 +110,7 @@ GEM excon (>= 0.38.0) json erubis (2.7.0) - excon (0.44.4) + excon (0.45.0) execjs (2.4.0) factory_girl (4.5.0) activesupport (>= 3.0.0) diff --git a/app/assets/javascripts/editor.js b/app/assets/javascripts/editor.js index fba0fe51..8f744e76 100644 --- a/app/assets/javascripts/editor.js +++ b/app/assets/javascripts/editor.js @@ -16,6 +16,7 @@ $(function() { var active_file = undefined; var active_frame = undefined; var running = false; + var qa_api = undefined; var flowrResultHtml = '
' @@ -128,22 +129,9 @@ $(function() { var evaluateCodeWithStreamedResponse = function(url, callback) { var event_source = new EventSource(url); - event_source.addEventListener('close', function(event) { - event_source.close(); - hideSpinner(); - running = false; - toggleButtonStates(); - if (JSON.parse(event.data).code !== 200) { - ajaxError(); - showTab(1); - } - if (qa_api) { - qa_api.executeCommand('syncOutput', [chunkBuffer]); - chunkBuffer = [{streamedResponse: true}]; - } - }); - event_source.addEventListener('error', ajaxError); + event_source.addEventListener('close', closeEventSource); + event_source.addEventListener('error', closeEventSource); event_source.addEventListener('hint', renderHint); event_source.addEventListener('info', storeContainerInformation); event_source.addEventListener('output', callback); @@ -154,11 +142,20 @@ $(function() { event_source.addEventListener('close', handleStderrOutputForFlowr); } + if (qa_api) { + event_source.addEventListener('close', handleStreamedResponseForCodePilot); + } + event_source.addEventListener('status', function(event) { showStatus(JSON.parse(event.data)); }); }; + var handleStreamedResponseForCodePilot = function(event) { + qa_api.executeCommand('syncOutput', [chunkBuffer]); + chunkBuffer = [{streamedResponse: true}]; + } + var evaluateCodeWithoutStreamedResponse = function(url, callback) { var jqxhr = ajax({ method: 'GET', @@ -277,9 +274,9 @@ $(function() { var handleTestResponse = function(response) { clearOutput(); printOutput(response[0], false, 0); - if (qa_api) { - qa_api.executeCommand('syncOutput', [response]); - } + if (qa_api) { + qa_api.executeCommand('syncOutput', [response]); + } showStatus(response[0]); showTab(2); }; @@ -347,7 +344,7 @@ $(function() { commentModal.find('#removeAllButton').off('click') commentModal.find('#addCommentButton').on('click', function(e){ - var user_id = 18 + var user_id = element.data('user-id') var commenttext = commentModal.find('textarea').val() if (commenttext !== "") { @@ -357,7 +354,7 @@ $(function() { }) commentModal.find('#removeAllButton').on('click', function(e){ - var user_id = 18; + var user_id = element.data('user-id') deleteComment(user_id,file_id,row,editor); commentModal.modal('hide') }) @@ -380,10 +377,7 @@ $(function() { } var setAnnotations = function (editor, file_id){ - - var session = editor.getSession(); - - // Retrieve comments for file and set them as annotations + var session = editor.getSession() var url = "/comments"; var jqrequest = $.ajax({ @@ -468,7 +462,7 @@ $(function() { url: '/comments', data: { id: annotation.id, - user_id: 18, + user_id: $('#editor').data('user-id'), comment: { row: annotation.row, text: annotation.text @@ -504,6 +498,7 @@ $(function() { $('#create-file').on('click', showFileDialog); $('#destroy-file').on('click', confirmDestroy); $('#download').on('click', downloadCode); + $('#request-for-comments').on('click', requestComments) }; var initializeTooltips = function() { @@ -529,15 +524,17 @@ $(function() { return 'executable' in active_frame.data(); }; - var setActiveFile = function (filename, fileId) { - active_file = { - filename: filename, - id: fileId - }; - } - var isActiveFileRenderable = function() { - return 'renderable' in active_frame.data(); - }; + var setActiveFile = function (filename, fileId) { + active_file = { + filename: filename, + id: fileId + }; + } + + var isActiveFileRenderable = function() { + return 'renderable' in active_frame.data(); + }; + var isActiveFileRunnable = function() { return isActiveFileExecutable() && ['main_file', 'user_defined_file'].includes(active_frame.data('role')); }; @@ -566,7 +563,7 @@ $(function() { panel.find('.row .col-sm-9').eq(3).find('a').attr('href', '#output-' + index); }; - var chunkBuffer = [{streamedResponse: true}]; + var chunkBuffer = [{streamedResponse: true}]; var printChunk = function(event) { var output = JSON.parse(event.data); @@ -612,10 +609,17 @@ $(function() { $('#results ul').first().html(''); $('.test-count .number').html(response.length); clearOutput(); + _.each(response, function(result, index) { printOutput(result, false, index); printScoringResult(result, index); }); + + if (_.some(response, function(result) { + return result.status === 'timeout'; + })) { + showTimeoutMessage(); + } if (qa_api) { // send test response to QA qa_api.executeCommand('syncOutput', [response]); @@ -743,7 +747,7 @@ $(function() { var showFirstFile = function() { var frame = $('.frame[data-role="main_file"]').isPresent() ? $('.frame[data-role="main_file"]') : $('.frame').first(); var file_id = frame.find('.editor').data('file-id'); - setActiveFile(frame.data('filename'), file_id); + setActiveFile(frame.data('filename'), file_id); $('#files').jstree().select_node(file_id); showFrame(frame); toggleButtonStates(); @@ -778,10 +782,7 @@ $(function() { var showStatus = function(output) { if (output.status === 'timeout') { - $.flash.danger({ - icon: ['fa', 'fa-clock-o'], - text: $('#editor').data('message-timeout') - }); + showTimeoutMessage(); } else if (output.stderr) { $.flash.danger({ icon: ['fa', 'fa-bug'], @@ -799,6 +800,13 @@ $(function() { $('a[data-toggle="tab"]').eq(index || 0).tab('show'); }; + var showTimeoutMessage = function() { + $.flash.danger({ + icon: ['fa', 'fa-clock-o'], + text: $('#editor').data('message-timeout') + }); + }; + var showWorkspaceTab = function(event) { event.preventDefault(); showTab(1); @@ -875,53 +883,55 @@ $(function() { $('#test').toggle(isActiveFileTestable()); }; - if ($('#editor').isPresent()) { - var qa_api; - if ($('#questions-column').isPresent() && QaApi.isBrowserSupported()) { - $('#editor-column').addClass('col-md-8').removeClass('col-md-10'); - $('#questions-column').addClass('col-md-3'); + var requestComments = function(e) { + var user_id = $('#editor').data('user-id') + var exercise_id = $('#editor').data('exercise-id') + var file_id = $('.editor').data('file-id') - var node = document.getElementById('questions-holder'); - var url = $('#questions-holder').data('url'); - - var qa_api = new QaApi(node, url); - } - configureEditors(); - initializeEditors(qa_api); - initializeEventHandlers(); - initializeFileTree(); - initializeTooltips(); - renderScore(); - showMainFile(); - showRequestedTab(); + $.ajax({ + method: 'POST', + url: '/request_for_comments', + data: { + request_for_comment: { + requestorid: user_id, + exerciseid: exercise_id, + fileid: file_id, + "requested_at(1i)": 2015, + "requested_at(2i)":3, + "requested_at(3i)":27, + "requested_at(4i)":17, + "requested_at(5i)":06 + } + } + }) } - var stderrOutput = '' - var handleStderrOutputForFlowr = function(event) { - var json = JSON.parse(event.data); + var initializeCodePilot = function() { + if ($('#questions-column').isPresent() && QaApi.isBrowserSupported()) { + $('#editor-column').addClass('col-md-8').removeClass('col-md-10'); + $('#questions-column').addClass('col-md-3'); - if (json.stderr) { - stderrOutput += json.stderr; - } else if (json.code) { - var flowrHintBody = $('#flowrHint .panel-body') + var node = document.getElementById('questions-holder'); + var url = $('#questions-holder').data('url'); - jQuery.getJSON(flowrUrl + '&query=' + escape(stderrOutput), function(data) { - for (var question in data.queryResults) { - // replace everything, not only one occurence - var collapsibleTileHtml = flowrResultHtml.replace(/{{collapseId}}/g, 'collapse-' + question).replace(/{{headingId}}/g, 'heading-' + question) - var resultTile = $(collapsibleTileHtml) - - resultTile.find('h4 > a').text(data.queryResults[question].title) - resultTile.find('.panel-body').append($(data.queryResults[question].body)) - - flowrHintBody.append(resultTile) + qa_api = new QaApi(node, url); } - - $('#flowrHint').fadeIn() - }) - - - stderrOutput = '' } - }; + + if ($('#editor').isPresent()) { + if (isBrowserSupported()) { + initializeCodePilot(); + $('.score, #development-environment').show(); + configureEditors(); + initializeEditors(); + initializeEventHandlers(); + initializeFileTree(); + initializeTooltips(); + renderScore(); + showFirstFile(); + showRequestedTab(); + } else { + $('#alert').show(); + } + } }); diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index d35c88cd..87fcec56 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -86,6 +86,12 @@ class ExercisesController < ApplicationController @submission = current_user.submissions.where(exercise_id: @exercise.id).order('created_at DESC').first @files = (@submission ? @submission.collect_files : @exercise.files).select(&:visible).sort_by(&:name_with_extension) @paths = collect_paths(@files) + + if current_user.respond_to? :external_id + @user_id = current_user.external_id + else + @user_id = '00000001-3100-4444-9999-000000000001' + end end def index diff --git a/app/controllers/request_for_comments_controller.rb b/app/controllers/request_for_comments_controller.rb new file mode 100644 index 00000000..fc0ab7b0 --- /dev/null +++ b/app/controllers/request_for_comments_controller.rb @@ -0,0 +1,62 @@ +class RequestForCommentsController < ApplicationController + before_action :set_request_for_comment, only: [:show, :edit, :update, :destroy] + + skip_after_action :verify_authorized + + + # GET /request_for_comments + # GET /request_for_comments.json + def index + @request_for_comments = RequestForComment.all + end + + # GET /request_for_comments/1 + # GET /request_for_comments/1.json + def show + end + + # GET /request_for_comments/new + def new + @request_for_comment = RequestForComment.new + end + + # GET /request_for_comments/1/edit + def edit + end + + # POST /request_for_comments + # POST /request_for_comments.json + def create + @request_for_comment = RequestForComment.new(request_for_comment_params) + + respond_to do |format| + if @request_for_comment.save + format.json { render :show, status: :created, location: @request_for_comment } + else + format.html { render :new } + format.json { render json: @request_for_comment.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /request_for_comments/1 + # DELETE /request_for_comments/1.json + def destroy + @request_for_comment.destroy + respond_to do |format| + format.html { redirect_to request_for_comments_url, notice: 'Request for comment was successfully destroyed.' } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_request_for_comment + @request_for_comment = RequestForComment.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def request_for_comment_params + params.require(:request_for_comment).permit(:requestorid, :exerciseid, :fileid, :requested_at) + end +end diff --git a/app/helpers/request_for_comments_helper.rb b/app/helpers/request_for_comments_helper.rb new file mode 100644 index 00000000..f46a73e4 --- /dev/null +++ b/app/helpers/request_for_comments_helper.rb @@ -0,0 +1,2 @@ +module RequestForCommentsHelper +end diff --git a/app/models/request_for_comment.rb b/app/models/request_for_comment.rb new file mode 100644 index 00000000..a22a7246 --- /dev/null +++ b/app/models/request_for_comment.rb @@ -0,0 +1,7 @@ +class RequestForComment < ActiveRecord::Base + before_create :set_requested_timestamp + + def set_requested_timestamp + self.requested_at = Time.now + end +end diff --git a/app/views/exercises/_editor.html.slim b/app/views/exercises/_editor.html.slim index ad637a36..b8450c29 100644 --- a/app/views/exercises/_editor.html.slim +++ b/app/views/exercises/_editor.html.slim @@ -1,4 +1,4 @@ -#editor.row data-exercise-id=exercise.id data-message-timeout=t('exercises.editor.timeout', permitted_execution_time: @exercise.execution_environment.permitted_execution_time) data-errors-url=execution_environment_errors_path(exercise.execution_environment) data-submissions-url=submissions_path +#editor.row data-exercise-id=exercise.id data-message-timeout=t('exercises.editor.timeout', permitted_execution_time: @exercise.execution_environment.permitted_execution_time) data-errors-url=execution_environment_errors_path(exercise.execution_environment) data-submissions-url=submissions_path data-user-id=@current_user.id .col-sm-3 = render('editor_file_tree', files: @files) #frames.col-sm-9 - @files.each do |file| diff --git a/app/views/exercises/_editor_file_tree.html.slim b/app/views/exercises/_editor_file_tree.html.slim index cad3c845..36ba8c37 100644 --- a/app/views/exercises/_editor_file_tree.html.slim +++ b/app/views/exercises/_editor_file_tree.html.slim @@ -5,5 +5,6 @@ hr = render('editor_button', classes: 'btn-block btn-primary btn-sm', data: {:'data-cause' => 'file'}, icon: 'fa fa-plus', id: 'create-file', label: t('exercises.editor.create_file')) = render('editor_button', classes: 'btn-block btn-warning btn-sm', data: {:'data-cause' => 'file', :'data-message-confirm' => t('shared.confirm_destroy')}, icon: 'fa fa-times', id: 'destroy-file', label: t('exercises.editor.destroy_file')) = render('editor_button', classes: 'btn-block btn-primary btn-sm', icon: 'fa fa-download', id: 'download', label: t('exercises.editor.download')) += render('editor_button', classes: 'btn-block btn-primary btn-sm', icon: 'fa fa-bullhorn', id: 'request-for-comments', label: 'Request comments') = render('shared/modal', id: 'modal-file', template: 'code_ocean/files/_form', title: t('exercises.editor.create_file')) diff --git a/app/views/exercises/_editor_frame.html.slim b/app/views/exercises/_editor_frame.html.slim index 9f72aa91..73421ff5 100644 --- a/app/views/exercises/_editor_frame.html.slim +++ b/app/views/exercises/_editor_frame.html.slim @@ -11,4 +11,5 @@ - else = link_to(file.native_file.file.name_with_extension, file.native_file.url) - else - .editor data-file-id=file.ancestor_id data-indent-size=file.file_type.indent_size data-mode=file.file_type.editor_mode data-read-only=file.read_only = file.content + .editor-content.hidden data-file-id=file.ancestor_id = file.content + .editor data-file-id=file.ancestor_id data-indent-size=file.file_type.indent_size data-mode=file.file_type.editor_mode data-read-only=file.read_only diff --git a/app/views/exercises/index.html.slim b/app/views/exercises/index.html.slim index 9cc44d32..d6e37ca9 100644 --- a/app/views/exercises/index.html.slim +++ b/app/views/exercises/index.html.slim @@ -40,4 +40,4 @@ h1 = Exercise.model_name.human(count: 2) td = link_to(t('shared.statistics'), statistics_exercise_path(exercise)) = render('shared/pagination', collection: @exercises) -p = render('shared/new_button', model: Exercise) +p = render('shared/new_button', model: Exercise) \ No newline at end of file diff --git a/app/views/layouts/application.html.slim b/app/views/layouts/application.html.slim index ee780de7..609579cf 100644 --- a/app/views/layouts/application.html.slim +++ b/app/views/layouts/application.html.slim @@ -34,6 +34,12 @@ html lang='en' .container data-controller=controller_name = render('breadcrumbs') = render('flash') - = yield + - if (controller_name == "exercises" && action_name == "implement") + .container-fluid + = yield + - else + .container + = yield + - template_variables = {execution_environment: @exercise.execution_environment} if action_name == 'implement' = render('shared/modal', classes: 'modal-lg', id: 'modal-help', template: 'application/help', template_variables: template_variables, title: t('shared.help.headline')) diff --git a/app/views/request_for_comments/_form.html.erb b/app/views/request_for_comments/_form.html.erb new file mode 100644 index 00000000..a8d037e9 --- /dev/null +++ b/app/views/request_for_comments/_form.html.erb @@ -0,0 +1,33 @@ +<%= form_for(@request_for_comment) do |f| %> + <% if @request_for_comment.errors.any? %> +
+

<%= pluralize(@request_for_comment.errors.count, "error") %> prohibited this request_for_comment from being saved:

+ + +
+ <% end %> + +
+ <%= f.label :requestorid %>
+ <%= f.number_field :requestorid %> +
+
+ <%= f.label :exerciseid %>
+ <%= f.number_field :exerciseid %> +
+
+ <%= f.label :fileid %>
+ <%= f.number_field :fileid %> +
+
+ <%= f.label :requested_at %>
+ <%= f.datetime_select :requested_at %> +
+
+ <%= f.submit %> +
+<% end %> diff --git a/app/views/request_for_comments/index.html.erb b/app/views/request_for_comments/index.html.erb new file mode 100644 index 00000000..d64d832a --- /dev/null +++ b/app/views/request_for_comments/index.html.erb @@ -0,0 +1,12 @@ +

Listing comment requests

+ +
+ <% @request_for_comments.each do |request_for_comment| %> + +

<%= Exercise.find(request_for_comment.exerciseid) %>

+

+ <%= InternalUser.find(request_for_comment.requestorid) %> | <%= request_for_comment.requested_at %> +

+
+ <% end %> +
diff --git a/app/views/request_for_comments/index.json.jbuilder b/app/views/request_for_comments/index.json.jbuilder new file mode 100644 index 00000000..26b37b67 --- /dev/null +++ b/app/views/request_for_comments/index.json.jbuilder @@ -0,0 +1,4 @@ +json.array!(@request_for_comments) do |request_for_comment| + json.extract! request_for_comment, :id, :requestorid, :exerciseid, :fileid, :requested_at + json.url request_for_comment_url(request_for_comment, format: :json) +end diff --git a/app/views/request_for_comments/show.html.erb b/app/views/request_for_comments/show.html.erb new file mode 100644 index 00000000..5a785a5e --- /dev/null +++ b/app/views/request_for_comments/show.html.erb @@ -0,0 +1,9 @@ +
+

<%= Exercise.find(@request_for_comment.exerciseid) %>

+

+ <%= InternalUser.find(@request_for_comment.requestorid) %> | <%= @request_for_comment.requested_at %> +

+
+ +
+
\ No newline at end of file diff --git a/app/views/request_for_comments/show.json.jbuilder b/app/views/request_for_comments/show.json.jbuilder new file mode 100644 index 00000000..27897168 --- /dev/null +++ b/app/views/request_for_comments/show.json.jbuilder @@ -0,0 +1 @@ +json.extract! @request_for_comment, :id, :requestorid, :exerciseid, :fileid, :requested_at, :created_at, :updated_at diff --git a/config/locales/de.yml b/config/locales/de.yml index 1410ec5f..e44da63a 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -180,7 +180,7 @@ de: stop: Stoppen submit: Code zur Bewertung abgeben test: Testen - timeout: 'Ihr Code benötigte länger als die erlaubten %{permitted_execution_time} Sekunden, um zu terminieren.' + timeout: 'Ihr Code hat die erlaubte Ausführungszeit von %{permitted_execution_time} Sekunden überschritten.' tooltips: save: Ihr Code wird automatisch gespeichert, wann immer Sie eine Datei herunterladen, ausführen oder testen. Explizites Speichern ist also selten notwendig. editor_file_tree: diff --git a/config/locales/en.yml b/config/locales/en.yml index bfcf49fe..4ee929ec 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -180,7 +180,7 @@ en: stop: Stop submit: Submit Code For Assessment test: Test - timeout: 'Your code took longer than the permitted %{permitted_execution_time} seconds to run.' + timeout: 'Your code exceeded the permitted execution time of %{permitted_execution_time} seconds.' tooltips: save: Your code is automatically saved whenever you download, run, or test it. Therefore, explicitly saving is rarely necessary. editor_file_tree: diff --git a/config/routes.rb b/config/routes.rb index 1645a919..a20b8fef 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,7 @@ FILENAME_REGEXP = /[\w\.]+/ unless Kernel.const_defined?(:FILENAME_REGEXP) Rails.application.routes.draw do + resources :request_for_comments resources :comments, except: [:destroy] do collection do delete :destroy diff --git a/db/migrate/20150327141740_create_request_for_comments.rb b/db/migrate/20150327141740_create_request_for_comments.rb new file mode 100644 index 00000000..ad3882d2 --- /dev/null +++ b/db/migrate/20150327141740_create_request_for_comments.rb @@ -0,0 +1,12 @@ +class CreateRequestForComments < ActiveRecord::Migration + def change + create_table :request_for_comments do |t| + t.integer :requestorid, :null => false + t.integer :exerciseid, :null => false + t.integer :fileid, :null => false + t.timestamp :requested_at + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 7006b710..e1aed2ef 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150317115338) do +ActiveRecord::Schema.define(version: 20150327141740) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -165,6 +165,15 @@ ActiveRecord::Schema.define(version: 20150317115338) do add_index "internal_users_teams", ["internal_user_id"], name: "index_internal_users_teams_on_internal_user_id", using: :btree add_index "internal_users_teams", ["team_id"], name: "index_internal_users_teams_on_team_id", using: :btree + create_table "request_for_comments", force: true do |t| + t.integer "requestorid", null: false + t.integer "exerciseid", null: false + t.integer "fileid", null: false + t.datetime "requested_at" + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "submissions", force: true do |t| t.integer "exercise_id" t.float "score" diff --git a/lib/assets/javascripts/flash.js b/lib/assets/javascripts/flash.js index 412e74e1..cd5cb72d 100644 --- a/lib/assets/javascripts/flash.js +++ b/lib/assets/javascripts/flash.js @@ -5,11 +5,7 @@ var buildFlash = function(options) { if (options.text) { var container = options.container; - var html = ''; - if (options.icon) { - html += ' '; - } - html += options.text; + var html = (options.icon ? '' : '') + options.text; container.html(html); showFlashes(); }