Merge branch 'master' into john-graph
# Conflicts: # Gemfile
This commit is contained in:
1
Gemfile
1
Gemfile
@ -37,6 +37,7 @@ gem 'tubesock'
|
||||
gem 'faye-websocket'
|
||||
gem 'nokogiri'
|
||||
gem 'd3-rails'
|
||||
gem 'rest-client’
|
||||
|
||||
group :development do
|
||||
gem 'better_errors', platform: :ruby
|
||||
|
13
Gemfile.lock
13
Gemfile.lock
@ -107,6 +107,8 @@ GEM
|
||||
docker-api (1.25.0)
|
||||
excon (>= 0.38.0)
|
||||
json
|
||||
domain_name (0.5.25)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
erubis (2.7.0)
|
||||
eventmachine (1.0.9.1)
|
||||
eventmachine (1.0.9.1-java)
|
||||
@ -127,6 +129,8 @@ GEM
|
||||
forgery (0.6.0)
|
||||
highline (1.7.8)
|
||||
hike (1.2.3)
|
||||
http-cookie (1.0.2)
|
||||
domain_name (~> 0.5)
|
||||
i18n (0.7.0)
|
||||
ims-lti (1.1.10)
|
||||
builder
|
||||
@ -157,6 +161,7 @@ GEM
|
||||
net-scp (1.2.1)
|
||||
net-ssh (>= 2.6.5)
|
||||
net-ssh (3.0.2)
|
||||
netrc (0.10.3)
|
||||
newrelic_rpm (3.14.3.313)
|
||||
nokogiri (1.6.7.2)
|
||||
mini_portile2 (~> 2.0.0.rc2)
|
||||
@ -220,6 +225,10 @@ GEM
|
||||
polyamorous (~> 1.2)
|
||||
rdoc (4.2.2)
|
||||
json (~> 1.4)
|
||||
rest-client (1.8.0)
|
||||
http-cookie (>= 1.0.2, < 2.0)
|
||||
mime-types (>= 1.16, < 3.0)
|
||||
netrc (~> 0.7)
|
||||
rspec (3.4.0)
|
||||
rspec-core (~> 3.4.0)
|
||||
rspec-expectations (~> 3.4.0)
|
||||
@ -313,6 +322,9 @@ GEM
|
||||
uglifier (2.7.2)
|
||||
execjs (>= 0.3.0)
|
||||
json (>= 1.8.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.1)
|
||||
unicode-display_width (0.3.1)
|
||||
web-console (2.3.0)
|
||||
activemodel (>= 4.0)
|
||||
@ -375,6 +387,7 @@ DEPENDENCIES
|
||||
rails-i18n (~> 4.0.0)
|
||||
rake
|
||||
ransack
|
||||
rest-client
|
||||
rspec-autotest
|
||||
rspec-rails
|
||||
rubocop
|
||||
|
@ -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();
|
||||
@ -737,10 +574,14 @@ $(function() {
|
||||
// output_mode_is_streaming = false;
|
||||
//}
|
||||
if (!colorize) {
|
||||
var stream = _.sortBy([output.stderr || '', output.stdout || ''], function(stream) {
|
||||
return stream.length;
|
||||
})[1];
|
||||
element.append(stream);
|
||||
if(output.stdout != ''){
|
||||
element.append(output.stdout)
|
||||
}
|
||||
|
||||
if(output.stderr != ''){
|
||||
element.append('There was an error: StdErr: ' + output.stderr);
|
||||
}
|
||||
|
||||
} else if (output.stderr) {
|
||||
element.addClass('text-warning').append(output.stderr);
|
||||
} else if (output.stdout) {
|
||||
@ -796,10 +637,16 @@ $(function() {
|
||||
|
||||
var payload = {
|
||||
user: {
|
||||
resource_uuid: $('#editor').data('user-id')
|
||||
type: 'User',
|
||||
uuid: $('#editor').data('user-id')
|
||||
},
|
||||
verb: {
|
||||
type: eventName
|
||||
},
|
||||
resource: {
|
||||
type: 'page',
|
||||
uuid: document.location.href
|
||||
},
|
||||
verb: eventName,
|
||||
resource: {},
|
||||
timestamp: new Date().toISOString(),
|
||||
with_result: {},
|
||||
in_context: contextData
|
||||
@ -1119,7 +966,6 @@ $(function() {
|
||||
$('#run').toggle(isActiveFileRunnable() && !running);
|
||||
$('#stop').toggle(isActiveFileStoppable());
|
||||
$('#test').toggle(isActiveFileTestable());
|
||||
$('#request-for-comments').toggle(isActiveFileSubmission() && !isActiveFileBinary());
|
||||
};
|
||||
|
||||
var initWebsocketConnection = function(url) {
|
||||
@ -1302,10 +1148,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',
|
||||
@ -1314,6 +1161,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,
|
||||
@ -1322,13 +1170,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() {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -14,6 +14,21 @@ module CodeOcean
|
||||
create_and_respond(object: @file, path: proc { implement_exercise_path(@file.context.exercise, tab: 2) })
|
||||
end
|
||||
|
||||
def create_and_respond(options = {})
|
||||
@object = options[:object]
|
||||
respond_to do |format|
|
||||
if @object.save
|
||||
yield if block_given?
|
||||
path = options[:path].try(:call) || @object
|
||||
respond_with_valid_object(format, notice: t('shared.object_created', model: @object.class.model_name.human), path: path, status: :created)
|
||||
else
|
||||
filename = (@object.path || '') + '/' + (@object.name || '') + (@object.file_type.file_extension || '')
|
||||
format.html { redirect_to(options[:path]); flash[:danger] = t('files.error.filename', name: filename) }
|
||||
format.json { render(json: @object.errors, status: :unprocessable_entity) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@file = CodeOcean::File.find(params[:id])
|
||||
authorize!
|
||||
|
@ -12,41 +12,16 @@ class CommentsController < ApplicationController
|
||||
# GET /comments
|
||||
# GET /comments.json
|
||||
def index
|
||||
#@comments = Comment.all
|
||||
#if admin, show all comments.
|
||||
#check whether user is the author of the passed file_id, if so, show all comments. otherwise, only show comments of the file-author and own comments
|
||||
file = CodeOcean::File.find(params[:file_id])
|
||||
#there might be no submission yet, so dont use find
|
||||
submission = Submission.find_by(id: file.context_id)
|
||||
if submission
|
||||
is_admin = false
|
||||
user_id = current_user.id
|
||||
|
||||
# if we have an internal user, check whether he is an admin
|
||||
if not current_user.respond_to? :external_id
|
||||
is_admin = current_user.role == 'admin'
|
||||
end
|
||||
|
||||
if(is_admin || user_id == submission.user_id)
|
||||
# fetch all comments for this file
|
||||
@comments = Comment.where(file_id: params[:file_id])
|
||||
else
|
||||
# fetch comments of the current user
|
||||
#@comments = Comment.where(file_id: params[:file_id], user_id: user_id)
|
||||
# fetch comments of file-author and the current user
|
||||
@comments = Comment.where(file_id: params[:file_id], user_id: [user_id, submission.user_id])
|
||||
end
|
||||
|
||||
#add names to comments
|
||||
# if the user is internal, set the name
|
||||
|
||||
@comments.map{|comment|
|
||||
comment.username = comment.user.name
|
||||
# alternative: # if the user is external, fetch the displayname from xikolo
|
||||
# Xikolo::UserClient.get(comment.user_id.to_s)[:display_name]
|
||||
comment.username = comment.user.displayname
|
||||
}
|
||||
else
|
||||
@comments = Comment.all.limit(0) #we need an empty relation here
|
||||
@comments = []
|
||||
end
|
||||
authorize!
|
||||
end
|
||||
@ -111,14 +86,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
|
||||
|
@ -119,7 +119,7 @@ class ExercisesController < ApplicationController
|
||||
private :user_by_code_harbor_token
|
||||
|
||||
def exercise_params
|
||||
params[:exercise].permit(:description, :execution_environment_id, :file_id, :instructions, :public, :hide_file_tree, :team_id, :title, files_attributes: file_attributes).merge(user_id: current_user.id, user_type: current_user.class.name)
|
||||
params[:exercise].permit(:description, :execution_environment_id, :file_id, :instructions, :public, :hide_file_tree, :allow_file_creation, :team_id, :title, files_attributes: file_attributes).merge(user_id: current_user.id, user_type: current_user.class.name)
|
||||
end
|
||||
private :exercise_params
|
||||
|
||||
|
@ -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
|
||||
|
@ -124,7 +124,7 @@ class SubmissionsController < ApplicationController
|
||||
|
||||
tubesock.onmessage do |data|
|
||||
Rails.logger.info(Time.now.getutc.to_s + ": Client sending: " + data)
|
||||
# Check wether the client send a JSON command and kill container
|
||||
# Check whether the client send a JSON command and kill container
|
||||
# if the command is 'exit', send it to docker otherwise.
|
||||
begin
|
||||
parsed = JSON.parse(data)
|
||||
|
@ -2,6 +2,19 @@ require File.expand_path('../../../uploaders/file_uploader', __FILE__)
|
||||
require File.expand_path('../../../../lib/active_model/validations/boolean_presence_validator', __FILE__)
|
||||
|
||||
module CodeOcean
|
||||
|
||||
class FileNameValidator < ActiveModel::Validator
|
||||
def validate(record)
|
||||
existing_files = File.where(name: record.name, path: record.path, file_type_id: record.file_type_id,
|
||||
context_id: record.context_id, context_type: record.context_type).to_a
|
||||
unless existing_files.empty?
|
||||
if (not record.context.is_a?(Exercise)) || (record.context.new_record?)
|
||||
record.errors[:base] << 'Duplicate'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class File < ActiveRecord::Base
|
||||
include DefaultValues
|
||||
|
||||
@ -44,6 +57,8 @@ module CodeOcean
|
||||
validates :weight, if: :teacher_defined_test?, numericality: true, presence: true
|
||||
validates :weight, absence: true, unless: :teacher_defined_test?
|
||||
|
||||
validates_with FileNameValidator, fields: [:name, :path, :file_type_id]
|
||||
|
||||
ROLES.each do |role|
|
||||
define_method("#{role}?") { self.role == role }
|
||||
end
|
||||
|
@ -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
|
||||
|
@ -21,4 +21,9 @@ class InternalUser < ActiveRecord::Base
|
||||
def teacher?
|
||||
role == 'teacher'
|
||||
end
|
||||
|
||||
def displayname
|
||||
name
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -25,8 +25,12 @@ 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
|
||||
select("id, user_id, exercise_id, file_id, question, 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
|
||||
end
|
||||
end
|
||||
|
@ -8,7 +8,11 @@ class CommentPolicy < ApplicationPolicy
|
||||
everyone
|
||||
end
|
||||
|
||||
[:new?, :show?, :destroy?].each do |action|
|
||||
def show?
|
||||
everyone
|
||||
end
|
||||
|
||||
[:new?, :destroy?].each do |action|
|
||||
define_method(action) { admin? || author? }
|
||||
end
|
||||
|
||||
|
@ -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'
|
||||
- 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
|
||||
|
@ -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')
|
||||
= render('shared/modal', id: 'comment-modal', title: t('exercises.implement.comment.request'), template: 'exercises/_request_comment_dialogcontent')
|
@ -2,9 +2,9 @@
|
||||
|
||||
hr
|
||||
|
||||
- if @exercise.allow_file_creation?
|
||||
= 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'))
|
||||
|
||||
= render('editor_button', classes: 'btn-block btn-primary btn-sm', icon: 'fa fa-download', id: 'download', label: t('exercises.editor.download'))
|
||||
|
@ -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.requestCommentsButton type='button'
|
||||
i.fa.fa-comment-o
|
||||
= t('exercises.editor.requestComments')
|
@ -28,6 +28,10 @@
|
||||
label
|
||||
= f.check_box(:hide_file_tree)
|
||||
= t('activerecord.attributes.exercise.hide_file_tree')
|
||||
.checkbox
|
||||
label
|
||||
= f.check_box(:allow_file_creation)
|
||||
= t('activerecord.attributes.exercise.allow_file_creation')
|
||||
h2 = t('activerecord.attributes.exercise.files')
|
||||
ul#files.list-unstyled.panel-group
|
||||
= f.fields_for :files do |files_form|
|
||||
|
@ -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')
|
@ -28,7 +28,7 @@ h1 = Exercise.model_name.human(count: 2)
|
||||
tr data-id=exercise.id
|
||||
td = exercise.title
|
||||
td = link_to_if(policy(exercise.author).show?, exercise.author, exercise.author)
|
||||
td = link_to_if(policy(exercise.execution_environment).show?, exercise.execution_environment, exercise.execution_environment)
|
||||
td = link_to_if(exercise.execution_environment && policy(exercise.execution_environment).show?, exercise.execution_environment, exercise.execution_environment)
|
||||
td = exercise.files.teacher_defined_tests.count
|
||||
td = exercise.maximum_score
|
||||
td.public data-value=exercise.public? = symbol_for(exercise.public?)
|
||||
|
@ -16,6 +16,7 @@ h1
|
||||
= row(label: 'exercise.maximum_score', value: @exercise.maximum_score)
|
||||
= row(label: 'exercise.public', value: @exercise.public?)
|
||||
= row(label: 'exercise.hide_file_tree', value: @exercise.hide_file_tree?)
|
||||
= row(label: 'exercise.allow_file_creation', value: @exercise.allow_file_creation?)
|
||||
= row(label: 'exercise.embedding_parameters') do
|
||||
= content_tag(:input, nil, class: 'form-control', readonly: true, value: embedding_parameters(@exercise))
|
||||
|
||||
|
@ -22,7 +22,7 @@ html lang='en'
|
||||
span.icon-bar
|
||||
span.icon-bar
|
||||
span.icon-bar
|
||||
a.navbar-brand href=root_path
|
||||
.navbar-brand
|
||||
i.fa.fa-code
|
||||
= application_name
|
||||
#navbar-collapse.collapse.navbar-collapse
|
||||
|
@ -1,19 +1,22 @@
|
||||
h1 = RequestForComment.model_name.human(count: 2)
|
||||
|
||||
.table-responsive
|
||||
table.table
|
||||
table.table.sortable
|
||||
thead
|
||||
tr
|
||||
th = t('activerecord.attributes.request_for_comments.exercise')
|
||||
th = t('activerecord.attributes.request_for_comments.execution_environment')
|
||||
th = t('activerecord.attributes.request_for_comments.question')
|
||||
th = t('activerecord.attributes.request_for_comments.username')
|
||||
th = t('activerecord.attributes.request_for_comments.requested_at')
|
||||
tbody
|
||||
- @request_for_comments.each do |request_for_comment|
|
||||
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.requested_at
|
||||
- if request_for_comment.has_attribute?(:question) && request_for_comment.question
|
||||
td = truncate(request_for_comment.question, length: 200)
|
||||
- else
|
||||
td = '-'
|
||||
td = request_for_comment.user.displayname
|
||||
td = t('shared.time.before', time: distance_of_time_in_words_to_now(request_for_comment.requested_at))
|
||||
|
||||
= render('shared/pagination', collection: @request_for_comments)
|
@ -14,6 +14,13 @@
|
||||
%>
|
||||
<%= user %> | <%= @request_for_comment.requested_at %>
|
||||
</p>
|
||||
<h5>
|
||||
<% if @request_for_comment.question and not @request_for_comment.question == '' %>
|
||||
<%= t('activerecord.attributes.request_for_comments.question')%>: "<%= @request_for_comment.question %>"
|
||||
<% else %>
|
||||
<%= t('request_for_comments.no_question') %>
|
||||
<% end %>
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
@ -25,27 +32,22 @@ do not put a carriage return in the line below. it will be present in the presen
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= render('shared/modal', id: 'comment-modal', title: t('exercises.implement.comment.dialogtitle'), template: 'exercises/_comment_dialogcontent') %>
|
||||
|
||||
<script type="text/javascript">
|
||||
var commentitor = $('.editor');
|
||||
var userid = commentitor.data('user-id');
|
||||
|
||||
commentitor.each(function (index, editor) {
|
||||
currentEditor = ace.edit(editor);
|
||||
var currentEditor = ace.edit(editor);
|
||||
currentEditor.setReadOnly(true);
|
||||
|
||||
setAnnotations(currentEditor, $(editor).data('file-id'));
|
||||
currentEditor.on("guttermousedown", handleSidebarClick);
|
||||
});
|
||||
|
||||
function setAnnotations(editor, fileid) {
|
||||
var session = editor.getSession()
|
||||
|
||||
var inputHtml = ''
|
||||
inputHtml += '<div class="input-group">'
|
||||
inputHtml += '<input type="text" class="form-control" id="commentInput"'
|
||||
inputHtml += 'placeholder="I\'d suggest a variable here" required>'
|
||||
inputHtml += '<span class="input-group-btn"><button id="submitComment"'
|
||||
inputHtml += 'class="btn btn-default"><%= t("exercises.implement.comment.addComment") %>!</button></span>'
|
||||
inputHtml += '</div>'
|
||||
var session = editor.getSession();
|
||||
|
||||
var jqrequest = $.ajax({
|
||||
dataType: 'json',
|
||||
@ -58,57 +60,109 @@ do not put a carriage return in the line below. it will be present in the presen
|
||||
|
||||
jqrequest.done(function(response){
|
||||
$.each(response, function(index, comment) {
|
||||
comment.className = "code-ocean_comment"
|
||||
comment.className = "code-ocean_comment";
|
||||
comment.text = comment.username + ": " + comment.text
|
||||
})
|
||||
});
|
||||
|
||||
editor.getSession().setAnnotations(response)
|
||||
|
||||
$(editor.container).find('.ace_gutter-cell').popover({
|
||||
title: 'Add a comment',
|
||||
html: true,
|
||||
content: inputHtml,
|
||||
position: 'right',
|
||||
trigger: 'focus click'
|
||||
}).on('shown.bs.popover', function() {
|
||||
var container = $(editor.container)
|
||||
container.find('#commentInput').focus()
|
||||
container.find('#submitComment').click(_.partial(addComment, editor, fileid));
|
||||
container.find('#commentInput').data('line', $(this).text())
|
||||
})
|
||||
session.setAnnotations(response);
|
||||
})
|
||||
}
|
||||
|
||||
function addComment(editor, fileid) {
|
||||
var commentInput = $(editor.container).find('#commentInput');
|
||||
var comment = commentInput.val()
|
||||
var line = commentInput.data('line')
|
||||
|
||||
if (line == '' || comment == '') {
|
||||
return
|
||||
} else {
|
||||
line = parseInt(line) - 1
|
||||
function hasCommentsInRow(editor, row){
|
||||
return editor.getSession().getAnnotations().some(function(element) {
|
||||
return element.row === row;
|
||||
})
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
function getCommentsForRow(editor, row){
|
||||
return editor.getSession().getAnnotations().filter(function(element) {
|
||||
return element.row === row;
|
||||
})
|
||||
}
|
||||
|
||||
function deleteComment(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);
|
||||
}
|
||||
|
||||
function createComment(file_id, row, editor, commenttext){
|
||||
var jqxhr = $.ajax({
|
||||
data: {
|
||||
comment: {
|
||||
user_id: userid,
|
||||
file_id: fileid,
|
||||
row: line,
|
||||
file_id: file_id,
|
||||
row: row,
|
||||
column: 0,
|
||||
text: comment
|
||||
text: commenttext
|
||||
}
|
||||
},
|
||||
dataType: 'json',
|
||||
method: 'POST',
|
||||
url: "/comments"
|
||||
}).done(setAnnotations(editor, fileid))
|
||||
|
||||
$('.ace_gutter-cell').popover('hide')
|
||||
});
|
||||
jqxhr.done(function(response){
|
||||
setAnnotations(editor, file_id);
|
||||
});
|
||||
jqxhr.fail(ajaxError);
|
||||
}
|
||||
|
||||
function handleSidebarClick(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();
|
||||
var file_id = $(editor.container).data('file-id');
|
||||
|
||||
if (commenttext !== "") {
|
||||
createComment(file_id, row, editor, commenttext);
|
||||
commentModal.modal('hide');
|
||||
}
|
||||
});
|
||||
|
||||
commentModal.find('#removeAllButton').on('click', function(e){
|
||||
var file_id = $(editor.container).data('file-id');
|
||||
deleteComment(file_id, row, editor);
|
||||
commentModal.modal('hide');
|
||||
});
|
||||
|
||||
commentModal.modal('show');
|
||||
}
|
||||
|
||||
function ajaxError(response) {
|
||||
var message = ((response || {}).responseJSON || {}).message || '';
|
||||
|
||||
$.flash.danger({
|
||||
text: message.length > 0 ? message : $('#flash').data('message-failure')
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
#commentitor, .ace_gutter, .ace_gutter-layer { overflow: visible }
|
||||
.popover { width: 400px; max-width: none; }
|
||||
</style>
|
||||
|
@ -38,6 +38,7 @@ de:
|
||||
team_id: Team
|
||||
title: Titel
|
||||
user: Autor
|
||||
allow_file_creation: "Dateierstellung erlauben"
|
||||
external_user:
|
||||
consumer: Konsument
|
||||
email: E-Mail
|
||||
@ -81,6 +82,7 @@ de:
|
||||
execution_environment: Sprache
|
||||
username: Benutzername
|
||||
requested_at: Angefragezeitpunkt
|
||||
question: "Frage"
|
||||
submission:
|
||||
cause: Anlass
|
||||
code: Code
|
||||
@ -209,6 +211,7 @@ de:
|
||||
timeout: 'Ausführung gestoppt. 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.
|
||||
request_for_comments_sent: "Kommentaranfrage gesendet."
|
||||
editor_file_tree:
|
||||
file_root: Dateien
|
||||
file_form:
|
||||
@ -243,8 +246,10 @@ de:
|
||||
others: Andere Kommentare auf dieser Zeile
|
||||
addyours: Fügen Sie Ihren Kommentar hinzu
|
||||
addComment: Kommentieren
|
||||
removeAllOnLine: Alle Kommentare auf dieser Zeile löschen
|
||||
removeAllOnLine: Meine Kommentare auf dieser Zeile löschen
|
||||
listing: Die neuesten Kommentaranfragen
|
||||
request: "Kommentaranfrage stellen"
|
||||
question: "Bitte beschreiben Sie kurz ihre Problem oder nennen Sie den Programmteil, zu dem sie Feedback wünschen."
|
||||
index:
|
||||
clone: Duplizieren
|
||||
implement: Implementieren
|
||||
@ -288,6 +293,8 @@ de:
|
||||
teacher_defined_test: Test als Bewertungsgrundlage
|
||||
user_defined_file: Benutzerdefinierte Datei
|
||||
user_defined_test: Benutzerdefinierter Test
|
||||
error:
|
||||
filename: "Die Datei konnte nicht gespeichert werden, da eine Datei mit dem Namen '%{name}' bereits existiert."
|
||||
hints:
|
||||
form:
|
||||
hints:
|
||||
@ -322,6 +329,8 @@ de:
|
||||
request_for_comments:
|
||||
index:
|
||||
get_my_comment_requests: Meine Kommentaranfragen
|
||||
all: "Alle Kommentaranfragen"
|
||||
no_question: "Der Autor hat keine Frage zu dieser Anfrage gestellt."
|
||||
sessions:
|
||||
create:
|
||||
failure: Fehlerhafte E-Mail oder Passwort.
|
||||
@ -390,6 +399,8 @@ de:
|
||||
shortcut: 'Tastaturkürzel: %{shortcut}'
|
||||
update: '%{model} aktualisieren'
|
||||
upload_file: Datei hochladen
|
||||
time:
|
||||
before: "%{time} zuvor"
|
||||
submissions:
|
||||
causes:
|
||||
autosave: Autosave
|
||||
|
@ -38,6 +38,7 @@ en:
|
||||
team_id: Team
|
||||
title: Title
|
||||
user: Author
|
||||
allow_file_creation: "Allow file creation"
|
||||
external_user:
|
||||
consumer: Consumer
|
||||
email: Email
|
||||
@ -81,6 +82,7 @@ en:
|
||||
execution_environment: Language
|
||||
username: Username
|
||||
requested_at: Request Date
|
||||
question: "Question"
|
||||
submission:
|
||||
cause: Cause
|
||||
code: Code
|
||||
@ -209,6 +211,7 @@ en:
|
||||
timeout: 'Execution stopped. 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.
|
||||
request_for_comments_sent: "Request for comments sent."
|
||||
editor_file_tree:
|
||||
file_root: Files
|
||||
file_form:
|
||||
@ -243,8 +246,10 @@ en:
|
||||
others: Other comments on this line
|
||||
addyours: Add your comment
|
||||
addComment: Comment this
|
||||
removeAllOnLine: Remove all comments on this line
|
||||
removeAllOnLine: Remove my comments on this line
|
||||
listing: Listing the newest comment requests
|
||||
request: "Request Comments"
|
||||
question: "Please shortly describe your problem or the program part you would like to get feedback for."
|
||||
index:
|
||||
clone: Duplicate
|
||||
implement: Implement
|
||||
@ -288,6 +293,8 @@ en:
|
||||
teacher_defined_test: Test for Assessment
|
||||
user_defined_file: User-defined File
|
||||
user_defined_test: User-defined Test
|
||||
error:
|
||||
filename: "The file could not be saved, because another file with the name '%{name}' already exists."
|
||||
hints:
|
||||
form:
|
||||
hints:
|
||||
@ -321,7 +328,9 @@ en:
|
||||
subject: Password reset instructions
|
||||
request_for_comments:
|
||||
index:
|
||||
all: All Requests for Comments
|
||||
get_my_comment_requests: My Requests for Comments
|
||||
no_question: "The author did not enter a question for this request."
|
||||
sessions:
|
||||
create:
|
||||
failure: Invalid email or password.
|
||||
@ -390,6 +399,8 @@ en:
|
||||
shortcut: 'Keyboard shortcut: %{shortcut}'
|
||||
update: 'Update %{model}'
|
||||
upload_file: Upload file
|
||||
time:
|
||||
before: "%{time} ago"
|
||||
submissions:
|
||||
causes:
|
||||
autosave: Autosave
|
||||
|
@ -0,0 +1,5 @@
|
||||
class AddQuestionToRequestForComments < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :request_for_comments, :question, :text
|
||||
end
|
||||
end
|
@ -0,0 +1,5 @@
|
||||
class AddAllowFileCreationToExercises < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :exercises, :allow_file_creation, :boolean
|
||||
end
|
||||
end
|
@ -11,7 +11,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20160302133540) do
|
||||
ActiveRecord::Schema.define(version: 20160510145341) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@ -89,6 +89,7 @@ ActiveRecord::Schema.define(version: 20160302133540) do
|
||||
t.string "token"
|
||||
t.integer "team_id"
|
||||
t.boolean "hide_file_tree"
|
||||
t.boolean "allow_file_creation"
|
||||
end
|
||||
|
||||
add_index "exercises", ["execution_environment_id"], name: "test3", using: :btree
|
||||
@ -190,6 +191,7 @@ ActiveRecord::Schema.define(version: 20160302133540) do
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "user_type"
|
||||
t.text "question"
|
||||
end
|
||||
|
||||
create_table "submissions", force: true do |t|
|
||||
|
@ -12,6 +12,8 @@ class Assessor
|
||||
score = 0.0;
|
||||
if(test_outcome[:passed].to_f != 0.0 && test_outcome[:count].to_f != 0.0)
|
||||
score = (test_outcome[:passed].to_f / test_outcome[:count].to_f)
|
||||
# prevent negative scores
|
||||
score = [0.0, score].max
|
||||
end
|
||||
score
|
||||
end
|
||||
|
@ -2,7 +2,7 @@ class JunitAdapter < TestingFrameworkAdapter
|
||||
COUNT_REGEXP = /Tests run: (\d+)/
|
||||
FAILURES_REGEXP = /Failures: (\d+)/
|
||||
SUCCESS_REGEXP = /OK \((\d+) test[s]?\)/
|
||||
ASSERTION_ERROR_REGEXP = /java\.lang\.AssertionError:\s(.*)/
|
||||
ASSERTION_ERROR_REGEXP = /java\.lang\.AssertionError:\s(.*)|org\.junit\.ComparisonFailure:\s(.*)/
|
||||
|
||||
def self.framework_name
|
||||
'JUnit'
|
||||
|
@ -11,7 +11,7 @@ class PyUnitAdapter < TestingFrameworkAdapter
|
||||
count = COUNT_REGEXP.match(output[:stderr]).captures.first.to_i
|
||||
matches = FAILURES_REGEXP.match(output[:stderr])
|
||||
failed = matches ? matches.captures.try(:first).to_i : 0
|
||||
error_matches = ASSERTION_ERROR_REGEXP.match(output[:stderr]).captures
|
||||
error_matches = ASSERTION_ERROR_REGEXP.match(output[:stderr]).try(:captures) || []
|
||||
{count: count, failed: failed, error_messages: error_matches}
|
||||
end
|
||||
end
|
||||
|
@ -34,8 +34,7 @@ class Xikolo::Client
|
||||
end
|
||||
|
||||
def self.url
|
||||
#todo: JanR: set an environment variable here, fallback value: http://open.hpi.de/api/
|
||||
'http://localhost:2000/api/'
|
||||
@url ||= CodeOcean::Config.new(:code_ocean).read.fetch(:xikolo_api_url, 'http://localhost:3000/api/') #caches this with ||=, second value of fetch is default value
|
||||
end
|
||||
|
||||
def self.accept
|
||||
@ -43,7 +42,7 @@ class Xikolo::Client
|
||||
end
|
||||
|
||||
def self.token
|
||||
'Token token="'+Rails.application.config.xikolo[:token]+'"'
|
||||
'Token token="'+Rails.application.secrets.openhpi_api_token+'"'
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -4,9 +4,14 @@ class Xikolo::UserClient
|
||||
|
||||
# return default values if user is not found or if there is a server issue:
|
||||
if user
|
||||
return {display_name: user['first_name'], user_visual: user['user_visual'], language: user['language']}
|
||||
if user['display_name'].present?
|
||||
name = user['display_name']
|
||||
else
|
||||
return {display_name: "Name" + user_id, user_visual: ActionController::Base.helpers.image_path('default.png'), language: "DE"}
|
||||
name = user['first_name']
|
||||
end
|
||||
return {display_name: name, user_visual: user['user_visual'], language: user['language']}
|
||||
else
|
||||
return {display_name: "User " + user_id, user_visual: ActionController::Base.helpers.image_path('default.png'), language: "DE"}
|
||||
end
|
||||
end
|
||||
end
|
@ -39,7 +39,9 @@ sudo service docker restart
|
||||
docker pull openhpi/docker_java
|
||||
docker pull openhpi/docker_ruby
|
||||
docker pull openhpi/docker_python
|
||||
|
||||
docker pull openhpi/co_execenv_python
|
||||
docker pull openhpi/co_execenv_java
|
||||
docker pull openhpi/co_execenv_java_antlr
|
||||
|
||||
# rvm
|
||||
apt-get install -y git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties libffi-dev
|
||||
@ -96,8 +98,9 @@ export RAILS_ENV=development
|
||||
rake db:schema:load
|
||||
rake db:migrate
|
||||
rake db:seed
|
||||
sudo mkdir -p /shared
|
||||
chown -R vagrant /shared
|
||||
ln -sf /shared tmp/files
|
||||
ln -sf /shared tmp/files #make sure you are running vagrant with admin privileges
|
||||
|
||||
# NGINX
|
||||
if [ ! -L /etc/nginx/sites-enabled/code_ocean ]
|
||||
|
Reference in New Issue
Block a user