Merge branch 'master' into score-websocket
Conflicts: app/assets/javascripts/editor.js.erb
This commit is contained in:
@ -19,6 +19,10 @@ $(function() {
|
||||
var SERVER_SEND_EVENT = 2;
|
||||
|
||||
var editors = [];
|
||||
var editor_for_file = new Map();
|
||||
var regex_for_language = new Map();
|
||||
var tracepositions_regex;
|
||||
|
||||
var active_file = undefined;
|
||||
var active_frame = undefined;
|
||||
var running = false;
|
||||
@ -176,7 +180,10 @@ $(function() {
|
||||
var downloadCode = function(event) {
|
||||
event.preventDefault();
|
||||
createSubmission(this, null,function(response) {
|
||||
var url = response.download_url.replace(FILENAME_URL_PLACEHOLDER, active_file.filename);
|
||||
var url = response.download_url;
|
||||
|
||||
// to download just a single file, use the following url
|
||||
//var url = response.download_file_url.replace(FILENAME_URL_PLACEHOLDER, active_file.filename);
|
||||
window.location = url;
|
||||
});
|
||||
};
|
||||
@ -415,12 +422,18 @@ $(function() {
|
||||
editor.setTheme(THEME);
|
||||
editor.commands.bindKey("ctrl+alt+0", null);
|
||||
editors.push(editor);
|
||||
editor_for_file.set($(element).parent().data('filename'), editor);
|
||||
var session = editor.getSession();
|
||||
session.setMode($(element).data('mode'));
|
||||
session.setTabSize($(element).data('indent-size'));
|
||||
session.setUseSoftTabs(true);
|
||||
session.setUseWrapMode(true);
|
||||
|
||||
// set regex for parsing error traces based on the mode of the main file.
|
||||
if( $(element).parent().data('role') == "main_file"){
|
||||
tracepositions_regex = regex_for_language.get($(element).data('mode'));
|
||||
}
|
||||
|
||||
var file_id = $(element).data('id');
|
||||
|
||||
/*
|
||||
@ -468,6 +481,12 @@ $(function() {
|
||||
$('#request-for-comments').on('click', requestComments);
|
||||
};
|
||||
|
||||
|
||||
var initializeRegexes = function(){
|
||||
regex_for_language.set("ace/mode/python", /File "(.+?)", line (\d+)/g);
|
||||
regex_for_language.set("ace/mode/java", /(.*\.java):(\d+):/g);
|
||||
}
|
||||
|
||||
var initializeTooltips = function() {
|
||||
$('[data-tooltip]').tooltip();
|
||||
};
|
||||
@ -602,8 +621,6 @@ $(function() {
|
||||
element.addClass('text-success').append(output.stdout);
|
||||
flowrOutputBuffer += output.stdout;
|
||||
QaApiOutputBuffer.stdout += output.stdout;
|
||||
|
||||
|
||||
//}else{
|
||||
// element.addClass('text-success');
|
||||
// element.data('content_buffer' , element.data('content_buffer') + output.stdout);
|
||||
@ -892,7 +909,9 @@ $(function() {
|
||||
}
|
||||
|
||||
var showWorkspaceTab = function(event) {
|
||||
event.preventDefault();
|
||||
if(event){
|
||||
event.preventDefault();
|
||||
}
|
||||
showTab(0);
|
||||
};
|
||||
|
||||
@ -1053,6 +1072,7 @@ $(function() {
|
||||
killWebsocketAndContainer();
|
||||
handleQaApiOutput();
|
||||
handleStderrOutputForFlowr();
|
||||
augmentStacktraceInOutput();
|
||||
break;
|
||||
case 'timeout':
|
||||
// just show the timeout message here. Another exit command is sent by the rails backend when the socket to the docker container closes.
|
||||
@ -1064,6 +1084,41 @@ $(function() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var jumpToSourceLine = function(event){
|
||||
var file = $(event.target).data('file');
|
||||
var line = $(event.target).data('line');
|
||||
|
||||
showWorkspaceTab(null);
|
||||
// set active file ?!?!
|
||||
|
||||
var frame = $('div.frame[data-filename="' + file + '"]');
|
||||
showFrame(frame);
|
||||
|
||||
var editor = editor_for_file.get(file);
|
||||
editor.gotoLine(line, 0);
|
||||
|
||||
};
|
||||
|
||||
var augmentStacktraceInOutput = function() {
|
||||
if(tracepositions_regex){
|
||||
var element = $('#output>pre');
|
||||
var text = element.text();
|
||||
element.on( "click", "a", jumpToSourceLine);
|
||||
|
||||
var matches;
|
||||
|
||||
while(matches = tracepositions_regex.exec(text)){
|
||||
var frame = $('div.frame[data-filename="' + matches[1] + '"]')
|
||||
|
||||
if(frame.length > 0){
|
||||
element.html(text.replace(matches[0], "<a href='#' data-file='" + matches[1] + "' data-line='" + matches[2] + "'>" + matches[0] + "</a>"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var renderWebsocketOutput = function(msg){
|
||||
var element = findOrCreateRenderElement(0);
|
||||
element.append(msg.data);
|
||||
@ -1177,25 +1232,25 @@ $(function() {
|
||||
var file_id = $('.editor').data('id')
|
||||
var question = $('#question').val();
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: '/request_for_comments',
|
||||
data: {
|
||||
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,
|
||||
"requested_at(4i)":17,
|
||||
"requested_at(5i)":06
|
||||
var createRequestForComments = function(submission) {
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: '/request_for_comments',
|
||||
data: {
|
||||
request_for_comment: {
|
||||
exercise_id: exercise_id,
|
||||
file_id: file_id,
|
||||
submission_id: submission.id,
|
||||
question: question
|
||||
}
|
||||
}
|
||||
}
|
||||
}).done(function() {
|
||||
hideSpinner();
|
||||
$.flash.success({ text: $('#askForCommentsButton').data('message-success') })
|
||||
}).error(ajaxError);
|
||||
}).done(function() {
|
||||
hideSpinner();
|
||||
$.flash.success({ text: $('#askForCommentsButton').data('message-success') });
|
||||
}).error(ajaxError);
|
||||
}
|
||||
|
||||
createSubmission($('.requestCommentsButton'), null, createRequestForComments);
|
||||
|
||||
$('#comment-modal').modal('hide');
|
||||
var button = $('.requestCommentsButton');
|
||||
@ -1224,6 +1279,7 @@ $(function() {
|
||||
|
||||
if ($('#editor').isPresent()) {
|
||||
if (isBrowserSupported()) {
|
||||
initializeRegexes();
|
||||
initializeCodePilot();
|
||||
$('.score, #development-environment').show();
|
||||
configureEditors();
|
||||
|
@ -12,6 +12,9 @@ $(function() {
|
||||
$('#files li:last select[name*="file_type_id"]').val(getSelectedExecutionEnvironment().file_type_id);
|
||||
$('#files li:last select').chosen(window.CodeOcean.CHOSEN_OPTIONS);
|
||||
$('body, html').scrollTo('#add-file');
|
||||
// if we collapse the file forms by default, we need to click on the new element in order to open it.
|
||||
// however, this crashes for more files (if we add several ones by clicking the add button more often), since the elements are probably not correctly added to the files list.
|
||||
//$('#files li:last>div:first>a>div').click();
|
||||
};
|
||||
|
||||
var ajaxError = function() {
|
||||
@ -148,6 +151,22 @@ $(function() {
|
||||
});
|
||||
};
|
||||
|
||||
var updateFileTemplates = function(fileType) {
|
||||
var jqxhr = $.ajax({
|
||||
url: '/file_templates/by_file_type/' + fileType + '.json',
|
||||
dataType: 'json'
|
||||
});
|
||||
jqxhr.done(function(response) {
|
||||
var noTemplateLabel = $('#noTemplateLabel').data('text');
|
||||
var options = "<option value>" + noTemplateLabel + "</option>";
|
||||
for (var i = 0; i < response.length; i++) {
|
||||
options += "<option value='" + response[i].id + "'>" + response[i].name + "</option>"
|
||||
}
|
||||
$("#code_ocean_file_file_template_id").find('option').remove().end().append($(options));
|
||||
});
|
||||
jqxhr.fail(ajaxError);
|
||||
}
|
||||
|
||||
if ($.isController('exercises')) {
|
||||
if ($('table').isPresent()) {
|
||||
enableBatchUpdate();
|
||||
@ -162,6 +181,10 @@ $(function() {
|
||||
inferFileAttributes();
|
||||
observeFileRoleChanges();
|
||||
overrideTextareaTabBehavior();
|
||||
} else if ($('#files.jstree').isPresent()) {
|
||||
var fileTypeSelect = $('#code_ocean_file_file_type_id');
|
||||
fileTypeSelect.on("change", function() {updateFileTemplates(fileTypeSelect.val())});
|
||||
updateFileTemplates(fileTypeSelect.val());
|
||||
}
|
||||
toggleCodeHeight();
|
||||
if (window.hljs) {
|
||||
|
3
app/assets/javascripts/file_templates.js.coffee
Normal file
3
app/assets/javascripts/file_templates.js.coffee
Normal file
@ -0,0 +1,3 @@
|
||||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://coffeescript.org/
|
@ -49,7 +49,10 @@ div#chart_2 {
|
||||
}
|
||||
|
||||
|
||||
|
||||
a.file-heading {
|
||||
color: black !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.bar {
|
||||
fill: orange;
|
||||
|
3
app/assets/stylesheets/file_templates.css.scss
Normal file
3
app/assets/stylesheets/file_templates.css.scss
Normal file
@ -0,0 +1,3 @@
|
||||
// Place all the styles related to the FileTemplates controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
@ -10,6 +10,11 @@ module CodeOcean
|
||||
|
||||
def create
|
||||
@file = CodeOcean::File.new(file_params)
|
||||
if @file.file_template_id
|
||||
content = FileTemplate.find(@file.file_template_id).content
|
||||
content.sub! '{{file_name}}', @file.name
|
||||
@file.content = content
|
||||
end
|
||||
authorize!
|
||||
create_and_respond(object: @file, path: proc { implement_exercise_path(@file.context.exercise, tab: 2) })
|
||||
end
|
||||
|
@ -46,7 +46,11 @@ class CommentsController < ApplicationController
|
||||
# POST /comments
|
||||
# POST /comments.json
|
||||
def create
|
||||
@comment = Comment.new(comment_params)
|
||||
@comment = Comment.new(comment_params_without_request_id)
|
||||
|
||||
if comment_params[:request_id]
|
||||
UserMailer.got_new_comment(@comment, RequestForComment.find(comment_params[:request_id]), current_user)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
if @comment.save
|
||||
@ -64,7 +68,7 @@ class CommentsController < ApplicationController
|
||||
# PATCH/PUT /comments/1.json
|
||||
def update
|
||||
respond_to do |format|
|
||||
if @comment.update(comment_params)
|
||||
if @comment.update(comment_params_without_request_id)
|
||||
format.html { head :no_content, notice: 'Comment was successfully updated.' }
|
||||
format.json { render :show, status: :ok, location: @comment }
|
||||
else
|
||||
@ -101,10 +105,14 @@ class CommentsController < ApplicationController
|
||||
@comment = Comment.find(params[:id])
|
||||
end
|
||||
|
||||
def comment_params_without_request_id
|
||||
comment_params.except :request_id
|
||||
end
|
||||
|
||||
# Never trust parameters from the scary internet, only allow the white list through.
|
||||
def comment_params
|
||||
#params.require(:comment).permit(:user_id, :file_id, :row, :column, :text)
|
||||
# fuer production mode, damit böse menschen keine falsche user_id uebergeben:
|
||||
params.require(:comment).permit(:file_id, :row, :column, :text).merge(user_id: current_user.id, user_type: current_user.class.name)
|
||||
params.require(:comment).permit(:file_id, :row, :column, :text, :request_id).merge(user_id: current_user.id, user_type: current_user.class.name)
|
||||
end
|
||||
end
|
||||
|
@ -1,6 +1,6 @@
|
||||
module FileParameters
|
||||
def file_attributes
|
||||
%w(content context_id feedback_message file_id file_type_id hidden id name native_file path read_only role weight)
|
||||
%w(content context_id feedback_message file_id file_type_id hidden id name native_file path read_only role weight file_template_id)
|
||||
end
|
||||
private :file_attributes
|
||||
end
|
||||
|
@ -9,7 +9,6 @@ class ExercisesController < ApplicationController
|
||||
before_action :set_exercise, only: MEMBER_ACTIONS + [:clone, :implement, :run, :statistics, :submit, :reload]
|
||||
before_action :set_external_user, only: [:statistics]
|
||||
before_action :set_file_types, only: [:create, :edit, :new, :update]
|
||||
before_action :set_teams, only: [:create, :edit, :new, :update]
|
||||
|
||||
skip_before_filter :verify_authenticity_token, only: [:import_proforma_xml]
|
||||
skip_after_action :verify_authorized, only: [:import_proforma_xml]
|
||||
@ -119,7 +118,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, :allow_file_creation, :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, :title, files_attributes: file_attributes).merge(user_id: current_user.id, user_type: current_user.class.name)
|
||||
end
|
||||
private :exercise_params
|
||||
|
||||
@ -195,11 +194,6 @@ class ExercisesController < ApplicationController
|
||||
end
|
||||
private :set_file_types
|
||||
|
||||
def set_teams
|
||||
@teams = Team.all.order(:name)
|
||||
end
|
||||
private :set_teams
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
|
94
app/controllers/file_templates_controller.rb
Normal file
94
app/controllers/file_templates_controller.rb
Normal file
@ -0,0 +1,94 @@
|
||||
class FileTemplatesController < ApplicationController
|
||||
before_action :set_file_template, only: [:show, :edit, :update, :destroy]
|
||||
|
||||
def authorize!
|
||||
authorize(@file_template || @file_templates)
|
||||
end
|
||||
private :authorize!
|
||||
|
||||
def by_file_type
|
||||
@file_templates = FileTemplate.where(:file_type_id => params[:file_type_id])
|
||||
authorize!
|
||||
respond_to do |format|
|
||||
format.json { render :show, status: :ok, json: @file_templates.to_json }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /file_templates
|
||||
# GET /file_templates.json
|
||||
def index
|
||||
@file_templates = FileTemplate.all.order(:file_type_id).paginate(page: params[:page])
|
||||
authorize!
|
||||
end
|
||||
|
||||
# GET /file_templates/1
|
||||
# GET /file_templates/1.json
|
||||
def show
|
||||
authorize!
|
||||
end
|
||||
|
||||
# GET /file_templates/new
|
||||
def new
|
||||
@file_template = FileTemplate.new
|
||||
authorize!
|
||||
end
|
||||
|
||||
# GET /file_templates/1/edit
|
||||
def edit
|
||||
authorize!
|
||||
end
|
||||
|
||||
# POST /file_templates
|
||||
# POST /file_templates.json
|
||||
def create
|
||||
@file_template = FileTemplate.new(file_template_params)
|
||||
authorize!
|
||||
|
||||
respond_to do |format|
|
||||
if @file_template.save
|
||||
format.html { redirect_to @file_template, notice: 'File template was successfully created.' }
|
||||
format.json { render :show, status: :created, location: @file_template }
|
||||
else
|
||||
format.html { render :new }
|
||||
format.json { render json: @file_template.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PATCH/PUT /file_templates/1
|
||||
# PATCH/PUT /file_templates/1.json
|
||||
def update
|
||||
authorize!
|
||||
respond_to do |format|
|
||||
if @file_template.update(file_template_params)
|
||||
format.html { redirect_to @file_template, notice: 'File template was successfully updated.' }
|
||||
format.json { render :show, status: :ok, location: @file_template }
|
||||
else
|
||||
format.html { render :edit }
|
||||
format.json { render json: @file_template.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /file_templates/1
|
||||
# DELETE /file_templates/1.json
|
||||
def destroy
|
||||
authorize!
|
||||
@file_template.destroy
|
||||
respond_to do |format|
|
||||
format.html { redirect_to file_templates_url, notice: 'File template was successfully destroyed.' }
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Use callbacks to share common setup or constraints between actions.
|
||||
def set_file_template
|
||||
@file_template = FileTemplate.find(params[:id])
|
||||
end
|
||||
|
||||
# Never trust parameters from the scary internet, only allow the white list through.
|
||||
def file_template_params
|
||||
params[:file_template].permit(:name, :file_type_id, :content)
|
||||
end
|
||||
end
|
@ -1,5 +1,5 @@
|
||||
class RequestForCommentsController < ApplicationController
|
||||
before_action :set_request_for_comment, only: [:show, :edit, :update, :destroy]
|
||||
before_action :set_request_for_comment, only: [:show, :edit, :update, :destroy, :mark_as_solved]
|
||||
|
||||
skip_after_action :verify_authorized
|
||||
|
||||
@ -20,6 +20,18 @@ class RequestForCommentsController < ApplicationController
|
||||
render 'index'
|
||||
end
|
||||
|
||||
def mark_as_solved
|
||||
authorize!
|
||||
@request_for_comment.solved = true
|
||||
respond_to do |format|
|
||||
if @request_for_comment.save
|
||||
format.json { render :show, status: :ok, location: @request_for_comment }
|
||||
else
|
||||
format.json { render json: @request_for_comment.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# GET /request_for_comments/1
|
||||
# GET /request_for_comments/1.json
|
||||
def show
|
||||
@ -70,6 +82,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, :question, :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, :solved, :submission_id).merge(user_id: current_user.id, user_type: current_user.class.name)
|
||||
end
|
||||
end
|
||||
|
@ -6,9 +6,9 @@ class SubmissionsController < ApplicationController
|
||||
include SubmissionScoring
|
||||
include Tubesock::Hijack
|
||||
|
||||
before_action :set_submission, only: [:download_file, :render_file, :run, :score, :show, :statistics, :stop, :test]
|
||||
before_action :set_submission, only: [:download, :download_file, :render_file, :run, :score, :show, :statistics, :stop, :test]
|
||||
before_action :set_docker_client, only: [:run, :test]
|
||||
before_action :set_files, only: [:download_file, :render_file, :show]
|
||||
before_action :set_files, only: [:download, :download_file, :render_file, :show]
|
||||
before_action :set_file, only: [:download_file, :render_file]
|
||||
before_action :set_mime_type, only: [:download_file, :render_file]
|
||||
skip_before_action :verify_authenticity_token, only: [:download_file, :render_file]
|
||||
@ -53,6 +53,20 @@ class SubmissionsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def download
|
||||
# files = @submission.files.map{ }
|
||||
# zipline( files, 'submission.zip')
|
||||
# send_data(@file.content, filename: @file.name_with_extension)
|
||||
require 'zip'
|
||||
stringio = Zip::OutputStream.write_buffer do |zio|
|
||||
@files.each do |file|
|
||||
zio.put_next_entry(file.name_with_extension)
|
||||
zio.write(file.content)
|
||||
end
|
||||
end
|
||||
send_data(stringio.string, filename: @submission.exercise.title.tr(" ", "_") + ".zip")
|
||||
end
|
||||
|
||||
def download_file
|
||||
if @file.native_file?
|
||||
send_file(@file.native_file.path)
|
||||
|
@ -1,51 +0,0 @@
|
||||
class TeamsController < ApplicationController
|
||||
include CommonBehavior
|
||||
|
||||
before_action :set_team, only: MEMBER_ACTIONS
|
||||
|
||||
def authorize!
|
||||
authorize(@team || @teams)
|
||||
end
|
||||
private :authorize!
|
||||
|
||||
def create
|
||||
@team = Team.new(team_params)
|
||||
authorize!
|
||||
create_and_respond(object: @team)
|
||||
end
|
||||
|
||||
def destroy
|
||||
destroy_and_respond(object: @team)
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def index
|
||||
@teams = Team.all.includes(:internal_users).order(:name).paginate(page: params[:page])
|
||||
authorize!
|
||||
end
|
||||
|
||||
def new
|
||||
@team = Team.new
|
||||
authorize!
|
||||
end
|
||||
|
||||
def set_team
|
||||
@team = Team.find(params[:id])
|
||||
authorize!
|
||||
end
|
||||
private :set_team
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def team_params
|
||||
params[:team].permit(:name, internal_user_ids: [])
|
||||
end
|
||||
private :team_params
|
||||
|
||||
def update
|
||||
update_and_respond(object: @team, params: team_params)
|
||||
end
|
||||
end
|
@ -11,4 +11,13 @@ class UserMailer < ActionMailer::Base
|
||||
@reset_password_url = reset_password_internal_user_url(user, token: user.reset_password_token)
|
||||
mail(subject: t('mailers.user_mailer.reset_password.subject'), to: user.email)
|
||||
end
|
||||
|
||||
def got_new_comment(comment, request_for_comment, commenting_user)
|
||||
# todo: check whether we can take the last known locale of the receiver?
|
||||
@receiver_displayname = request_for_comment.user.displayname
|
||||
@commenting_user_displayname = commenting_user.displayname
|
||||
@comment_text = comment.text
|
||||
@rfc_link = request_for_comment_url(request_for_comment)
|
||||
mail(subject: t('mailers.user_mailer.got_new_comment.subject', commenting_user_displayname: @commenting_user_displayname), to: request_for_comment.user.email).deliver
|
||||
end
|
||||
end
|
||||
|
@ -35,6 +35,7 @@ module CodeOcean
|
||||
|
||||
has_many :files
|
||||
has_many :testruns
|
||||
has_many :comments
|
||||
alias_method :descendants, :files
|
||||
|
||||
mount_uploader :native_file, FileUploader
|
||||
|
@ -11,7 +11,6 @@ class Exercise < ActiveRecord::Base
|
||||
|
||||
belongs_to :execution_environment
|
||||
has_many :submissions
|
||||
belongs_to :team
|
||||
|
||||
has_many :external_users, source: :user, source_type: ExternalUser, through: :submissions
|
||||
has_many :internal_users, source: :user, source_type: InternalUser, through: :submissions
|
||||
|
@ -7,7 +7,9 @@ class ExternalUser < ActiveRecord::Base
|
||||
def displayname
|
||||
result = name
|
||||
if(consumer.name == 'openHPI')
|
||||
result = Xikolo::UserClient.get(external_id.to_s)[:display_name]
|
||||
result = Rails.cache.fetch("#{cache_key}/displayname", expires_in: 12.hours) do
|
||||
Xikolo::UserClient.get(external_id.to_s)[:display_name]
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
10
app/models/file_template.rb
Normal file
10
app/models/file_template.rb
Normal file
@ -0,0 +1,10 @@
|
||||
class FileTemplate < ActiveRecord::Base
|
||||
|
||||
belongs_to :file_type
|
||||
|
||||
|
||||
def to_s
|
||||
name
|
||||
end
|
||||
|
||||
end
|
@ -12,6 +12,7 @@ class FileType < ActiveRecord::Base
|
||||
|
||||
has_many :execution_environments
|
||||
has_many :files
|
||||
has_many :file_templates
|
||||
|
||||
validates :binary, boolean_presence: true
|
||||
validates :editor_mode, presence: true, unless: :binary?
|
||||
|
@ -3,8 +3,6 @@ class InternalUser < ActiveRecord::Base
|
||||
|
||||
authenticates_with_sorcery!
|
||||
|
||||
has_and_belongs_to_many :teams
|
||||
|
||||
validates :email, presence: true, uniqueness: true
|
||||
validates :password, confirmation: true, if: :password_void?, on: :update, presence: true
|
||||
validates :role, inclusion: {in: ROLES}
|
||||
|
@ -1,7 +1,8 @@
|
||||
class RequestForComment < ActiveRecord::Base
|
||||
include Creation
|
||||
belongs_to :submission
|
||||
belongs_to :exercise
|
||||
belongs_to :file, class_name: 'CodeOcean::File'
|
||||
belongs_to :user, polymorphic: true
|
||||
|
||||
before_create :set_requested_timestamp
|
||||
|
||||
@ -13,10 +14,8 @@ class RequestForComment < ActiveRecord::Base
|
||||
self.requested_at = Time.now
|
||||
end
|
||||
|
||||
def submission
|
||||
Submission.find(file.context_id)
|
||||
end
|
||||
|
||||
# not used right now, finds the last submission for the respective user and exercise.
|
||||
# might be helpful to check whether the exercise has been solved in the meantime.
|
||||
def last_submission
|
||||
Submission.find_by_sql(" select * from submissions
|
||||
where exercise_id = #{exercise_id} AND
|
||||
@ -25,12 +24,27 @@ class RequestForComment < ActiveRecord::Base
|
||||
limit 1").first
|
||||
end
|
||||
|
||||
# not used any longer, since we directly saved the submission_id now.
|
||||
# Was used before that to determine the submission belonging to the request_for_comment.
|
||||
def last_submission_before_creation
|
||||
Submission.find_by_sql(" select * from submissions
|
||||
where exercise_id = #{exercise_id} AND
|
||||
user_id = #{user_id} AND
|
||||
'#{created_at.localtime}' > created_at
|
||||
order by created_at desc
|
||||
limit 1").first
|
||||
end
|
||||
|
||||
def comments_count
|
||||
submission.files.map { |file| file.comments.size}.sum
|
||||
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, 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
|
||||
select("id, user_id, exercise_id, file_id, question, created_at, updated_at, user_type, solved, submission_id, row_number() OVER (PARTITION BY user_id ORDER BY created_at DESC) as row_number").to_sql
|
||||
end
|
||||
end
|
||||
|
@ -2,7 +2,7 @@ class Submission < ActiveRecord::Base
|
||||
include Context
|
||||
include Creation
|
||||
|
||||
CAUSES = %w(assess download file render run save submit test autosave)
|
||||
CAUSES = %w(assess download file render run save submit test autosave requestComments)
|
||||
FILENAME_URL_PLACEHOLDER = '{filename}'
|
||||
|
||||
belongs_to :exercise
|
||||
@ -28,13 +28,17 @@ class Submission < ActiveRecord::Base
|
||||
ancestors.merge(descendants).values
|
||||
end
|
||||
|
||||
[:download, :render, :run, :test].each do |action|
|
||||
[:download_file, :render, :run, :test].each do |action|
|
||||
filename = FILENAME_URL_PLACEHOLDER.gsub(/\W/, '')
|
||||
define_method("#{action}_url") do
|
||||
Rails.application.routes.url_helpers.send(:"#{action}_submission_path", self, filename).sub(filename, FILENAME_URL_PLACEHOLDER)
|
||||
end
|
||||
end
|
||||
|
||||
def download_url
|
||||
Rails.application.routes.url_helpers.send(:download_submission_path, self)
|
||||
end
|
||||
|
||||
def main_file
|
||||
collect_files.detect(&:main_file?)
|
||||
end
|
||||
|
@ -1,10 +0,0 @@
|
||||
class Team < ActiveRecord::Base
|
||||
has_and_belongs_to_many :internal_users
|
||||
alias_method :members, :internal_users
|
||||
|
||||
validates :name, presence: true
|
||||
|
||||
def to_s
|
||||
name
|
||||
end
|
||||
end
|
@ -13,24 +13,19 @@ class ExercisePolicy < AdminOrAuthorPolicy
|
||||
end
|
||||
|
||||
[:clone?, :destroy?, :edit?, :statistics?, :update?].each do |action|
|
||||
define_method(action) { admin? || author? || team_member? }
|
||||
define_method(action) { admin? || author?}
|
||||
end
|
||||
|
||||
[:implement?, :submit?, :reload?].each do |action|
|
||||
define_method(action) { everyone }
|
||||
end
|
||||
|
||||
def team_member?
|
||||
@record.team.try(:members, []).include?(@user) if @record.team
|
||||
end
|
||||
private :team_member?
|
||||
|
||||
class Scope < Scope
|
||||
def resolve
|
||||
if @user.admin?
|
||||
@scope.all
|
||||
elsif @user.internal_user?
|
||||
@scope.where('user_id = ? OR public = TRUE OR (team_id IS NOT NULL AND team_id IN (SELECT t.id FROM teams t JOIN internal_users_teams iut ON t.id = iut.team_id WHERE iut.internal_user_id = ?))', @user.id, @user.id)
|
||||
@scope.where('user_id = ? OR public = TRUE', @user.id)
|
||||
else
|
||||
@scope.none
|
||||
end
|
||||
|
11
app/policies/file_template_policy.rb
Normal file
11
app/policies/file_template_policy.rb
Normal file
@ -0,0 +1,11 @@
|
||||
class FileTemplatePolicy < AdminOnlyPolicy
|
||||
|
||||
def show?
|
||||
everyone
|
||||
end
|
||||
|
||||
def by_file_type?
|
||||
everyone
|
||||
end
|
||||
|
||||
end
|
@ -1,5 +1,8 @@
|
||||
class RequestForCommentPolicy < ApplicationPolicy
|
||||
|
||||
def author?
|
||||
@user == @record.author
|
||||
end
|
||||
private :author?
|
||||
|
||||
def create?
|
||||
everyone
|
||||
@ -13,6 +16,10 @@ class RequestForCommentPolicy < ApplicationPolicy
|
||||
define_method(action) { admin? }
|
||||
end
|
||||
|
||||
def mark_as_solved?
|
||||
admin? || author?
|
||||
end
|
||||
|
||||
def edit?
|
||||
admin?
|
||||
end
|
||||
|
@ -8,7 +8,7 @@ class SubmissionPolicy < ApplicationPolicy
|
||||
everyone
|
||||
end
|
||||
|
||||
[:download_file?, :render_file?, :run?, :score?, :show?, :statistics?, :stop?, :test?].each do |action|
|
||||
[:download?, :download_file?, :render_file?, :run?, :score?, :show?, :statistics?, :stop?, :test?].each do |action|
|
||||
define_method(action) { admin? || author? }
|
||||
end
|
||||
|
||||
|
@ -1,14 +0,0 @@
|
||||
class TeamPolicy < ApplicationPolicy
|
||||
[:create?, :index?, :new?].each do |action|
|
||||
define_method(action) { admin? }
|
||||
end
|
||||
|
||||
[:destroy?, :edit?, :show?, :update?].each do |action|
|
||||
define_method(action) { admin? || member? }
|
||||
end
|
||||
|
||||
def member?
|
||||
@record.members.include?(@user)
|
||||
end
|
||||
private :member?
|
||||
end
|
@ -8,7 +8,7 @@
|
||||
- if current_user.admin?
|
||||
li = link_to(t('breadcrumbs.dashboard.show'), admin_dashboard_path)
|
||||
li.divider
|
||||
- models = [ExecutionEnvironment, Exercise, Consumer, CodeHarborLink, ExternalUser, FileType, InternalUser, Submission, Team].sort_by { |model| model.model_name.human(count: 2) }
|
||||
- models = [ExecutionEnvironment, Exercise, Consumer, CodeHarborLink, ExternalUser, FileType, FileTemplate, InternalUser, Submission].sort_by { |model| model.model_name.human(count: 2) }
|
||||
- models.each do |model|
|
||||
- if policy(model).index?
|
||||
li = link_to(model.model_name.human(count: 2), send(:"#{model.model_name.collection}_path"))
|
||||
|
@ -8,5 +8,9 @@
|
||||
.form-group
|
||||
= f.label(:file_type_id, t('activerecord.attributes.file.file_type_id'))
|
||||
= f.collection_select(:file_type_id, FileType.where(binary: false).order(:name), :id, :name, {selected: @exercise.execution_environment.file_type.try(:id)}, class: 'form-control')
|
||||
.form-group
|
||||
= f.label(:file_template_id, t('activerecord.attributes.file.file_template_id'))
|
||||
= f.collection_select(:file_template_id, FileTemplate.all.order(:name), :id, :name, {:include_blank => true}, class: 'form-control')
|
||||
= f.hidden_field(:context_id)
|
||||
.hidden#noTemplateLabel data-text=t('file_template.no_template_label')
|
||||
.actions = render('shared/submit_button', f: f, object: CodeOcean::File.new)
|
||||
|
@ -14,6 +14,6 @@
|
||||
.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
|
||||
button.btn.btn-primary.requestCommentsButton type='button' id="requestComments"
|
||||
i.fa.fa-comment
|
||||
= t('exercises.editor.requestComments')
|
@ -1,10 +1,10 @@
|
||||
- id = f.object.id
|
||||
li.panel.panel-default
|
||||
.panel-heading role="tab" id="heading"
|
||||
div.clearfix role="button"
|
||||
span = f.object.name
|
||||
a.pull-right data-toggle="collapse" data-parent="#files" href="#collapse#{id}" collapse
|
||||
.panel-collapse.collapse.in id="collapse#{id}" role="tabpanel"
|
||||
a.file-heading data-toggle="collapse" data-parent="#files" href="#collapse#{id}"
|
||||
div.clearfix role="button"
|
||||
span = f.object.name
|
||||
.panel-collapse.collapse-in id="collapse#{id}" role="tabpanel"
|
||||
.panel-body
|
||||
.clearfix = link_to(t('shared.destroy'), '#', class:'btn btn-warning btn-sm discard-file pull-right')
|
||||
.form-group
|
||||
|
@ -17,9 +17,6 @@
|
||||
= f.label(:instructions)
|
||||
= f.hidden_field(:instructions)
|
||||
.form-control.markdown
|
||||
/.form-group
|
||||
= f.label(:team_id)
|
||||
= f.collection_select(:team_id, @teams, :id, :name, {include_blank: true}, class: 'form-control')
|
||||
.checkbox
|
||||
label
|
||||
= f.check_box(:public)
|
||||
|
@ -50,9 +50,9 @@ h1 = "#{@exercise} (external user #{@external_user})"
|
||||
td
|
||||
-submission.testruns.each do |run|
|
||||
- if run.passed
|
||||
.unit-test-result.positive-result
|
||||
.unit-test-result.positive-result title=run.output
|
||||
- else
|
||||
.unit-test-result.negative-result
|
||||
.unit-test-result.negative-result title=run.output
|
||||
td = Time.at(deltas[1..index].inject(:+)).utc.strftime("%H:%M:%S") if index > 0
|
||||
-working_times_until.push((Time.at(deltas[1..index].inject(:+)).utc.strftime("%H:%M:%S") if index > 0))
|
||||
p = t('.addendum')
|
||||
|
@ -12,7 +12,6 @@ h1
|
||||
= row(label: 'exercise.description', value: render_markdown(@exercise.description))
|
||||
= row(label: 'exercise.execution_environment', value: link_to_if(policy(@exercise.execution_environment).show?, @exercise.execution_environment, @exercise.execution_environment))
|
||||
/= row(label: 'exercise.instructions', value: render_markdown(@exercise.instructions))
|
||||
= row(label: 'exercise.team', value: @exercise.team ? link_to(@exercise.team, @exercise.team) : nil)
|
||||
= 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?)
|
||||
@ -23,13 +22,15 @@ h1
|
||||
h2 = t('activerecord.attributes.exercise.files')
|
||||
|
||||
ul.list-unstyled.panel-group#files
|
||||
- @exercise.files.each do |file|
|
||||
- @exercise.files.order('name').each do |file|
|
||||
li.panel.panel-default
|
||||
.panel-heading role="tab" id="heading"
|
||||
div.clearfix role="button"
|
||||
span.panel-title = file.name_with_extension
|
||||
a.pull-right data-toggle="collapse" data-parent="#files" href="#collapse#{file.id}" collapse
|
||||
.panel-collapse.collapse.in id="collapse#{file.id}" role="tabpanel"
|
||||
a.file-heading data-toggle="collapse" data-parent="#files" href=".collapse#{file.id}"
|
||||
div.clearfix role="button"
|
||||
span = file.name_with_extension
|
||||
// probably set an icon here that shows that the rows can be collapsed
|
||||
//span.pull-right.collapse.in class="collapse#{file.id}" ☼
|
||||
.panel-collapse.collapse class="collapse#{file.id}" role="tabpanel"
|
||||
.panel-body
|
||||
- if policy(file).destroy?
|
||||
.clearfix = link_to(t('shared.destroy'), file, class:'btn btn-warning btn-sm pull-right', data: {confirm: t('shared.confirm_destroy')}, method: :delete)
|
||||
|
12
app/views/file_templates/_form.html.slim
Normal file
12
app/views/file_templates/_form.html.slim
Normal file
@ -0,0 +1,12 @@
|
||||
= form_for(@file_template) do |f|
|
||||
= render('shared/form_errors', object: @file_template)
|
||||
.form-group
|
||||
= f.label(:name)
|
||||
= f.text_field(:name, class: 'form-control', required: true)
|
||||
.form-group
|
||||
= f.label(:file_type_id)
|
||||
= f.collection_select(:file_type_id, FileType.all.order(:name), :id, :name, {}, class: 'form-control')
|
||||
.form-group
|
||||
= f.label(:content)
|
||||
= f.text_area(:content, class: 'form-control')
|
||||
.actions = render('shared/submit_button', f: f, object: @file_template)
|
3
app/views/file_templates/edit.html.slim
Normal file
3
app/views/file_templates/edit.html.slim
Normal file
@ -0,0 +1,3 @@
|
||||
h1 = @file_template
|
||||
|
||||
= render('form')
|
20
app/views/file_templates/index.html.slim
Normal file
20
app/views/file_templates/index.html.slim
Normal file
@ -0,0 +1,20 @@
|
||||
h1 = FileTemplate.model_name.human(count: 2)
|
||||
|
||||
.table-responsive
|
||||
table.table
|
||||
thead
|
||||
tr
|
||||
th = t('activerecord.attributes.file_template.name')
|
||||
th = t('activerecord.attributes.file_template.file_type')
|
||||
th colspan=3 = t('shared.actions')
|
||||
tbody
|
||||
- @file_templates.each do |file_template|
|
||||
tr
|
||||
td = file_template.name
|
||||
td = link_to(file_template.file_type, file_type_path(file_template.file_type))
|
||||
td = link_to(t('shared.show'), file_template)
|
||||
td = link_to(t('shared.edit'), edit_file_template_path(file_template))
|
||||
td = link_to(t('shared.destroy'), file_template, data: {confirm: t('shared.confirm_destroy')}, method: :delete)
|
||||
|
||||
= render('shared/pagination', collection: @file_templates)
|
||||
p = render('shared/new_button', model: FileTemplate)
|
3
app/views/file_templates/new.html.slim
Normal file
3
app/views/file_templates/new.html.slim
Normal file
@ -0,0 +1,3 @@
|
||||
h1 = t('shared.new_model', model: FileTemplate.model_name.human)
|
||||
|
||||
= render('form')
|
7
app/views/file_templates/show.html.slim
Normal file
7
app/views/file_templates/show.html.slim
Normal file
@ -0,0 +1,7 @@
|
||||
h1
|
||||
= @file_template
|
||||
= render('shared/edit_button', object: @file_template)
|
||||
|
||||
= row(label: 'file_template.name', value: @file_template.name)
|
||||
= row(label: 'file_template.file_type', value: link_to(@file_template.file_type, file_type_path(@file_template.file_type)))
|
||||
= row(label: 'file_template.content', value: @file_template.content)
|
@ -4,19 +4,29 @@ h1 = RequestForComment.model_name.human(count: 2)
|
||||
table.table.sortable
|
||||
thead
|
||||
tr
|
||||
th
|
||||
i class="fa fa-lightbulb-o" aria-hidden="true" title = t('request_for_comments.solved') align="right"
|
||||
th = t('activerecord.attributes.request_for_comments.exercise')
|
||||
th = t('activerecord.attributes.request_for_comments.question')
|
||||
th
|
||||
i class="fa fa-comment" aria-hidden="true" title = t('request_for_comments.comments') align="center"
|
||||
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
|
||||
- if request_for_comment.solved?
|
||||
td
|
||||
span class="fa fa-check" aria-hidden="true"
|
||||
- else
|
||||
td = ''
|
||||
td = link_to(request_for_comment.exercise.title, request_for_comment)
|
||||
- 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.comments_count
|
||||
td = request_for_comment.user.displayname
|
||||
td = t('shared.time.before', time: distance_of_time_in_words_to_now(request_for_comment.requested_at))
|
||||
td = t('shared.time.before', time: distance_of_time_in_words_to_now(request_for_comment.created_at))
|
||||
|
||||
= render('shared/pagination', collection: @request_for_comments)
|
@ -1,46 +1,76 @@
|
||||
<div class="list-group">
|
||||
<h4 class="list-group-item-heading"><%= Exercise.find(@request_for_comment.exercise_id) %></h4>
|
||||
<h4 id ="exercise_caption" class="list-group-item-heading" data-rfc-id = "<%= @request_for_comment.id %>" ><%= link_to(@request_for_comment.exercise.title, [:implement, @request_for_comment.exercise]) %></h4>
|
||||
<p class="list-group-item-text">
|
||||
<%
|
||||
user = @request_for_comment.user
|
||||
submission_id = ActiveRecord::Base.connection.execute("select id from submissions
|
||||
where exercise_id =
|
||||
#{@request_for_comment.exercise_id} AND
|
||||
user_id = #{@request_for_comment.user_id} AND
|
||||
'#{@request_for_comment.created_at}' > created_at
|
||||
order by created_at desc
|
||||
limit 1").first['id'].to_i
|
||||
submission = Submission.find(submission_id)
|
||||
submission = @request_for_comment.submission
|
||||
%>
|
||||
<%= user.displayname %> | <%= @request_for_comment.requested_at %>
|
||||
<%= user.displayname %> | <%= @request_for_comment.created_at.localtime %>
|
||||
</p>
|
||||
<h5>
|
||||
<u><%= t('activerecord.attributes.exercise.description') %>:</u> "<%= render_markdown(@request_for_comment.exercise.description) %>"
|
||||
</h5>
|
||||
|
||||
<h5>
|
||||
<% if @request_for_comment.question and not @request_for_comment.question == '' %>
|
||||
<%= t('activerecord.attributes.request_for_comments.question')%>: "<%= @request_for_comment.question %>"
|
||||
<u><%= t('activerecord.attributes.request_for_comments.question')%>:</u> "<%= @request_for_comment.question %>"
|
||||
<% else %>
|
||||
<%= t('request_for_comments.no_question') %>
|
||||
<u><%= t('activerecord.attributes.request_for_comments.question')%>:</u> <%= t('request_for_comments.no_question') %>
|
||||
<% end %>
|
||||
</h5>
|
||||
<% if (policy(@request_for_comment).mark_as_solved? and not @request_for_comment.solved?) %>
|
||||
<button class="btn btn-default" id="mark-as-solved-button"><%= t('request_for_comments.mark_as_solved') %></button>
|
||||
<% elsif (@request_for_comment.solved?) %>
|
||||
<button type="button" class="btn btn-success"><%= t('request_for_comments.solved') %></button>
|
||||
<% else %>
|
||||
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
do not put a carriage return in the line below. it will be present in the presentation of the source code, otherwise.
|
||||
also, all settings from the rails model needed for the editor configuration in the JavaScript are attached to the editor as data attributes here.
|
||||
-->
|
||||
<% submission.files.each do |file| %>
|
||||
<%= (file.path or "") + "/" + file.name + file.file_type.file_extension %>
|
||||
<div id='commentitor' class='editor' data-read-only='true' data-file-id='<%=file.id%>'><%= file.content %>
|
||||
<div id='commentitor' class='editor' data-read-only='true' data-file-id='<%=file.id%>' data-mode='<%=file.file_type.editor_mode%>'><%= file.content %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= render('shared/modal', id: 'comment-modal', title: t('exercises.implement.comment.dialogtitle'), template: 'exercises/_comment_dialogcontent') %>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
var solvedButton = $('#mark-as-solved-button');
|
||||
|
||||
solvedButton.on('click', function(event){
|
||||
var jqrequest = $.ajax({
|
||||
dataType: 'json',
|
||||
method: 'GET',
|
||||
url: location + '/mark_as_solved'
|
||||
});
|
||||
|
||||
jqrequest.done(function(response){
|
||||
if(response.solved){
|
||||
solvedButton.hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// set file paths for ace
|
||||
var ACE_FILES_PATH = '/assets/ace/';
|
||||
_.each(['modePath', 'themePath', 'workerPath'], function(attribute) {
|
||||
ace.config.set(attribute, ACE_FILES_PATH);
|
||||
});
|
||||
|
||||
var commentitor = $('.editor');
|
||||
var userid = commentitor.data('user-id');
|
||||
|
||||
commentitor.each(function (index, editor) {
|
||||
var currentEditor = ace.edit(editor);
|
||||
currentEditor.setReadOnly(true);
|
||||
// set editor mode (used for syntax highlighting
|
||||
currentEditor.getSession().setMode($(editor).data('mode'));
|
||||
|
||||
setAnnotations(currentEditor, $(editor).data('file-id'));
|
||||
currentEditor.on("guttermousedown", handleSidebarClick);
|
||||
@ -101,7 +131,8 @@ do not put a carriage return in the line below. it will be present in the presen
|
||||
file_id: file_id,
|
||||
row: row,
|
||||
column: 0,
|
||||
text: commenttext
|
||||
text: commenttext,
|
||||
request_id: $('h4#exercise_caption').data('rfc-id')
|
||||
}
|
||||
},
|
||||
dataType: 'json',
|
||||
|
@ -1 +1 @@
|
||||
json.extract! @request_for_comment, :id, :user_id, :exercise_id, :file_id, :requested_at, :created_at, :updated_at, :user_type
|
||||
json.extract! @request_for_comment, :id, :user_id, :exercise_id, :file_id, :requested_at, :created_at, :updated_at, :user_type, :solved
|
||||
|
@ -1 +1 @@
|
||||
json.extract! @submission, :download_url, :id, :score_url, :render_url, :run_url, :stop_url, :test_url, :files
|
||||
json.extract! @submission, :download_url, :download_file_url, :id, :score_url, :render_url, :run_url, :stop_url, :test_url, :files
|
||||
|
@ -1,9 +0,0 @@
|
||||
= form_for(@team) do |f|
|
||||
= render('shared/form_errors', object: @team)
|
||||
.form-group
|
||||
= f.label(:name)
|
||||
= f.text_field(:name, class: 'form-control', required: true)
|
||||
.form-group
|
||||
= f.label(:internal_user_ids)
|
||||
= f.collection_select(:internal_user_ids, InternalUser.all.order(:name), :id, :name, {}, {class: 'form-control', multiple: true})
|
||||
.actions = render('shared/submit_button', f: f, object: @team)
|
@ -1,3 +0,0 @@
|
||||
h1 = @hint
|
||||
|
||||
= render('form')
|
@ -1,20 +0,0 @@
|
||||
h1 = Team.model_name.human(count: 2)
|
||||
|
||||
.table-responsive
|
||||
table.table
|
||||
thead
|
||||
tr
|
||||
th = t('activerecord.attributes.team.name')
|
||||
th = t('activerecord.attributes.team.internal_user_ids')
|
||||
th colspan=3 = t('shared.actions')
|
||||
tbody
|
||||
- @teams.each do |team|
|
||||
tr
|
||||
td = team.name
|
||||
td = team.members.count
|
||||
td = link_to(t('shared.show'), team_path(team.id))
|
||||
td = link_to(t('shared.edit'), edit_team_path(team.id))
|
||||
td = link_to(t('shared.destroy'), team_path(team.id), data: {confirm: t('shared.confirm_destroy')}, method: :delete)
|
||||
|
||||
= render('shared/pagination', collection: @teams)
|
||||
p = render('shared/new_button', model: Team, path: new_team_path)
|
@ -1,3 +0,0 @@
|
||||
h1 = t('shared.new_model', model: Team.model_name.human)
|
||||
|
||||
= render('form')
|
@ -1,9 +0,0 @@
|
||||
h1
|
||||
= @team
|
||||
= render('shared/edit_button', object: @team, path: edit_team_path(@team.id))
|
||||
|
||||
= row(label: 'team.name', value: @team.name)
|
||||
= row(label: 'team.internal_user_ids') do
|
||||
ul.list-unstyled
|
||||
- @team.members.order(:name).each do |internal_user|
|
||||
li = link_to(internal_user, internal_user)
|
1
app/views/user_mailer/got_new_comment.slim
Normal file
1
app/views/user_mailer/got_new_comment.slim
Normal file
@ -0,0 +1 @@
|
||||
== t('mailers.user_mailer.got_new_comment.body', receiver_displayname: @receiver_displayname, link: link_to(@rfc_link, @rfc_link), commenting_user_displayname: @commenting_user_displayname, comment_text: @comment_text)
|
Reference in New Issue
Block a user