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? %> ++ <%= InternalUser.find(request_for_comment.requestorid) %> | <%= request_for_comment.requested_at %> +
+ + <% end %> ++ <%= InternalUser.find(@request_for_comment.requestorid) %> | <%= @request_for_comment.requested_at %> +
+