diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index bcb3faea..21afeec7 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -13,6 +13,7 @@ $(function() { var THEME = 'ace/theme/textmate'; var REMEMBER_TAB = false; var AUTOSAVE_INTERVAL = 15 * 1000; + var REQUEST_FOR_COMMENTS_DELAY = 3 * 60 * 1000; var NONE = 0; var WEBSOCKET = 1; var SERVER_SEND_EVENT = 2; @@ -110,18 +111,6 @@ $(function() { var createSubmission = function(initiator, filter, callback) { showSpinner(initiator); - - var annotations_arr = []; - - $('.editor').each(function(index, element) { - var editor = ace.edit(element); - var cleaned_annotations = editor.getSession().getAnnotations(); - for(var i = cleaned_annotations.length-1; i>=0; --i){ - cleaned_annotations[i].text = cleaned_annotations[i].text.replace(cleaned_annotations[i].username + ": ", ""); - } - annotations_arr = annotations_arr.concat(editor.getSession().getAnnotations()); - }); - var jqxhr = ajax({ data: { submission: { @@ -129,7 +118,7 @@ $(function() { exercise_id: $('#editor').data('exercise-id'), files_attributes: (filter || _.identity)(collectFiles()) }, - annotations_arr: annotations_arr + annotations_arr: [] }, dataType: 'json', method: 'POST', @@ -169,7 +158,6 @@ $(function() { } } } - setAnnotations(editors[i], $(editors[i].container).data('id')); } // toggle button states (it might be the case that the request for comments button has to be enabled toggleButtonStates(); @@ -256,8 +244,6 @@ $(function() { } }; - - var getPanelClass = function(result) { if (result.stderr && !result.score) { return 'panel-danger'; @@ -425,7 +411,6 @@ $(function() { session.setUseWrapMode(true); var file_id = $(element).data('id'); - //setAnnotations(editor, file_id); /* * Register event handlers @@ -434,17 +419,6 @@ $(function() { // editor itself editor.on("paste", handlePasteEvent); editor.on("copy", handleCopyEvent); - editor.on("guttermousedown", handleSidebarClick); - - /* // alternative: - editor.on("guttermousedown", function(e) { - handleSidebarClick(e); - }); - */ - - //session - session.on('annotationRemoval', handleAnnotationRemoval); - session.on('annotationChange', handleAnnotationChange); // listener for autosave session.on("change", function (deltaObject) { @@ -453,158 +427,6 @@ $(function() { }); }; - var hasCommentsInRow = function (editor, row){ - return editor.getSession().getAnnotations().some(function(element) { - return element.row === row; - }) - }; - - var getCommentsForRow = function (editor, row){ - return editor.getSession().getAnnotations().filter(function(element) { - return element.row === row; - }) - }; - - var setAnnotations = function (editor, file_id){ - var session = editor.getSession(); - var url = "/comments"; - - var jqrequest = $.ajax({ - dataType: 'json', - method: 'GET', - url: url, - data: { - file_id: file_id - } - }); - - jqrequest.done(function(response){ - setAnnotationsCallback(response, session); - }); - jqrequest.fail(ajaxError); - }; - - var setAnnotationsCallback = function (response, session) { - var annotations = response; - - // add classname and the username in front of each comment - $.each(annotations, function(index, comment){ - comment.className = "code-ocean_comment"; - comment.text = comment.username + ": " + comment.text; - }); - - session.setAnnotations(annotations); - } - - var deleteComment = function (file_id, row, editor) { - var jqxhr = $.ajax({ - type: 'DELETE', - url: "/comments", - data: { - row: row, - file_id: file_id } - }); - jqxhr.done(function (response) { - setAnnotations(editor, file_id); - }); - jqxhr.fail(ajaxError); - } - - var createComment = function (file_id, row, editor, commenttext){ - var jqxhr = $.ajax({ - data: { - comment: { - file_id: file_id, - row: row, - column: 0, - text: commenttext - } - }, - dataType: 'json', - method: 'POST', - url: "/comments" - }); - jqxhr.done(function(response){ - setAnnotations(editor, file_id); - }); - jqxhr.fail(ajaxError); - }; - - var handleAnnotationRemoval = function(removedAnnotations) { - removedAnnotations.forEach(function(annotation) { - $.ajax({ - method: 'DELETE', - url: '/comment_by_id', - data: { - id: annotation.id, - } - }) - }) - }; - - var handleAnnotationChange = function(changedAnnotations) { - changedAnnotations.forEach(function(annotation) { - $.ajax({ - method: 'PUT', - url: '/comments', - data: { - id: annotation.id, - user_id: $('#editor').data('user-id'), - comment: { - row: annotation.row, - text: annotation.text - } - } - }) - }) - }; - - // Code for clicks on gutter / sidepanel - var handleSidebarClick = function(e) { - var target = e.domEvent.target; - var editor = e.editor; - - if (target.className.indexOf("ace_gutter-cell") == -1) return; - if (!editor.isFocused()) return; - if (e.clientX > 25 + target.getBoundingClientRect().left) return; - - var row = e.getDocumentPosition().row; - e.stop(); - - var commentModal = $('#comment-modal'); - - if (hasCommentsInRow(editor, row)) { - var rowComments = getCommentsForRow(editor, row); - var comments = _.pluck(rowComments, 'text').join('\n'); - commentModal.find('#other-comments').text(comments); - } else { - commentModal.find('#other-comments').text('none'); - } - - commentModal.find('#addCommentButton').off('click'); - commentModal.find('#removeAllButton').off('click'); - - commentModal.find('#addCommentButton').on('click', function(e){ - var commenttext = commentModal.find('textarea').val(); - // attention: use id of data attribute here, not file-id (file-id is the original file) - var file_id = $(editor.container).data('id'); - - if (commenttext !== "") { - createComment(file_id, row, editor, commenttext); - commentModal.modal('hide'); - } - }); - - commentModal.find('#removeAllButton').on('click', function(e){ - // attention: use id of data attribute here, not file-id (file-id is the original file) - var file_id = $(editor.container).data('id'); - deleteComment(file_id,row, editor); - commentModal.modal('hide'); - }); - - commentModal.modal('show'); - }; - var initializeEventHandlers = function() { $(document).on('click', '#results a', showOutput); $(document).on('keypress', handleKeyPress); @@ -612,6 +434,7 @@ $(function() { initializeFileTreeButtons(); initializeWorkflowButtons(); initializeWorkspaceButtons(); + initializeRequestForComments() }; var initializeFileTree = function() { @@ -654,6 +477,20 @@ $(function() { $('#start-over').on('click', confirmReset); }; + var initializeRequestForComments = function () { + var button = $('#requestCommentsButton'); + button.hide(); + button.on('click', function() { + $('#comment-modal').modal('show'); + }); + + $('#askForCommentsButton').on('click', requestComments); + + setTimeout(function() { + button.fadeIn(); + }, REQUEST_FOR_COMMENTS_DELAY); + }; + var isActiveFileBinary = function() { return 'binary' in active_frame.data(); }; @@ -667,7 +504,7 @@ $(function() { filename: filename, id: fileId }; - } + }; var isActiveFileRenderable = function() { return 'renderable' in active_frame.data(); @@ -1125,7 +962,6 @@ $(function() { $('#run').toggle(isActiveFileRunnable() && !running); $('#stop').toggle(isActiveFileStoppable()); $('#test').toggle(isActiveFileTestable()); - $('#request-for-comments').toggle(isActiveFileSubmission() && !isActiveFileBinary()); }; var initWebsocketConnection = function(url) { @@ -1308,10 +1144,11 @@ $(function() { } }; - var requestComments = function(e) { + var requestComments = function() { var user_id = $('#editor').data('user-id') var exercise_id = $('#editor').data('exercise-id') var file_id = $('.editor').data('id') + var question = $('#question').val(); $.ajax({ method: 'POST', @@ -1320,6 +1157,7 @@ $(function() { request_for_comment: { exercise_id: exercise_id, file_id: file_id, + question: question, "requested_at(1i)": 2015, // these are the timestamp values that the request handler demands "requested_at(2i)":3, // they could be random here, because the timestamp is updated on serverside anyway "requested_at(3i)":27, @@ -1328,13 +1166,13 @@ $(function() { } } }).done(function() { - hideSpinner() - $.flash.success({ text: 'Request for comments sent!' }) - }) + hideSpinner(); + $.flash.success({ text: $('#askForCommentsButton').data('message-success') }) + }).error(ajaxError); - showSpinner($('#request-for-comments')) - // hide button until next submission is created - $('#request-for-comments').toggle(false); + $('#comment-modal').modal('hide'); + var button = $('#requestCommentsButton'); + button.fadeOut(); } var initializeCodePilot = function() { diff --git a/app/assets/stylesheets/editor.css.scss b/app/assets/stylesheets/editor.css.scss index efcf1d25..ef298c9e 100644 --- a/app/assets/stylesheets/editor.css.scss +++ b/app/assets/stylesheets/editor.css.scss @@ -88,3 +88,10 @@ button i.fa-spin { color: #777; font-size: 0.8em; } + +#requestCommentsButton { + position: relative; + margin-top: -50px; + margin-right: 25px; + float: right; +} diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 15bf99fb..2335a469 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -41,7 +41,7 @@ class CommentsController < ApplicationController # if the user is internal, set the name @comments.map{|comment| - comment.username = comment.user.name + comment.username = comment.user.displayname # alternative: # if the user is external, fetch the displayname from xikolo # Xikolo::UserClient.get(comment.user_id.to_s)[:display_name] } @@ -111,14 +111,13 @@ class CommentsController < ApplicationController end def destroy - @comments = Comment.where(file_id: params[:file_id], row: params[:row]) - @comments.delete_all + @comments = Comment.where(file_id: params[:file_id], row: params[:row], user: current_user) + @comments.each { |comment| authorize comment; comment.destroy } respond_to do |format| #format.html { redirect_to comments_url, notice: 'Comments were successfully destroyed.' } format.html { head :no_content, notice: 'Comments were successfully destroyed.' } format.json { head :no_content } end - authorize! end private diff --git a/app/controllers/request_for_comments_controller.rb b/app/controllers/request_for_comments_controller.rb index 1394f0c3..555dad09 100644 --- a/app/controllers/request_for_comments_controller.rb +++ b/app/controllers/request_for_comments_controller.rb @@ -70,6 +70,6 @@ class RequestForCommentsController < ApplicationController # Never trust parameters from the scary internet, only allow the white list through. def request_for_comment_params - params.require(:request_for_comment).permit(:exercise_id, :file_id, :requested_at).merge(user_id: current_user.id, user_type: current_user.class.name) + params.require(:request_for_comment).permit(:exercise_id, :file_id, :question, :requested_at).merge(user_id: current_user.id, user_type: current_user.class.name) end end diff --git a/app/models/external_user.rb b/app/models/external_user.rb index c1d9ad28..8038d2dc 100644 --- a/app/models/external_user.rb +++ b/app/models/external_user.rb @@ -3,4 +3,13 @@ class ExternalUser < ActiveRecord::Base validates :consumer_id, presence: true validates :external_id, presence: true + + def displayname + result = name + if(consumer.name == 'openHPI') + result = Xikolo::UserClient.get(external_id.to_s)[:display_name] + end + result + end + end diff --git a/app/models/internal_user.rb b/app/models/internal_user.rb index 54b8df4d..e5cebde9 100644 --- a/app/models/internal_user.rb +++ b/app/models/internal_user.rb @@ -21,4 +21,9 @@ class InternalUser < ActiveRecord::Base def teacher? role == 'teacher' end + + def displayname + name + end + end diff --git a/app/models/request_for_comment.rb b/app/models/request_for_comment.rb index e685805f..af3676c0 100644 --- a/app/models/request_for_comment.rb +++ b/app/models/request_for_comment.rb @@ -25,6 +25,10 @@ class RequestForComment < ActiveRecord::Base limit 1").first end + def to_s + "RFC-" + self.id.to_s + end + private def self.row_number_user_sql select("id, user_id, exercise_id, file_id, requested_at, created_at, updated_at, user_type, row_number() OVER (PARTITION BY user_id ORDER BY created_at DESC) as row_number").to_sql diff --git a/app/views/application/_session.html.slim b/app/views/application/_session.html.slim index 52858f8d..cbbf3b12 100644 --- a/app/views/application/_session.html.slim +++ b/app/views/application/_session.html.slim @@ -1,19 +1,17 @@ - if current_user - - if current_user.internal_user? - li.dropdown - a.dropdown-toggle data-toggle='dropdown' href='#' - i.fa.fa-user - = current_user - span.caret - ul.dropdown-menu role='menu' + li.dropdown + a.dropdown-toggle data-toggle='dropdown' href='#' + i.fa.fa-user + = current_user + span.caret + ul.dropdown-menu role='menu' + - if current_user.internal_user? li = link_to(t('consumers.show.link'), current_user.consumer) if current_user.consumer li = link_to(t('internal_users.show.link'), current_user) + li = link_to(t('request_for_comments.index.all'), request_for_comments_path) + li = link_to(t('request_for_comments.index.get_my_comment_requests'), my_request_for_comments_path) + - if current_user.internal_user? li = link_to(t('sessions.destroy.link'), sign_out_path, method: :delete) - - else - li - p.navbar-text - i.fa.fa-user - = current_user - else li = link_to(sign_in_path) do i.fa.fa-sign-in diff --git a/app/views/exercises/_editor.html.slim b/app/views/exercises/_editor.html.slim index cc5dfe7e..42b12e42 100644 --- a/app/views/exercises/_editor.html.slim +++ b/app/views/exercises/_editor.html.slim @@ -38,4 +38,4 @@ = t('exercises.editor.test') = render('editor_button', data: {:'data-placement' => 'top', :'data-tooltip' => true}, icon: 'fa fa-trophy', id: 'assess', label: t('exercises.editor.score'), title: t('shared.tooltips.shortcut', shortcut: 'ALT + s')) -= render('shared/modal', id: 'comment-modal', title: t('exercises.implement.comment.dialogtitle'), template: 'exercises/_comment_dialogcontent') \ No newline at end of file += render('shared/modal', id: 'comment-modal', title: t('exercises.implement.comment.request'), template: 'exercises/_request_comment_dialogcontent') \ No newline at end of file diff --git a/app/views/exercises/_editor_file_tree.html.slim b/app/views/exercises/_editor_file_tree.html.slim index 9ed5626d..cad3c845 100644 --- a/app/views/exercises/_editor_file_tree.html.slim +++ b/app/views/exercises/_editor_file_tree.html.slim @@ -5,6 +5,5 @@ 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: t('exercises.editor.requestComments')) = 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 dc077e02..9c3cc738 100644 --- a/app/views/exercises/_editor_frame.html.slim +++ b/app/views/exercises/_editor_frame.html.slim @@ -13,3 +13,7 @@ - else .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 data-id=file.id + + button.btn.btn-primary id='requestCommentsButton' type='button' + i.fa.fa-comment-o + = t('exercises.editor.requestComments') \ No newline at end of file diff --git a/app/views/exercises/_request_comment_dialogcontent.html.slim b/app/views/exercises/_request_comment_dialogcontent.html.slim new file mode 100644 index 00000000..4fc34dcd --- /dev/null +++ b/app/views/exercises/_request_comment_dialogcontent.html.slim @@ -0,0 +1,4 @@ +h5 = t('exercises.implement.comment.question') +textarea.form-control#question(style='resize:none;') +p = '' +button#askForCommentsButton.btn.btn-block.btn-primary(type='button' data-message-success=t('exercises.editor.request_for_comments_sent')) =t('exercises.implement.comment.request') \ No newline at end of file diff --git a/app/views/request_for_comments/index.html.slim b/app/views/request_for_comments/index.html.slim index 89d8c239..80c17bf3 100644 --- a/app/views/request_for_comments/index.html.slim +++ b/app/views/request_for_comments/index.html.slim @@ -1,7 +1,7 @@ h1 = RequestForComment.model_name.human(count: 2) .table-responsive - table.table + table.table.sortable thead tr th = t('activerecord.attributes.request_for_comments.exercise') @@ -13,7 +13,7 @@ h1 = RequestForComment.model_name.human(count: 2) tr data-id=request_for_comment.id td = link_to(request_for_comment.exercise.title, request_for_comment) td = request_for_comment.exercise.execution_environment - td = request_for_comment.user.name + td = request_for_comment.user.displayname td = request_for_comment.requested_at = render('shared/pagination', collection: @request_for_comments) \ No newline at end of file diff --git a/app/views/request_for_comments/show.html.erb b/app/views/request_for_comments/show.html.erb index a47eba32..5564d187 100644 --- a/app/views/request_for_comments/show.html.erb +++ b/app/views/request_for_comments/show.html.erb @@ -14,6 +14,13 @@ %> <%= user %> | <%= @request_for_comment.requested_at %>
+