From 1cd879bcb6560ed43f35a366281f40042d6c1370 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Thu, 9 Jun 2016 22:38:19 +0200 Subject: [PATCH 01/34] Scaffold file templates --- .../javascripts/file_templates.js.coffee | 3 + .../stylesheets/file_templates.css.scss | 3 + app/controllers/file_templates_controller.rb | 86 +++++++++++++++++++ app/models/file_template.rb | 10 +++ app/models/file_type.rb | 1 + app/policies/file_template_policy.rb | 7 ++ app/views/file_templates/_form.html.slim | 12 +++ app/views/file_templates/edit.html.slim | 3 + app/views/file_templates/index.html.slim | 20 +++++ app/views/file_templates/new.html.slim | 3 + app/views/file_templates/show.html.slim | 7 ++ config/locales/de.yml | 4 + config/locales/en.yml | 4 + config/routes.rb | 1 + .../20160609185708_create_file_templates.rb | 10 +++ db/schema.rb | 10 ++- 16 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/file_templates.js.coffee create mode 100644 app/assets/stylesheets/file_templates.css.scss create mode 100644 app/controllers/file_templates_controller.rb create mode 100644 app/models/file_template.rb create mode 100644 app/policies/file_template_policy.rb create mode 100644 app/views/file_templates/_form.html.slim create mode 100644 app/views/file_templates/edit.html.slim create mode 100644 app/views/file_templates/index.html.slim create mode 100644 app/views/file_templates/new.html.slim create mode 100644 app/views/file_templates/show.html.slim create mode 100644 db/migrate/20160609185708_create_file_templates.rb diff --git a/app/assets/javascripts/file_templates.js.coffee b/app/assets/javascripts/file_templates.js.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/file_templates.js.coffee @@ -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/ diff --git a/app/assets/stylesheets/file_templates.css.scss b/app/assets/stylesheets/file_templates.css.scss new file mode 100644 index 00000000..bf8e27e8 --- /dev/null +++ b/app/assets/stylesheets/file_templates.css.scss @@ -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/ diff --git a/app/controllers/file_templates_controller.rb b/app/controllers/file_templates_controller.rb new file mode 100644 index 00000000..45636d61 --- /dev/null +++ b/app/controllers/file_templates_controller.rb @@ -0,0 +1,86 @@ +class FileTemplatesController < ApplicationController + before_action :set_file_template, only: [:show, :edit, :update, :destroy] + + def authorize! + authorize(@file_template || @file_templates) + end + private :authorize! + + # 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 diff --git a/app/models/file_template.rb b/app/models/file_template.rb new file mode 100644 index 00000000..ef068e13 --- /dev/null +++ b/app/models/file_template.rb @@ -0,0 +1,10 @@ +class FileTemplate < ActiveRecord::Base + + belongs_to :file_type + + + def to_s + name + end + +end diff --git a/app/models/file_type.rb b/app/models/file_type.rb index 53bf18dc..d3b519d5 100644 --- a/app/models/file_type.rb +++ b/app/models/file_type.rb @@ -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? diff --git a/app/policies/file_template_policy.rb b/app/policies/file_template_policy.rb new file mode 100644 index 00000000..b2f780a8 --- /dev/null +++ b/app/policies/file_template_policy.rb @@ -0,0 +1,7 @@ +class FileTemplatePolicy < AdminOnlyPolicy + + def show? + everyone + end + +end diff --git a/app/views/file_templates/_form.html.slim b/app/views/file_templates/_form.html.slim new file mode 100644 index 00000000..1a5c34bc --- /dev/null +++ b/app/views/file_templates/_form.html.slim @@ -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) diff --git a/app/views/file_templates/edit.html.slim b/app/views/file_templates/edit.html.slim new file mode 100644 index 00000000..c198271f --- /dev/null +++ b/app/views/file_templates/edit.html.slim @@ -0,0 +1,3 @@ +h1 = @file_template + += render('form') diff --git a/app/views/file_templates/index.html.slim b/app/views/file_templates/index.html.slim new file mode 100644 index 00000000..3022ea53 --- /dev/null +++ b/app/views/file_templates/index.html.slim @@ -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) diff --git a/app/views/file_templates/new.html.slim b/app/views/file_templates/new.html.slim new file mode 100644 index 00000000..bf434860 --- /dev/null +++ b/app/views/file_templates/new.html.slim @@ -0,0 +1,3 @@ +h1 = t('shared.new_model', model: FileTemplate.model_name.human) + += render('form') diff --git a/app/views/file_templates/show.html.slim b/app/views/file_templates/show.html.slim new file mode 100644 index 00000000..19f0d28f --- /dev/null +++ b/app/views/file_templates/show.html.slim @@ -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) diff --git a/config/locales/de.yml b/config/locales/de.yml index e7d78943..4a98e0a0 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -93,6 +93,10 @@ de: team: internal_user_ids: Mitglieder name: Name + file_template: + name: "Name" + file_type: "Dateityp" + content: "Code" models: code_harbor_link: one: CodeHarbor-Link diff --git a/config/locales/en.yml b/config/locales/en.yml index f2470fc8..de0f3aa4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -93,6 +93,10 @@ en: team: internal_user_ids: Members name: Name + file_template: + name: "Name" + file_type: "File Type" + content: "Content" models: code_harbor_link: one: CodeHarbor Link diff --git a/config/routes.rb b/config/routes.rb index 72ccb489..7965f0a3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,7 @@ FILENAME_REGEXP = /[\w\.]+/ unless Kernel.const_defined?(:FILENAME_REGEXP) Rails.application.routes.draw do + resources :file_templates resources :code_harbor_links resources :request_for_comments get '/my_request_for_comments', as: 'my_request_for_comments', to: 'request_for_comments#get_my_comment_requests' diff --git a/db/migrate/20160609185708_create_file_templates.rb b/db/migrate/20160609185708_create_file_templates.rb new file mode 100644 index 00000000..43cde0d0 --- /dev/null +++ b/db/migrate/20160609185708_create_file_templates.rb @@ -0,0 +1,10 @@ +class CreateFileTemplates < ActiveRecord::Migration + def change + create_table :file_templates do |t| + t.string :name + t.text :content + t.belongs_to :file_type + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 104a1469..4f7fc833 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160512131539) do +ActiveRecord::Schema.define(version: 20160609185708) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -101,6 +101,14 @@ ActiveRecord::Schema.define(version: 20160512131539) do t.datetime "updated_at" end + create_table "file_templates", force: true do |t| + t.string "name" + t.text "content" + t.integer "file_type_id" + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "file_types", force: true do |t| t.string "editor_mode" t.string "file_extension" From 4f8feb38e10c7e3591a6f7c60436c45dbd67e29d Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Fri, 10 Jun 2016 13:41:38 +0200 Subject: [PATCH 02/34] Use file template to generate new file content --- app/controllers/code_ocean/files_controller.rb | 3 +++ app/controllers/concerns/file_parameters.rb | 2 +- app/views/code_ocean/files/_form.html.slim | 3 +++ config/locales/de.yml | 1 + config/locales/en.yml | 1 + db/migrate/20160610111602_add_file_template_to_file.rb | 5 +++++ db/schema.rb | 3 ++- 7 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20160610111602_add_file_template_to_file.rb diff --git a/app/controllers/code_ocean/files_controller.rb b/app/controllers/code_ocean/files_controller.rb index 74c1932f..e38607c2 100644 --- a/app/controllers/code_ocean/files_controller.rb +++ b/app/controllers/code_ocean/files_controller.rb @@ -10,6 +10,9 @@ module CodeOcean def create @file = CodeOcean::File.new(file_params) + if @file.file_template_id + @file.content = FileTemplate.find(@file.file_template_id).content + end authorize! create_and_respond(object: @file, path: proc { implement_exercise_path(@file.context.exercise, tab: 2) }) end diff --git a/app/controllers/concerns/file_parameters.rb b/app/controllers/concerns/file_parameters.rb index e61e719e..295b66c3 100644 --- a/app/controllers/concerns/file_parameters.rb +++ b/app/controllers/concerns/file_parameters.rb @@ -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 diff --git a/app/views/code_ocean/files/_form.html.slim b/app/views/code_ocean/files/_form.html.slim index 07dd3355..ee74f4b8 100644 --- a/app/views/code_ocean/files/_form.html.slim +++ b/app/views/code_ocean/files/_form.html.slim @@ -8,5 +8,8 @@ .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, {}, class: 'form-control') = f.hidden_field(:context_id) .actions = render('shared/submit_button', f: f, object: CodeOcean::File.new) diff --git a/config/locales/de.yml b/config/locales/de.yml index 4a98e0a0..9a48ee4f 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -54,6 +54,7 @@ de: read_only: Schreibgeschützt role: Rolle weight: Punktzahl + file_template_id: "Dateivorlage" file_type: binary: Binär editor_mode: Editor-Modus diff --git a/config/locales/en.yml b/config/locales/en.yml index de0f3aa4..49fc8bbb 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -54,6 +54,7 @@ en: read_only: Read-only role: Role weight: Score + file_template_id: "File Template" file_type: binary: Binary editor_mode: Editor Mode diff --git a/db/migrate/20160610111602_add_file_template_to_file.rb b/db/migrate/20160610111602_add_file_template_to_file.rb new file mode 100644 index 00000000..a595e90b --- /dev/null +++ b/db/migrate/20160610111602_add_file_template_to_file.rb @@ -0,0 +1,5 @@ +class AddFileTemplateToFile < ActiveRecord::Migration + def change + add_reference :files, :file_template + end +end diff --git a/db/schema.rb b/db/schema.rb index 4f7fc833..bed23c98 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160609185708) do +ActiveRecord::Schema.define(version: 20160610111602) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -140,6 +140,7 @@ ActiveRecord::Schema.define(version: 20160609185708) do t.string "feedback_message" t.float "weight" t.string "path" + t.integer "file_template_id" end add_index "files", ["context_id", "context_type"], name: "index_files_on_context_id_and_context_type", using: :btree From c10b07690ad29138dd7be79bc2fdf21836b90b48 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Fri, 10 Jun 2016 13:46:13 +0200 Subject: [PATCH 03/34] Make file template optional --- app/views/code_ocean/files/_form.html.slim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/code_ocean/files/_form.html.slim b/app/views/code_ocean/files/_form.html.slim index ee74f4b8..4b44647d 100644 --- a/app/views/code_ocean/files/_form.html.slim +++ b/app/views/code_ocean/files/_form.html.slim @@ -10,6 +10,6 @@ = 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, {}, class: 'form-control') + = f.collection_select(:file_template_id, FileTemplate.all.order(:name), :id, :name, {:include_blank => true}, class: 'form-control') = f.hidden_field(:context_id) .actions = render('shared/submit_button', f: f, object: CodeOcean::File.new) From 4d2676fea75a724faed1ef7a2beaf0d04d22c4c2 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Fri, 10 Jun 2016 17:48:04 +0200 Subject: [PATCH 04/34] Only show file templates which are available for the selected file type --- app/assets/javascripts/exercises.js | 20 ++++++++++++++++++++ app/controllers/file_templates_controller.rb | 8 ++++++++ app/policies/file_template_policy.rb | 4 ++++ app/views/code_ocean/files/_form.html.slim | 1 + config/locales/de.yml | 2 ++ config/locales/en.yml | 2 ++ config/routes.rb | 6 +++++- 7 files changed, 42 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/exercises.js b/app/assets/javascripts/exercises.js index 1f861ef6..0e3805db 100644 --- a/app/assets/javascripts/exercises.js +++ b/app/assets/javascripts/exercises.js @@ -148,6 +148,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 = ""; + for (var i = 0; i < response.length; i++) { + options += "" + } + $("#code_ocean_file_file_template_id").find('option').remove().end().append($(options)); + }); + jqxhr.fail(ajaxError); + } + if ($.isController('exercises')) { if ($('table').isPresent()) { enableBatchUpdate(); @@ -162,6 +178,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) { diff --git a/app/controllers/file_templates_controller.rb b/app/controllers/file_templates_controller.rb index 45636d61..a6039500 100644 --- a/app/controllers/file_templates_controller.rb +++ b/app/controllers/file_templates_controller.rb @@ -6,6 +6,14 @@ class FileTemplatesController < ApplicationController 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 diff --git a/app/policies/file_template_policy.rb b/app/policies/file_template_policy.rb index b2f780a8..92ced442 100644 --- a/app/policies/file_template_policy.rb +++ b/app/policies/file_template_policy.rb @@ -4,4 +4,8 @@ class FileTemplatePolicy < AdminOnlyPolicy everyone end + def by_file_type? + everyone + end + end diff --git a/app/views/code_ocean/files/_form.html.slim b/app/views/code_ocean/files/_form.html.slim index 4b44647d..46c5b2c2 100644 --- a/app/views/code_ocean/files/_form.html.slim +++ b/app/views/code_ocean/files/_form.html.slim @@ -12,4 +12,5 @@ = 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) diff --git a/config/locales/de.yml b/config/locales/de.yml index 9a48ee4f..c0aa4b13 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -432,3 +432,5 @@ de: next_label: 'Nächste Seite →' page_gap: '…' previous_label: '← Vorherige Seite' + file_template: + no_template_label: "Leere Datei" diff --git a/config/locales/en.yml b/config/locales/en.yml index 49fc8bbb..f03e42fb 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -432,3 +432,5 @@ en: next_label: 'Next Page →' page_gap: '…' previous_label: '← Previous Page' + file_template: + no_template_label: "Empty File" diff --git a/config/routes.rb b/config/routes.rb index 7965f0a3..afd8c8ce 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,11 @@ FILENAME_REGEXP = /[\w\.]+/ unless Kernel.const_defined?(:FILENAME_REGEXP) Rails.application.routes.draw do - resources :file_templates + resources :file_templates do + collection do + get 'by_file_type/:file_type_id', as: :by_file_type, to: :by_file_type + end + end resources :code_harbor_links resources :request_for_comments get '/my_request_for_comments', as: 'my_request_for_comments', to: 'request_for_comments#get_my_comment_requests' From 8d030e42e9621391c4c9aa4a7de32eb731cdba27 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Fri, 10 Jun 2016 18:08:57 +0200 Subject: [PATCH 05/34] Allow templates to include the file name as a macro --- app/controllers/code_ocean/files_controller.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/code_ocean/files_controller.rb b/app/controllers/code_ocean/files_controller.rb index e38607c2..55d8b369 100644 --- a/app/controllers/code_ocean/files_controller.rb +++ b/app/controllers/code_ocean/files_controller.rb @@ -11,7 +11,9 @@ module CodeOcean def create @file = CodeOcean::File.new(file_params) if @file.file_template_id - @file.content = FileTemplate.find(@file.file_template_id).content + 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) }) From 7ef401f75aa97c9dd4fc86ec7deb87151891d187 Mon Sep 17 00:00:00 2001 From: Maximilian Grundke Date: Fri, 10 Jun 2016 18:15:37 +0200 Subject: [PATCH 06/34] Add navigation item for file templates --- app/views/application/_navigation.html.slim | 2 +- config/locales/de.yml | 3 +++ config/locales/en.yml | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/views/application/_navigation.html.slim b/app/views/application/_navigation.html.slim index 4ab39e30..0f859844 100644 --- a/app/views/application/_navigation.html.slim +++ b/app/views/application/_navigation.html.slim @@ -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, Team].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")) diff --git a/config/locales/de.yml b/config/locales/de.yml index c0aa4b13..c0013777 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -120,6 +120,9 @@ de: file: one: Datei other: Dateien + file_template: + one: Dateivorlage + other: Dateivorlagen file_type: one: Dateityp other: Dateitypen diff --git a/config/locales/en.yml b/config/locales/en.yml index f03e42fb..1d597bf8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -120,6 +120,9 @@ en: file: one: File other: Files + file_template: + one: File Template + other: File Templates file_type: one: File Type other: File Types From 96de763b8314c0fb6a194fc985cb6191c651dbe0 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Fri, 17 Jun 2016 16:42:07 +0200 Subject: [PATCH 07/34] Handle scoring presentation to client via websockets --- app/assets/javascripts/editor.js.erb | 19 ++++++++++--------- app/controllers/submissions_controller.rb | 6 +++++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index 1e64195e..f9555e92 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -184,8 +184,8 @@ $(function() { (streamed ? evaluateCodeWithStreamedResponse : evaluateCodeWithoutStreamedResponse)(url, callback); }; - var evaluateCodeWithStreamedResponse = function(url, callback) { - initWebsocketConnection(url); + var evaluateCodeWithStreamedResponse = function(url, onmessageFunction) { + initWebsocketConnection(url, onmessageFunction); // TODO only init turtle when required initTurtle(); @@ -306,9 +306,10 @@ $(function() { } }; - var handleScoringResponse = function(response) { - printScoringResults(response); - var score = _.reduce(response, function(sum, result) { + var handleScoringResponse = function(websocket_event) { + results = JSON.parse(websocket_event.data); + printScoringResults(results); + var score = _.reduce(results, function(sum, result) { return sum + result.score * result.weight; }, 0).toFixed(2); $('#score').data('score', score); @@ -743,7 +744,7 @@ $(function() { showSpinner($('#run')); toggleButtonStates(); var url = response.run_url.replace(FILENAME_URL_PLACEHOLDER, active_file.filename); - evaluateCode(url, true, printChunk); + evaluateCode(url, true, function(evt) { parseCanvasMessage(evt.data, true); }); }); } }; @@ -778,7 +779,7 @@ $(function() { createSubmission(this, null, function(response) { showSpinner($('#assess')); var url = response.score_url; - evaluateCode(url, false, handleScoringResponse); + evaluateCode(url, true, handleScoringResponse); }); }; @@ -974,14 +975,14 @@ $(function() { $('#test').toggle(isActiveFileTestable()); }; - var initWebsocketConnection = function(url) { + var initWebsocketConnection = function(url, onmessageFunction) { //TODO: get the protocol from config file dependent on environment. (dev: ws, prod: wss) //causes: Puma::HttpParserError: Invalid HTTP format, parsing fails. //TODO: make sure that this gets cached. websocket = new WebSocket('<%= DockerClient.config['ws_client_protocol'] %>' + window.location.hostname + ':' + window.location.port + url); websocket.onopen = function(evt) { resetOutputTab(); }; // todo show some kind of indicator for established connection websocket.onclose = function(evt) { /* expected at some point */ }; - websocket.onmessage = function(evt) { parseCanvasMessage(evt.data, true); }; + websocket.onmessage = onmessageFunction; websocket.onerror = function(evt) { showWebsocketError(); }; websocket.flush = function() { this.send('\n'); } }; diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 4f9d6a5b..c656ea87 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -214,7 +214,11 @@ class SubmissionsController < ApplicationController end def score - render(json: score_submission(@submission)) + hijack do |tubesock| + Thread.new { EventMachine.run } unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive? + # tubesock is the socket to the client + tubesock.send_data JSON.dump(score_submission(@submission)) + end end def set_docker_client From 7c1be5594a72d210997fb2757ef3448712cbbe55 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Fri, 17 Jun 2016 17:23:47 +0200 Subject: [PATCH 08/34] also handle testcommand via websocket --- app/assets/javascripts/editor.js.erb | 11 ++++++----- app/controllers/submissions_controller.rb | 10 ++++++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index f9555e92..dc79ccff 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -350,13 +350,14 @@ $(function() { flowrOutputBuffer = ''; }; - var handleTestResponse = function(response) { + var handleTestResponse = function(websocket_event) { + result = JSON.parse(websocket_event.data); clearOutput(); - printOutput(response[0], false, 0); + printOutput(result, false, 0); if (qa_api) { - qa_api.executeCommand('syncOutput', [response]); + qa_api.executeCommand('syncOutput', [result]); } - showStatus(response[0]); + showStatus(result); showTab(1); }; @@ -956,7 +957,7 @@ $(function() { createSubmission(this, null, function(response) { showSpinner($('#test')); var url = response.test_url.replace(FILENAME_URL_PLACEHOLDER, active_file.filename); - evaluateCode(url, false, handleTestResponse); + evaluateCode(url, true, handleTestResponse); }); } }; diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index c656ea87..335e4b0c 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -270,8 +270,14 @@ class SubmissionsController < ApplicationController private :store_error def test - output = @docker_client.execute_test_command(@submission, params[:filename]) - render(json: [output]) + hijack do |tubesock| + Thread.new { EventMachine.run } unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive? + + output = @docker_client.execute_test_command(@submission, params[:filename]) + + # tubesock is the socket to the client + tubesock.send_data JSON.dump(output) + end end def with_server_sent_events From d29cf9cf6134691cdeba974499d4979b46521848 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Fri, 17 Jun 2016 19:22:25 +0200 Subject: [PATCH 09/34] Also support run output for QaApi --- app/assets/javascripts/editor.js.erb | 15 +++++++++++++++ lib/docker_client.rb | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index dc79ccff..f9ab5b94 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -37,6 +37,7 @@ $(function() { var ENTER_KEY_CODE = 13; var flowrOutputBuffer = ""; + var QaApiOutputBuffer = {'stdout': '', 'stderr': ''}; var flowrResultHtml = '
' var ajax = function(options) { @@ -317,6 +318,14 @@ $(function() { showTab(2); }; + var handleQaApiOutput = function() { + if (qa_api) { + qa_api.executeCommand('syncOutput', [[QaApiOutputBuffer]]); + // reset the object + } + QaApiOutputBuffer = {'stdout': '', 'stderr': ''}; + } + // activate flowr only for half of the audience var isFlowrEnabled = true;//parseInt($('#editor').data('user-id'))%2 == 0; var handleStderrOutputForFlowr = function() { @@ -586,10 +595,15 @@ $(function() { } else if (output.stderr) { element.addClass('text-warning').append(output.stderr); + flowrOutputBuffer += output.stderr; + QaApiOutputBuffer.stderr += output.stderr; } else if (output.stdout) { //if (output_mode_is_streaming){ 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); @@ -1037,6 +1051,7 @@ $(function() { break; case 'exit': killWebsocketAndContainer(); + handleQaApiOutput(); handleStderrOutputForFlowr(); break; case 'timeout': diff --git a/lib/docker_client.rb b/lib/docker_client.rb index 80986377..f993c37c 100644 --- a/lib/docker_client.rb +++ b/lib/docker_client.rb @@ -200,7 +200,7 @@ class DockerClient execute_command(command, nil, block) end - #only used by server sent events (deprecated?) + #only used by score def execute_command(command, before_execution_block, output_consuming_block) #tries ||= 0 @container = DockerContainerPool.get_container(@execution_environment) From c21bf3f3d3aeb4a2d4130886e644e7e69d071249 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Fri, 17 Jun 2016 19:24:04 +0200 Subject: [PATCH 10/34] Adapt Modernizr requires.. We no longer need eventsource --- app/assets/javascripts/editor.js.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index f9ab5b94..c00d7350 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -538,8 +538,8 @@ $(function() { }; var isBrowserSupported = function() { - // eventsource tests for server send events (used for scoring), websockets is used for run - return Modernizr.eventsource && Modernizr.websockets; + // websockets is used for run, score and test + return Modernizr.websockets; }; var populatePanel = function(panel, result, index) { From 72def4a661c8852da24e094695c62cc3ca0c5f41 Mon Sep 17 00:00:00 2001 From: yqbk Date: Thu, 30 Jun 2016 12:00:54 +0200 Subject: [PATCH 11/34] delete class team --- app/models/team.rb | 10 ---------- app/views/application/_navigation.html.slim | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 app/models/team.rb diff --git a/app/models/team.rb b/app/models/team.rb deleted file mode 100644 index a0dcb8d6..00000000 --- a/app/models/team.rb +++ /dev/null @@ -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 diff --git a/app/views/application/_navigation.html.slim b/app/views/application/_navigation.html.slim index 4ab39e30..3c260cb8 100644 --- a/app/views/application/_navigation.html.slim +++ b/app/views/application/_navigation.html.slim @@ -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, 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")) From a1023eb9fa07dadf504ca9ef502e4fcda585931a Mon Sep 17 00:00:00 2001 From: yqbk Date: Thu, 30 Jun 2016 12:02:44 +0200 Subject: [PATCH 12/34] delete teams view --- app/views/teams/_form.html.slim | 9 --------- app/views/teams/edit.html.slim | 3 --- app/views/teams/index.html.slim | 20 -------------------- app/views/teams/new.html.slim | 3 --- app/views/teams/show.html.slim | 9 --------- 5 files changed, 44 deletions(-) delete mode 100644 app/views/teams/_form.html.slim delete mode 100644 app/views/teams/edit.html.slim delete mode 100644 app/views/teams/index.html.slim delete mode 100644 app/views/teams/new.html.slim delete mode 100644 app/views/teams/show.html.slim diff --git a/app/views/teams/_form.html.slim b/app/views/teams/_form.html.slim deleted file mode 100644 index 47f547dd..00000000 --- a/app/views/teams/_form.html.slim +++ /dev/null @@ -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) diff --git a/app/views/teams/edit.html.slim b/app/views/teams/edit.html.slim deleted file mode 100644 index 0c1f4dac..00000000 --- a/app/views/teams/edit.html.slim +++ /dev/null @@ -1,3 +0,0 @@ -h1 = @hint - -= render('form') diff --git a/app/views/teams/index.html.slim b/app/views/teams/index.html.slim deleted file mode 100644 index 4eb6640b..00000000 --- a/app/views/teams/index.html.slim +++ /dev/null @@ -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) diff --git a/app/views/teams/new.html.slim b/app/views/teams/new.html.slim deleted file mode 100644 index 8c8f2aab..00000000 --- a/app/views/teams/new.html.slim +++ /dev/null @@ -1,3 +0,0 @@ -h1 = t('shared.new_model', model: Team.model_name.human) - -= render('form') diff --git a/app/views/teams/show.html.slim b/app/views/teams/show.html.slim deleted file mode 100644 index 1b37d931..00000000 --- a/app/views/teams/show.html.slim +++ /dev/null @@ -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) From 0046706fce6f1660df06cd89a9b7c91864453aa3 Mon Sep 17 00:00:00 2001 From: yqbk Date: Thu, 30 Jun 2016 12:04:16 +0200 Subject: [PATCH 13/34] remove team policy --- app/policies/team_policy.rb | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 app/policies/team_policy.rb diff --git a/app/policies/team_policy.rb b/app/policies/team_policy.rb deleted file mode 100644 index ff05c0c3..00000000 --- a/app/policies/team_policy.rb +++ /dev/null @@ -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 From 2ab0829cb3d160a767763d21614c338d3b2e34c8 Mon Sep 17 00:00:00 2001 From: yqbk Date: Thu, 30 Jun 2016 12:05:25 +0200 Subject: [PATCH 14/34] remove team controller --- app/controllers/teams_controller.rb | 51 ----------------------------- 1 file changed, 51 deletions(-) delete mode 100644 app/controllers/teams_controller.rb diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb deleted file mode 100644 index 1ca8f1fe..00000000 --- a/app/controllers/teams_controller.rb +++ /dev/null @@ -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 From b0d468c0e055035606725332688f9552e7e23757 Mon Sep 17 00:00:00 2001 From: yqbk Date: Thu, 30 Jun 2016 12:17:19 +0200 Subject: [PATCH 15/34] remove rest of teams code -- NEED TO REMOVE TEAMS FROM DATABASE! --- app/controllers/exercises_controller.rb | 8 +------- app/models/exercise.rb | 3 +-- app/models/internal_user.rb | 2 -- app/policies/exercise_policy.rb | 8 ++------ app/views/exercises/_form.html.slim | 3 --- app/views/exercises/show.html.slim | 1 - config/locales/de.yml | 8 -------- config/locales/en.yml | 8 -------- config/routes.rb | 1 - 9 files changed, 4 insertions(+), 38 deletions(-) diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index 0304abb4..45fd04d9 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -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 diff --git a/app/models/exercise.rb b/app/models/exercise.rb index 4a1e0486..24570dc6 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -10,8 +10,7 @@ class Exercise < ActiveRecord::Base after_initialize :set_default_values belongs_to :execution_environment - has_many :submissions - belongs_to :team + has_many :submission has_many :external_users, source: :user, source_type: ExternalUser, through: :submissions has_many :internal_users, source: :user, source_type: InternalUser, through: :submissions diff --git a/app/models/internal_user.rb b/app/models/internal_user.rb index e5cebde9..8f1bf04b 100644 --- a/app/models/internal_user.rb +++ b/app/models/internal_user.rb @@ -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} diff --git a/app/policies/exercise_policy.rb b/app/policies/exercise_policy.rb index 29a1c570..d30c4aff 100644 --- a/app/policies/exercise_policy.rb +++ b/app/policies/exercise_policy.rb @@ -13,23 +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? + #need to remove team query @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) else @scope.none diff --git a/app/views/exercises/_form.html.slim b/app/views/exercises/_form.html.slim index f0f69f7a..968540af 100644 --- a/app/views/exercises/_form.html.slim +++ b/app/views/exercises/_form.html.slim @@ -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) diff --git a/app/views/exercises/show.html.slim b/app/views/exercises/show.html.slim index b4b72932..5c554da8 100644 --- a/app/views/exercises/show.html.slim +++ b/app/views/exercises/show.html.slim @@ -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?) diff --git a/config/locales/de.yml b/config/locales/de.yml index 53df1858..ce8ba98f 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -34,8 +34,6 @@ de: instructions: Anweisungen maximum_score: Erreichbare Punktzahl public: Öffentlich - team: Team - team_id: Team title: Titel user: Autor allow_file_creation: "Dateierstellung erlauben" @@ -91,9 +89,6 @@ de: files: Dateien score: Punktzahl user: Autor - team: - internal_user_ids: Mitglieder - name: Name models: code_harbor_link: one: CodeHarbor-Link @@ -128,9 +123,6 @@ de: submission: one: Abgabe other: Abgaben - team: - one: Team - other: Teams errors: messages: together: 'muss zusammen mit %{attribute} definiert werden' diff --git a/config/locales/en.yml b/config/locales/en.yml index 1e72cb34..86acbb15 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -34,8 +34,6 @@ en: instructions: Instructions maximum_score: Maximum Score public: Public - team: Team - team_id: Team title: Title user: Author allow_file_creation: "Allow file creation" @@ -91,9 +89,6 @@ en: files: Files score: Score user: Author - team: - internal_user_ids: Members - name: Name models: code_harbor_link: one: CodeHarbor Link @@ -128,9 +123,6 @@ en: submission: one: Submission other: Submissions - team: - one: Team - other: Teams errors: messages: together: 'has to be set along with %{attribute}' diff --git a/config/routes.rb b/config/routes.rb index a6ca545c..f13d1c9f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -99,5 +99,4 @@ Rails.application.routes.draw do end end - resources :teams end From 19f214caf1d9d2f2bf4f4d2b0419d9946e363bfb Mon Sep 17 00:00:00 2001 From: yqbk Date: Mon, 4 Jul 2016 16:36:23 +0200 Subject: [PATCH 16/34] remove teams from databse --- db/migrate/20160704142512_drop_teams.rb | 7 +++++++ db/migrate/20160704143402_remove_teams.rb | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 db/migrate/20160704142512_drop_teams.rb create mode 100644 db/migrate/20160704143402_remove_teams.rb diff --git a/db/migrate/20160704142512_drop_teams.rb b/db/migrate/20160704142512_drop_teams.rb new file mode 100644 index 00000000..32a0532a --- /dev/null +++ b/db/migrate/20160704142512_drop_teams.rb @@ -0,0 +1,7 @@ +class DropTeams < ActiveRecord::Migration + def change + remove_column :exercises, :team_id, :integer + drop_table :teams + drop_table :internal_users_teams + end +end diff --git a/db/migrate/20160704143402_remove_teams.rb b/db/migrate/20160704143402_remove_teams.rb new file mode 100644 index 00000000..9c01871a --- /dev/null +++ b/db/migrate/20160704143402_remove_teams.rb @@ -0,0 +1,7 @@ +class RemoveTeams < ActiveRecord::Migration + def change + remove_column :exercises, :team_id, :integer + drop_table :teams + drop_table :internal_users_teams + end +end From a98107479e58d19464e0510911d4a9f2f2493eb2 Mon Sep 17 00:00:00 2001 From: yqbk Date: Mon, 4 Jul 2016 16:37:59 +0200 Subject: [PATCH 17/34] remove additional files --- db/migrate/20160704142512_drop_teams.rb | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 db/migrate/20160704142512_drop_teams.rb diff --git a/db/migrate/20160704142512_drop_teams.rb b/db/migrate/20160704142512_drop_teams.rb deleted file mode 100644 index 32a0532a..00000000 --- a/db/migrate/20160704142512_drop_teams.rb +++ /dev/null @@ -1,7 +0,0 @@ -class DropTeams < ActiveRecord::Migration - def change - remove_column :exercises, :team_id, :integer - drop_table :teams - drop_table :internal_users_teams - end -end From 2bb1362520bda92423d6d3750af350c79e6451d7 Mon Sep 17 00:00:00 2001 From: yqbk Date: Mon, 4 Jul 2016 16:59:30 +0200 Subject: [PATCH 18/34] further delete (spec files) --- db/migrate/20160704143402_remove_teams.rb | 2 +- db/seeds/development.rb | 3 - spec/controllers/teams_controller_spec.rb | 93 ----------------------- spec/factories/team.rb | 6 -- spec/features/authorization_spec.rb | 6 +- spec/models/team_spec.rb | 9 --- spec/policies/exercise_policy_spec.rb | 20 +---- spec/policies/team_policy_spec.rb | 41 ---------- 8 files changed, 6 insertions(+), 174 deletions(-) delete mode 100644 spec/controllers/teams_controller_spec.rb delete mode 100644 spec/factories/team.rb delete mode 100644 spec/models/team_spec.rb delete mode 100644 spec/policies/team_policy_spec.rb diff --git a/db/migrate/20160704143402_remove_teams.rb b/db/migrate/20160704143402_remove_teams.rb index 9c01871a..20b8a204 100644 --- a/db/migrate/20160704143402_remove_teams.rb +++ b/db/migrate/20160704143402_remove_teams.rb @@ -1,6 +1,6 @@ class RemoveTeams < ActiveRecord::Migration def change - remove_column :exercises, :team_id, :integer + remove_reference :exercises, :team drop_table :teams drop_table :internal_users_teams end diff --git a/db/seeds/development.rb b/db/seeds/development.rb index 74783d39..6fc8e050 100644 --- a/db/seeds/development.rb +++ b/db/seeds/development.rb @@ -22,6 +22,3 @@ Hint.create_factories # submissions FactoryGirl.create(:submission, exercise: @exercises[:fibonacci]) - -# teams -FactoryGirl.create(:team, internal_users: InternalUser.limit(10)) diff --git a/spec/controllers/teams_controller_spec.rb b/spec/controllers/teams_controller_spec.rb deleted file mode 100644 index 78dfc60d..00000000 --- a/spec/controllers/teams_controller_spec.rb +++ /dev/null @@ -1,93 +0,0 @@ -require 'rails_helper' - -describe TeamsController do - let(:team) { FactoryGirl.create(:team) } - let(:user) { FactoryGirl.create(:admin) } - before(:each) { allow(controller).to receive(:current_user).and_return(user) } - - describe 'POST #create' do - context 'with a valid team' do - let(:request) { proc { post :create, team: FactoryGirl.attributes_for(:team) } } - before(:each) { request.call } - - expect_assigns(team: Team) - - it 'creates the team' do - expect { request.call }.to change(Team, :count).by(1) - end - - expect_redirect(Team.last) - end - - context 'with an invalid team' do - before(:each) { post :create, team: {} } - - expect_assigns(team: Team) - expect_status(200) - expect_template(:new) - end - end - - describe 'DELETE #destroy' do - before(:each) { delete :destroy, id: team.id } - - expect_assigns(team: Team) - - it 'destroys the team' do - team = FactoryGirl.create(:team) - expect { delete :destroy, id: team.id }.to change(Team, :count).by(-1) - end - - expect_redirect(:teams) - end - - describe 'GET #edit' do - before(:each) { get :edit, id: team.id } - - expect_assigns(team: Team) - expect_status(200) - expect_template(:edit) - end - - describe 'GET #index' do - before(:all) { FactoryGirl.create_pair(:team) } - before(:each) { get :index } - - expect_assigns(teams: Team.all) - expect_status(200) - expect_template(:index) - end - - describe 'GET #new' do - before(:each) { get :new } - - expect_assigns(team: Team) - expect_status(200) - expect_template(:new) - end - - describe 'GET #show' do - before(:each) { get :show, id: team.id } - - expect_assigns(team: :team) - expect_status(200) - expect_template(:show) - end - - describe 'PUT #update' do - context 'with a valid team' do - before(:each) { put :update, team: FactoryGirl.attributes_for(:team), id: team.id } - - expect_assigns(team: Team) - expect_redirect(:team) - end - - context 'with an invalid team' do - before(:each) { put :update, team: {name: ''}, id: team.id } - - expect_assigns(team: Team) - expect_status(200) - expect_template(:edit) - end - end -end diff --git a/spec/factories/team.rb b/spec/factories/team.rb deleted file mode 100644 index f34d323e..00000000 --- a/spec/factories/team.rb +++ /dev/null @@ -1,6 +0,0 @@ -FactoryGirl.define do - factory :team do - internal_users { build_pair :teacher } - name 'The A-Team' - end -end diff --git a/spec/features/authorization_spec.rb b/spec/features/authorization_spec.rb index 6a7eaad0..7f0ff04f 100644 --- a/spec/features/authorization_spec.rb +++ b/spec/features/authorization_spec.rb @@ -5,7 +5,7 @@ describe 'Authorization' do let(:user) { FactoryGirl.create(:admin) } before(:each) { allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) } - [Consumer, ExecutionEnvironment, Exercise, FileType, InternalUser, Team].each do |model| + [Consumer, ExecutionEnvironment, Exercise, FileType, InternalUser].each do |model| expect_permitted_path(:"new_#{model.model_name.singular}_path") end end @@ -14,7 +14,7 @@ describe 'Authorization' do let(:user) { FactoryGirl.create(:external_user) } before(:each) { allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) } - [Consumer, ExecutionEnvironment, Exercise, FileType, InternalUser, Team].each do |model| + [Consumer, ExecutionEnvironment, Exercise, FileType, InternalUser].each do |model| expect_forbidden_path(:"new_#{model.model_name.singular}_path") end end @@ -27,7 +27,7 @@ describe 'Authorization' do expect_forbidden_path(:"new_#{model.model_name.singular}_path") end - [ExecutionEnvironment, Exercise, FileType, Team].each do |model| + [ExecutionEnvironment, Exercise, FileType].each do |model| expect_permitted_path(:"new_#{model.model_name.singular}_path") end end diff --git a/spec/models/team_spec.rb b/spec/models/team_spec.rb deleted file mode 100644 index 777efd32..00000000 --- a/spec/models/team_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'rails_helper' - -describe Team do - let(:team) { described_class.create } - - it 'validates the presence of a name' do - expect(team.errors[:name]).to be_present - end -end diff --git a/spec/policies/exercise_policy_spec.rb b/spec/policies/exercise_policy_spec.rb index c9762f9e..1b25fbca 100644 --- a/spec/policies/exercise_policy_spec.rb +++ b/spec/policies/exercise_policy_spec.rb @@ -3,8 +3,6 @@ require 'rails_helper' describe ExercisePolicy do subject { described_class } - let(:exercise) { FactoryGirl.build(:dummy, team: FactoryGirl.create(:team)) } - permissions :batch_update? do it 'grants access to admins only' do expect(subject).to permit(FactoryGirl.build(:admin), exercise) @@ -40,10 +38,6 @@ describe ExercisePolicy do expect(subject).to permit(exercise.author, exercise) end - it 'grants access to team members' do - expect(subject).to permit(exercise.team.members.first, exercise) - end - it 'does not grant access to all other users' do [:external_user, :teacher].each do |factory_name| expect(subject).not_to permit(FactoryGirl.build(factory_name), exercise) @@ -71,9 +65,7 @@ describe ExercisePolicy do [@admin, @teacher].each do |user| [true, false].each do |public| - [@team, nil].each do |team| - FactoryGirl.create(:dummy, public: public, team: team, user_id: user.id, user_type: InternalUser.class.name) - end + FactoryGirl.create(:dummy, public: public, user_id: user.id, user_type: InternalUser.class.name) end end end @@ -95,10 +87,6 @@ describe ExercisePolicy do end context 'for teachers' do - before(:each) do - @team = FactoryGirl.create(:team) - @team.members << @teacher - end let(:scope) { Pundit.policy_scope!(@teacher, Exercise) } @@ -110,12 +98,8 @@ describe ExercisePolicy do expect(scope.map(&:id)).to include(*Exercise.where(public: false, user_id: @teacher.id).map(&:id)) end - it "includes all of team members' non-public exercises" do - expect(scope.map(&:id)).to include(*Exercise.where(public: false, team_id: @teacher.teams.first.id).map(&:id)) - end - it "does not include other authors' non-public exercises" do - expect(scope.map(&:id)).not_to include(*Exercise.where(public: false).where("team_id <> #{@team.id} AND user_id <> #{@teacher.id}").map(&:id)) + expect(scope.map(&:id)).not_to include(*Exercise.where(public: false).where(user_id <> #{@teacher.id}").map(&:id)) end end end diff --git a/spec/policies/team_policy_spec.rb b/spec/policies/team_policy_spec.rb deleted file mode 100644 index aa3ba1d8..00000000 --- a/spec/policies/team_policy_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'rails_helper' - -describe TeamPolicy do - subject { described_class } - - let(:team) { FactoryGirl.build(:team) } - - [:create?, :index?, :new?].each do |action| - permissions(action) do - it 'grants access to admins' do - expect(subject).to permit(FactoryGirl.build(:admin), team) - end - - it 'grants access to teachers' do - expect(subject).to permit(FactoryGirl.build(:teacher), team) - end - - it 'does not grant access to external users' do - expect(subject).not_to permit(FactoryGirl.build(:external_user), team) - end - end - end - - [:destroy?, :edit?, :show?, :update?].each do |action| - permissions(action) do - it 'grants access to admins' do - expect(subject).to permit(FactoryGirl.build(:admin), team) - end - - it 'grants access to members' do - expect(subject).to permit(team.members.last, team) - end - - it 'does not grant access to all other users' do - [:external_user, :teacher].each do |factory_name| - expect(subject).not_to permit(FactoryGirl.build(factory_name), team) - end - end - end - end -end From 34fc1c450486ec2c786e6054c082d47eb30bb69b Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Mon, 4 Jul 2016 17:36:26 +0200 Subject: [PATCH 19/34] On exit, augment the stacktrace and link it to the editors --- app/assets/javascripts/editor.js.erb | 60 +++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index 1e64195e..51d61031 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -19,6 +19,15 @@ $(function() { var SERVER_SEND_EVENT = 2; var editors = []; + var editor_for_file = new Map(); + + var tracepositions_regexes = { + python: /File "(.+?)", line (\d+)/g, + java: /(.*\.java):(\d):/g + }; + + var tracepositions_regex; + var active_file = undefined; var active_frame = undefined; var running = false; @@ -404,12 +413,23 @@ $(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"){ + var mode = $(element).data('mode'); + if(mode == "ace/mode/python"){ + tracepositions_regex = tracepositions_regexes.python; + } else if (mode = "ace/mode/java"){ + tracepositions_regex = tracepositions_regexes.java; + } + } + var file_id = $(element).data('id'); /* @@ -876,7 +896,9 @@ $(function() { } var showWorkspaceTab = function(event) { - event.preventDefault(); + if(event){ + event.preventDefault(); + } showTab(0); }; @@ -1036,6 +1058,7 @@ $(function() { case 'exit': killWebsocketAndContainer(); 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. @@ -1047,6 +1070,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], "" + matches[0] + "")); + } + } + } + + }; + var renderWebsocketOutput = function(msg){ var element = findOrCreateRenderElement(0); element.append(msg.data); From b962400fab8cd9dffdaf13d9306f7d9dc91a0b4f Mon Sep 17 00:00:00 2001 From: yqbk Date: Mon, 4 Jul 2016 17:40:01 +0200 Subject: [PATCH 20/34] further delete (db queries) --- app/policies/exercise_policy.rb | 3 +-- db/schema.rb | 15 --------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/app/policies/exercise_policy.rb b/app/policies/exercise_policy.rb index d30c4aff..114e7d6e 100644 --- a/app/policies/exercise_policy.rb +++ b/app/policies/exercise_policy.rb @@ -25,8 +25,7 @@ class ExercisePolicy < AdminOrAuthorPolicy if @user.admin? @scope.all elsif @user.internal_user? - #need to remove team query - @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, @user.id) else @scope.none end diff --git a/db/schema.rb b/db/schema.rb index 67b3b35c..2f50d81f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -87,7 +87,6 @@ ActiveRecord::Schema.define(version: 20160624130951) do t.boolean "public" t.string "user_type" t.string "token" - t.integer "team_id" t.boolean "hide_file_tree" t.boolean "allow_file_creation" end @@ -173,14 +172,6 @@ ActiveRecord::Schema.define(version: 20160624130951) do add_index "internal_users", ["remember_me_token"], name: "index_internal_users_on_remember_me_token", using: :btree add_index "internal_users", ["reset_password_token"], name: "index_internal_users_on_reset_password_token", using: :btree - create_table "internal_users_teams", force: true do |t| - t.integer "internal_user_id" - t.integer "team_id" - end - - add_index "internal_users_teams", ["internal_user_id"], name: "index_internal_users_teams_on_internal_user_id", using: :btree - add_index "internal_users_teams", ["team_id"], name: "index_internal_users_teams_on_team_id", using: :btree - create_table "request_for_comments", force: true do |t| t.integer "user_id", null: false t.integer "exercise_id", null: false @@ -203,12 +194,6 @@ ActiveRecord::Schema.define(version: 20160624130951) do t.string "user_type" end - create_table "teams", force: true do |t| - t.string "name" - t.datetime "created_at" - t.datetime "updated_at" - end - create_table "testruns", force: true do |t| t.boolean "passed" t.text "output" From 223df2ffa8a9f34dd23fc2a92a18ec6dee6d7726 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Mon, 4 Jul 2016 17:44:22 +0200 Subject: [PATCH 21/34] some cleanup of request for comments. Work in progress. Noticed a flaw when fetching the last submission, which is caused by timezone differences. First step to solve this. Existing Request for Comments still need to be updated with their current submissionId, the SQL to do that is not yet finished. --- app/assets/javascripts/editor.js.erb | 37 ++++++++++--------- .../request_for_comments_controller.rb | 2 +- app/models/request_for_comment.rb | 29 +++++++++++++++ app/models/submission.rb | 2 +- app/views/exercises/_editor_frame.html.slim | 4 +- app/views/request_for_comments/show.html.erb | 13 ++----- db/schema.rb | 3 +- 7 files changed, 57 insertions(+), 33 deletions(-) diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index 1e64195e..b706b437 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -1160,25 +1160,26 @@ $(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) { + console.log(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'); diff --git a/app/controllers/request_for_comments_controller.rb b/app/controllers/request_for_comments_controller.rb index 72653ab2..37d8bef9 100644 --- a/app/controllers/request_for_comments_controller.rb +++ b/app/controllers/request_for_comments_controller.rb @@ -82,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, :solved).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 diff --git a/app/models/request_for_comment.rb b/app/models/request_for_comment.rb index 57b9a079..1343f5b3 100644 --- a/app/models/request_for_comment.rb +++ b/app/models/request_for_comment.rb @@ -25,6 +25,35 @@ class RequestForComment < ActiveRecord::Base limit 1").first end + def last_submission_before_creation + submission1 = 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 + submission2 = Submission.find_by_sql(" select * from submissions + where exercise_id = #{exercise_id} AND + user_id = #{user_id} AND + '#{created_at}' > created_at + order by created_at desc + limit 1").first + submission3 = Submission.find_by_sql(" select * from submissions + where exercise_id = #{exercise_id} AND + user_id = #{user_id} AND + '#{created_at.strftime('%Y-%m-%d %H:%M:%S.%N')}' > created_at + order by created_at desc + limit 1").first + submission4 = Submission.find_by_sql(" select * from submissions + where exercise_id = #{exercise_id} AND + user_id = #{user_id} AND + '#{created_at.localtime.strftime('%Y-%m-%d %H:%M:%S.%N')}' > created_at + order by created_at desc + limit 1").first + binding.pry + submission1 + end + def comments_count submission.files.map { |file| file.comments.size}.sum end diff --git a/app/models/submission.rb b/app/models/submission.rb index 323f1d58..28e98555 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -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 diff --git a/app/views/exercises/_editor_frame.html.slim b/app/views/exercises/_editor_frame.html.slim index eacc62a9..01640fa8 100644 --- a/app/views/exercises/_editor_frame.html.slim +++ b/app/views/exercises/_editor_frame.html.slim @@ -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') \ 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 6ef176bd..8e52150c 100644 --- a/app/views/request_for_comments/show.html.erb +++ b/app/views/request_for_comments/show.html.erb @@ -3,19 +3,12 @@

<% 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.last_submission_before_creation %> - <%= user.displayname %> | <%= @request_for_comment.requested_at %> + <%= user.displayname %> | <%= @request_for_comment.requested_at.localtime %>

- <%= t('activerecord.attributes.exercise.instructions') %>: "<%= @request_for_comment.exercise.description %>" + <%= t('activerecord.attributes.exercise.description') %>: "<%= render_markdown(@request_for_comment.exercise.description) %>"
diff --git a/db/schema.rb b/db/schema.rb index 67b3b35c..287b5bd5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160624130951) do +ActiveRecord::Schema.define(version: 20160630154310) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -191,6 +191,7 @@ ActiveRecord::Schema.define(version: 20160624130951) do t.string "user_type" t.text "question" t.boolean "solved" + t.integer "submission_id" end create_table "submissions", force: true do |t| From 59536ab189102771eba68578cf56332e868f5e52 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Mon, 4 Jul 2016 17:46:37 +0200 Subject: [PATCH 22/34] the migrations to the changes in schema.rb. --- .../20160630154310_add_submission_to_request_for_comments.rb | 5 +++++ ...01092140_remove_requested_at_from_request_for_comments.rb | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 db/migrate/20160630154310_add_submission_to_request_for_comments.rb create mode 100644 db/migrate/20160701092140_remove_requested_at_from_request_for_comments.rb diff --git a/db/migrate/20160630154310_add_submission_to_request_for_comments.rb b/db/migrate/20160630154310_add_submission_to_request_for_comments.rb new file mode 100644 index 00000000..d7d13e67 --- /dev/null +++ b/db/migrate/20160630154310_add_submission_to_request_for_comments.rb @@ -0,0 +1,5 @@ +class AddSubmissionToRequestForComments < ActiveRecord::Migration + def change + add_reference :request_for_comments, :submission + end +end diff --git a/db/migrate/20160701092140_remove_requested_at_from_request_for_comments.rb b/db/migrate/20160701092140_remove_requested_at_from_request_for_comments.rb new file mode 100644 index 00000000..bb5611f6 --- /dev/null +++ b/db/migrate/20160701092140_remove_requested_at_from_request_for_comments.rb @@ -0,0 +1,5 @@ +class RemoveRequestedAtFromRequestForComments < ActiveRecord::Migration + def change + remove_column :request_for_comments, :requested_at + end +end From eb1fc76a32c15d2e95aaf688b2946fd1427c530e Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Mon, 4 Jul 2016 17:47:49 +0200 Subject: [PATCH 23/34] removed flowr hints for normal stdout --- app/assets/javascripts/editor.js.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index 1e64195e..48cfaa62 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -587,7 +587,7 @@ $(function() { } else if (output.stdout) { //if (output_mode_is_streaming){ element.addClass('text-success').append(output.stdout); - flowrOutputBuffer += output.stdout; + // flowrOutputBuffer += output.stdout; //}else{ // element.addClass('text-success'); // element.data('content_buffer' , element.data('content_buffer') + output.stdout); From 4651c87491b29d327443306cc490a264f8bb255a Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Mon, 4 Jul 2016 17:48:52 +0200 Subject: [PATCH 24/34] Testrun output is now shown when hovering over the status icons (round red or green circles) --- app/views/exercises/external_users/statistics.html.slim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim index 56bd614b..3c755be6 100644 --- a/app/views/exercises/external_users/statistics.html.slim +++ b/app/views/exercises/external_users/statistics.html.slim @@ -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') From 16206cf58f019f963f9b7df766204d366bd5d1be Mon Sep 17 00:00:00 2001 From: yqbk Date: Mon, 4 Jul 2016 17:56:57 +0200 Subject: [PATCH 25/34] restore one line --- db/schema.rb | 2 +- spec/policies/exercise_policy_spec.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index 2f50d81f..d304b1d5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160624130951) do +ActiveRecord::Schema.define(version: 20160704143402) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/spec/policies/exercise_policy_spec.rb b/spec/policies/exercise_policy_spec.rb index 1b25fbca..7b5aeabf 100644 --- a/spec/policies/exercise_policy_spec.rb +++ b/spec/policies/exercise_policy_spec.rb @@ -3,6 +3,8 @@ require 'rails_helper' describe ExercisePolicy do subject { described_class } +let(:exercise) { FactoryGirl.build(:dummy) } + permissions :batch_update? do it 'grants access to admins only' do expect(subject).to permit(FactoryGirl.build(:admin), exercise) From 4bb8c79150a7e5e0cf7ca14beba0bacdd2b8e629 Mon Sep 17 00:00:00 2001 From: yqbk Date: Tue, 5 Jul 2016 15:27:49 +0200 Subject: [PATCH 26/34] typo in submissionS --- app/models/exercise.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/exercise.rb b/app/models/exercise.rb index 24570dc6..5b7ff9b2 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -10,7 +10,7 @@ class Exercise < ActiveRecord::Base after_initialize :set_default_values belongs_to :execution_environment - has_many :submission + has_many :submissions has_many :external_users, source: :user, source_type: ExternalUser, through: :submissions has_many :internal_users, source: :user, source_type: InternalUser, through: :submissions From 605042d2cfc43c395d07685a39d0fa7a8bd19129 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Thu, 7 Jul 2016 14:27:00 +0200 Subject: [PATCH 27/34] cleanup of code for linking stacktraces as suggested by tom. --- app/assets/javascripts/editor.js.erb | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index 2dc790e0..f9900eec 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -20,12 +20,7 @@ $(function() { var editors = []; var editor_for_file = new Map(); - - var tracepositions_regexes = { - python: /File "(.+?)", line (\d+)/g, - java: /(.*\.java):(\d):/g - }; - + var regex_for_language = new Map(); var tracepositions_regex; var active_file = undefined; @@ -422,12 +417,7 @@ $(function() { // set regex for parsing error traces based on the mode of the main file. if( $(element).parent().data('role') == "main_file"){ - var mode = $(element).data('mode'); - if(mode == "ace/mode/python"){ - tracepositions_regex = tracepositions_regexes.python; - } else if (mode = "ace/mode/java"){ - tracepositions_regex = tracepositions_regexes.java; - } + tracepositions_regex = regex_for_language.get($(element).data('mode')); } var file_id = $(element).data('id'); @@ -477,6 +467,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(); }; @@ -1265,6 +1261,7 @@ $(function() { if ($('#editor').isPresent()) { if (isBrowserSupported()) { + initializeRegexes(); initializeCodePilot(); $('.score, #development-environment').show(); configureEditors(); From f80d1fc7fe5b33b6a00aa90b733bc0637011daea Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Thu, 7 Jul 2016 15:22:20 +0200 Subject: [PATCH 28/34] fixed regex for java... --- app/assets/javascripts/editor.js.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index f9900eec..2a526aff 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -470,7 +470,7 @@ $(function() { var initializeRegexes = function(){ regex_for_language.set("ace/mode/python", /File "(.+?)", line (\d+)/g); - regex_for_language.set("ace/mode/java", /(.*\.java):(\d):/g); + regex_for_language.set("ace/mode/java", /(.*\.java):(\d+):/g); } var initializeTooltips = function() { From 3bf03a418bec70fd8eb59590a36de2f056a80489 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Sun, 10 Jul 2016 22:00:42 +0200 Subject: [PATCH 29/34] fix policy scope statement for internal users --- app/policies/exercise_policy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/policies/exercise_policy.rb b/app/policies/exercise_policy.rb index 114e7d6e..55f7d16b 100644 --- a/app/policies/exercise_policy.rb +++ b/app/policies/exercise_policy.rb @@ -25,7 +25,7 @@ class ExercisePolicy < AdminOrAuthorPolicy if @user.admin? @scope.all elsif @user.internal_user? - @scope.where('user_id = ? OR public = TRUE', @user.id, @user.id) + @scope.where('user_id = ? OR public = TRUE', @user.id) else @scope.none end From f5868a4fa24db189eceb2db5defa04e99c11eedd Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Thu, 14 Jul 2016 13:22:24 +0200 Subject: [PATCH 30/34] merge master --- app/assets/javascripts/editor.js.erb | 59 +++++++++++- app/controllers/exercises_controller.rb | 8 +- app/controllers/teams_controller.rb | 51 ---------- app/models/exercise.rb | 1 - app/models/internal_user.rb | 2 - app/models/team.rb | 10 -- app/policies/exercise_policy.rb | 9 +- app/policies/team_policy.rb | 14 --- app/views/application/_navigation.html.slim | 2 +- app/views/exercises/_form.html.slim | 3 - .../external_users/statistics.html.slim | 4 +- app/views/exercises/show.html.slim | 1 - app/views/teams/_form.html.slim | 9 -- app/views/teams/edit.html.slim | 3 - app/views/teams/index.html.slim | 20 ---- app/views/teams/new.html.slim | 3 - app/views/teams/show.html.slim | 9 -- config/locales/de.yml | 8 -- config/locales/en.yml | 8 -- config/routes.rb | 1 - db/migrate/20160704143402_remove_teams.rb | 7 ++ db/schema.rb | 17 +--- db/seeds/development.rb | 3 - spec/controllers/teams_controller_spec.rb | 93 ------------------- spec/factories/team.rb | 6 -- spec/features/authorization_spec.rb | 6 +- spec/models/team_spec.rb | 9 -- spec/policies/exercise_policy_spec.rb | 22 +---- spec/policies/team_policy_spec.rb | 41 -------- 29 files changed, 78 insertions(+), 351 deletions(-) delete mode 100644 app/controllers/teams_controller.rb delete mode 100644 app/models/team.rb delete mode 100644 app/policies/team_policy.rb delete mode 100644 app/views/teams/_form.html.slim delete mode 100644 app/views/teams/edit.html.slim delete mode 100644 app/views/teams/index.html.slim delete mode 100644 app/views/teams/new.html.slim delete mode 100644 app/views/teams/show.html.slim create mode 100644 db/migrate/20160704143402_remove_teams.rb delete mode 100644 spec/controllers/teams_controller_spec.rb delete mode 100644 spec/factories/team.rb delete mode 100644 spec/models/team_spec.rb delete mode 100644 spec/policies/team_policy_spec.rb diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index b706b437..480f6580 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -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; @@ -404,12 +408,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'); /* @@ -457,6 +467,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(); }; @@ -587,7 +603,7 @@ $(function() { } else if (output.stdout) { //if (output_mode_is_streaming){ element.addClass('text-success').append(output.stdout); - flowrOutputBuffer += output.stdout; + // flowrOutputBuffer += output.stdout; //}else{ // element.addClass('text-success'); // element.data('content_buffer' , element.data('content_buffer') + output.stdout); @@ -876,7 +892,9 @@ $(function() { } var showWorkspaceTab = function(event) { - event.preventDefault(); + if(event){ + event.preventDefault(); + } showTab(0); }; @@ -1036,6 +1054,7 @@ $(function() { case 'exit': killWebsocketAndContainer(); 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. @@ -1047,6 +1066,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], "" + matches[0] + "")); + } + } + } + + }; + var renderWebsocketOutput = function(msg){ var element = findOrCreateRenderElement(0); element.append(msg.data); @@ -1208,6 +1262,7 @@ $(function() { if ($('#editor').isPresent()) { if (isBrowserSupported()) { + initializeRegexes(); initializeCodePilot(); $('.score, #development-environment').show(); configureEditors(); diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index 0304abb4..45fd04d9 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -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 diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb deleted file mode 100644 index 1ca8f1fe..00000000 --- a/app/controllers/teams_controller.rb +++ /dev/null @@ -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 diff --git a/app/models/exercise.rb b/app/models/exercise.rb index 4a1e0486..5b7ff9b2 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -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 diff --git a/app/models/internal_user.rb b/app/models/internal_user.rb index e5cebde9..8f1bf04b 100644 --- a/app/models/internal_user.rb +++ b/app/models/internal_user.rb @@ -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} diff --git a/app/models/team.rb b/app/models/team.rb deleted file mode 100644 index a0dcb8d6..00000000 --- a/app/models/team.rb +++ /dev/null @@ -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 diff --git a/app/policies/exercise_policy.rb b/app/policies/exercise_policy.rb index 29a1c570..55f7d16b 100644 --- a/app/policies/exercise_policy.rb +++ b/app/policies/exercise_policy.rb @@ -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 diff --git a/app/policies/team_policy.rb b/app/policies/team_policy.rb deleted file mode 100644 index ff05c0c3..00000000 --- a/app/policies/team_policy.rb +++ /dev/null @@ -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 diff --git a/app/views/application/_navigation.html.slim b/app/views/application/_navigation.html.slim index 4ab39e30..3c260cb8 100644 --- a/app/views/application/_navigation.html.slim +++ b/app/views/application/_navigation.html.slim @@ -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, 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")) diff --git a/app/views/exercises/_form.html.slim b/app/views/exercises/_form.html.slim index f0f69f7a..968540af 100644 --- a/app/views/exercises/_form.html.slim +++ b/app/views/exercises/_form.html.slim @@ -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) diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim index 56bd614b..3c755be6 100644 --- a/app/views/exercises/external_users/statistics.html.slim +++ b/app/views/exercises/external_users/statistics.html.slim @@ -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') diff --git a/app/views/exercises/show.html.slim b/app/views/exercises/show.html.slim index b4b72932..5c554da8 100644 --- a/app/views/exercises/show.html.slim +++ b/app/views/exercises/show.html.slim @@ -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?) diff --git a/app/views/teams/_form.html.slim b/app/views/teams/_form.html.slim deleted file mode 100644 index 47f547dd..00000000 --- a/app/views/teams/_form.html.slim +++ /dev/null @@ -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) diff --git a/app/views/teams/edit.html.slim b/app/views/teams/edit.html.slim deleted file mode 100644 index 0c1f4dac..00000000 --- a/app/views/teams/edit.html.slim +++ /dev/null @@ -1,3 +0,0 @@ -h1 = @hint - -= render('form') diff --git a/app/views/teams/index.html.slim b/app/views/teams/index.html.slim deleted file mode 100644 index 4eb6640b..00000000 --- a/app/views/teams/index.html.slim +++ /dev/null @@ -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) diff --git a/app/views/teams/new.html.slim b/app/views/teams/new.html.slim deleted file mode 100644 index 8c8f2aab..00000000 --- a/app/views/teams/new.html.slim +++ /dev/null @@ -1,3 +0,0 @@ -h1 = t('shared.new_model', model: Team.model_name.human) - -= render('form') diff --git a/app/views/teams/show.html.slim b/app/views/teams/show.html.slim deleted file mode 100644 index 1b37d931..00000000 --- a/app/views/teams/show.html.slim +++ /dev/null @@ -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) diff --git a/config/locales/de.yml b/config/locales/de.yml index 53df1858..ce8ba98f 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -34,8 +34,6 @@ de: instructions: Anweisungen maximum_score: Erreichbare Punktzahl public: Öffentlich - team: Team - team_id: Team title: Titel user: Autor allow_file_creation: "Dateierstellung erlauben" @@ -91,9 +89,6 @@ de: files: Dateien score: Punktzahl user: Autor - team: - internal_user_ids: Mitglieder - name: Name models: code_harbor_link: one: CodeHarbor-Link @@ -128,9 +123,6 @@ de: submission: one: Abgabe other: Abgaben - team: - one: Team - other: Teams errors: messages: together: 'muss zusammen mit %{attribute} definiert werden' diff --git a/config/locales/en.yml b/config/locales/en.yml index 1e72cb34..86acbb15 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -34,8 +34,6 @@ en: instructions: Instructions maximum_score: Maximum Score public: Public - team: Team - team_id: Team title: Title user: Author allow_file_creation: "Allow file creation" @@ -91,9 +89,6 @@ en: files: Files score: Score user: Author - team: - internal_user_ids: Members - name: Name models: code_harbor_link: one: CodeHarbor Link @@ -128,9 +123,6 @@ en: submission: one: Submission other: Submissions - team: - one: Team - other: Teams errors: messages: together: 'has to be set along with %{attribute}' diff --git a/config/routes.rb b/config/routes.rb index a6ca545c..f13d1c9f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -99,5 +99,4 @@ Rails.application.routes.draw do end end - resources :teams end diff --git a/db/migrate/20160704143402_remove_teams.rb b/db/migrate/20160704143402_remove_teams.rb new file mode 100644 index 00000000..20b8a204 --- /dev/null +++ b/db/migrate/20160704143402_remove_teams.rb @@ -0,0 +1,7 @@ +class RemoveTeams < ActiveRecord::Migration + def change + remove_reference :exercises, :team + drop_table :teams + drop_table :internal_users_teams + end +end diff --git a/db/schema.rb b/db/schema.rb index 287b5bd5..5cad22c6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160630154310) do +ActiveRecord::Schema.define(version: 20160704143402) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -87,7 +87,6 @@ ActiveRecord::Schema.define(version: 20160630154310) do t.boolean "public" t.string "user_type" t.string "token" - t.integer "team_id" t.boolean "hide_file_tree" t.boolean "allow_file_creation" end @@ -173,14 +172,6 @@ ActiveRecord::Schema.define(version: 20160630154310) do add_index "internal_users", ["remember_me_token"], name: "index_internal_users_on_remember_me_token", using: :btree add_index "internal_users", ["reset_password_token"], name: "index_internal_users_on_reset_password_token", using: :btree - create_table "internal_users_teams", force: true do |t| - t.integer "internal_user_id" - t.integer "team_id" - end - - add_index "internal_users_teams", ["internal_user_id"], name: "index_internal_users_teams_on_internal_user_id", using: :btree - add_index "internal_users_teams", ["team_id"], name: "index_internal_users_teams_on_team_id", using: :btree - create_table "request_for_comments", force: true do |t| t.integer "user_id", null: false t.integer "exercise_id", null: false @@ -204,12 +195,6 @@ ActiveRecord::Schema.define(version: 20160630154310) do t.string "user_type" end - create_table "teams", force: true do |t| - t.string "name" - t.datetime "created_at" - t.datetime "updated_at" - end - create_table "testruns", force: true do |t| t.boolean "passed" t.text "output" diff --git a/db/seeds/development.rb b/db/seeds/development.rb index 74783d39..6fc8e050 100644 --- a/db/seeds/development.rb +++ b/db/seeds/development.rb @@ -22,6 +22,3 @@ Hint.create_factories # submissions FactoryGirl.create(:submission, exercise: @exercises[:fibonacci]) - -# teams -FactoryGirl.create(:team, internal_users: InternalUser.limit(10)) diff --git a/spec/controllers/teams_controller_spec.rb b/spec/controllers/teams_controller_spec.rb deleted file mode 100644 index 78dfc60d..00000000 --- a/spec/controllers/teams_controller_spec.rb +++ /dev/null @@ -1,93 +0,0 @@ -require 'rails_helper' - -describe TeamsController do - let(:team) { FactoryGirl.create(:team) } - let(:user) { FactoryGirl.create(:admin) } - before(:each) { allow(controller).to receive(:current_user).and_return(user) } - - describe 'POST #create' do - context 'with a valid team' do - let(:request) { proc { post :create, team: FactoryGirl.attributes_for(:team) } } - before(:each) { request.call } - - expect_assigns(team: Team) - - it 'creates the team' do - expect { request.call }.to change(Team, :count).by(1) - end - - expect_redirect(Team.last) - end - - context 'with an invalid team' do - before(:each) { post :create, team: {} } - - expect_assigns(team: Team) - expect_status(200) - expect_template(:new) - end - end - - describe 'DELETE #destroy' do - before(:each) { delete :destroy, id: team.id } - - expect_assigns(team: Team) - - it 'destroys the team' do - team = FactoryGirl.create(:team) - expect { delete :destroy, id: team.id }.to change(Team, :count).by(-1) - end - - expect_redirect(:teams) - end - - describe 'GET #edit' do - before(:each) { get :edit, id: team.id } - - expect_assigns(team: Team) - expect_status(200) - expect_template(:edit) - end - - describe 'GET #index' do - before(:all) { FactoryGirl.create_pair(:team) } - before(:each) { get :index } - - expect_assigns(teams: Team.all) - expect_status(200) - expect_template(:index) - end - - describe 'GET #new' do - before(:each) { get :new } - - expect_assigns(team: Team) - expect_status(200) - expect_template(:new) - end - - describe 'GET #show' do - before(:each) { get :show, id: team.id } - - expect_assigns(team: :team) - expect_status(200) - expect_template(:show) - end - - describe 'PUT #update' do - context 'with a valid team' do - before(:each) { put :update, team: FactoryGirl.attributes_for(:team), id: team.id } - - expect_assigns(team: Team) - expect_redirect(:team) - end - - context 'with an invalid team' do - before(:each) { put :update, team: {name: ''}, id: team.id } - - expect_assigns(team: Team) - expect_status(200) - expect_template(:edit) - end - end -end diff --git a/spec/factories/team.rb b/spec/factories/team.rb deleted file mode 100644 index f34d323e..00000000 --- a/spec/factories/team.rb +++ /dev/null @@ -1,6 +0,0 @@ -FactoryGirl.define do - factory :team do - internal_users { build_pair :teacher } - name 'The A-Team' - end -end diff --git a/spec/features/authorization_spec.rb b/spec/features/authorization_spec.rb index 6a7eaad0..7f0ff04f 100644 --- a/spec/features/authorization_spec.rb +++ b/spec/features/authorization_spec.rb @@ -5,7 +5,7 @@ describe 'Authorization' do let(:user) { FactoryGirl.create(:admin) } before(:each) { allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) } - [Consumer, ExecutionEnvironment, Exercise, FileType, InternalUser, Team].each do |model| + [Consumer, ExecutionEnvironment, Exercise, FileType, InternalUser].each do |model| expect_permitted_path(:"new_#{model.model_name.singular}_path") end end @@ -14,7 +14,7 @@ describe 'Authorization' do let(:user) { FactoryGirl.create(:external_user) } before(:each) { allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) } - [Consumer, ExecutionEnvironment, Exercise, FileType, InternalUser, Team].each do |model| + [Consumer, ExecutionEnvironment, Exercise, FileType, InternalUser].each do |model| expect_forbidden_path(:"new_#{model.model_name.singular}_path") end end @@ -27,7 +27,7 @@ describe 'Authorization' do expect_forbidden_path(:"new_#{model.model_name.singular}_path") end - [ExecutionEnvironment, Exercise, FileType, Team].each do |model| + [ExecutionEnvironment, Exercise, FileType].each do |model| expect_permitted_path(:"new_#{model.model_name.singular}_path") end end diff --git a/spec/models/team_spec.rb b/spec/models/team_spec.rb deleted file mode 100644 index 777efd32..00000000 --- a/spec/models/team_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'rails_helper' - -describe Team do - let(:team) { described_class.create } - - it 'validates the presence of a name' do - expect(team.errors[:name]).to be_present - end -end diff --git a/spec/policies/exercise_policy_spec.rb b/spec/policies/exercise_policy_spec.rb index c9762f9e..7b5aeabf 100644 --- a/spec/policies/exercise_policy_spec.rb +++ b/spec/policies/exercise_policy_spec.rb @@ -3,8 +3,8 @@ require 'rails_helper' describe ExercisePolicy do subject { described_class } - let(:exercise) { FactoryGirl.build(:dummy, team: FactoryGirl.create(:team)) } - +let(:exercise) { FactoryGirl.build(:dummy) } + permissions :batch_update? do it 'grants access to admins only' do expect(subject).to permit(FactoryGirl.build(:admin), exercise) @@ -40,10 +40,6 @@ describe ExercisePolicy do expect(subject).to permit(exercise.author, exercise) end - it 'grants access to team members' do - expect(subject).to permit(exercise.team.members.first, exercise) - end - it 'does not grant access to all other users' do [:external_user, :teacher].each do |factory_name| expect(subject).not_to permit(FactoryGirl.build(factory_name), exercise) @@ -71,9 +67,7 @@ describe ExercisePolicy do [@admin, @teacher].each do |user| [true, false].each do |public| - [@team, nil].each do |team| - FactoryGirl.create(:dummy, public: public, team: team, user_id: user.id, user_type: InternalUser.class.name) - end + FactoryGirl.create(:dummy, public: public, user_id: user.id, user_type: InternalUser.class.name) end end end @@ -95,10 +89,6 @@ describe ExercisePolicy do end context 'for teachers' do - before(:each) do - @team = FactoryGirl.create(:team) - @team.members << @teacher - end let(:scope) { Pundit.policy_scope!(@teacher, Exercise) } @@ -110,12 +100,8 @@ describe ExercisePolicy do expect(scope.map(&:id)).to include(*Exercise.where(public: false, user_id: @teacher.id).map(&:id)) end - it "includes all of team members' non-public exercises" do - expect(scope.map(&:id)).to include(*Exercise.where(public: false, team_id: @teacher.teams.first.id).map(&:id)) - end - it "does not include other authors' non-public exercises" do - expect(scope.map(&:id)).not_to include(*Exercise.where(public: false).where("team_id <> #{@team.id} AND user_id <> #{@teacher.id}").map(&:id)) + expect(scope.map(&:id)).not_to include(*Exercise.where(public: false).where(user_id <> #{@teacher.id}").map(&:id)) end end end diff --git a/spec/policies/team_policy_spec.rb b/spec/policies/team_policy_spec.rb deleted file mode 100644 index aa3ba1d8..00000000 --- a/spec/policies/team_policy_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'rails_helper' - -describe TeamPolicy do - subject { described_class } - - let(:team) { FactoryGirl.build(:team) } - - [:create?, :index?, :new?].each do |action| - permissions(action) do - it 'grants access to admins' do - expect(subject).to permit(FactoryGirl.build(:admin), team) - end - - it 'grants access to teachers' do - expect(subject).to permit(FactoryGirl.build(:teacher), team) - end - - it 'does not grant access to external users' do - expect(subject).not_to permit(FactoryGirl.build(:external_user), team) - end - end - end - - [:destroy?, :edit?, :show?, :update?].each do |action| - permissions(action) do - it 'grants access to admins' do - expect(subject).to permit(FactoryGirl.build(:admin), team) - end - - it 'grants access to members' do - expect(subject).to permit(team.members.last, team) - end - - it 'does not grant access to all other users' do - [:external_user, :teacher].each do |factory_name| - expect(subject).not_to permit(FactoryGirl.build(factory_name), team) - end - end - end - end -end From 8282820974620ad9fe3634632dacf816a07fcd5b Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Thu, 14 Jul 2016 16:22:22 +0200 Subject: [PATCH 31/34] removed requested_at partly using submission_id some cleanup --- app/assets/javascripts/editor.js.erb | 1 - app/models/request_for_comment.rb | 28 ++++--------------- .../request_for_comments/index.html.slim | 2 +- app/views/request_for_comments/show.html.erb | 4 +-- db/schema.rb | 7 ++--- 5 files changed, 12 insertions(+), 30 deletions(-) diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index 480f6580..658ff3cd 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -1215,7 +1215,6 @@ $(function() { var question = $('#question').val(); var createRequestForComments = function(submission) { - console.log(submission); $.ajax({ method: 'POST', url: '/request_for_comments', diff --git a/app/models/request_for_comment.rb b/app/models/request_for_comment.rb index 1343f5b3..cd57c5c2 100644 --- a/app/models/request_for_comment.rb +++ b/app/models/request_for_comment.rb @@ -17,6 +17,8 @@ class RequestForComment < ActiveRecord::Base 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,33 +27,15 @@ 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 - submission1 = Submission.find_by_sql(" select * from submissions + 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 - submission2 = Submission.find_by_sql(" select * from submissions - where exercise_id = #{exercise_id} AND - user_id = #{user_id} AND - '#{created_at}' > created_at - order by created_at desc - limit 1").first - submission3 = Submission.find_by_sql(" select * from submissions - where exercise_id = #{exercise_id} AND - user_id = #{user_id} AND - '#{created_at.strftime('%Y-%m-%d %H:%M:%S.%N')}' > created_at - order by created_at desc - limit 1").first - submission4 = Submission.find_by_sql(" select * from submissions - where exercise_id = #{exercise_id} AND - user_id = #{user_id} AND - '#{created_at.localtime.strftime('%Y-%m-%d %H:%M:%S.%N')}' > created_at - order by created_at desc - limit 1").first - binding.pry - submission1 end def comments_count @@ -64,6 +48,6 @@ class RequestForComment < ActiveRecord::Base private def self.row_number_user_sql - select("id, user_id, exercise_id, file_id, question, requested_at, created_at, updated_at, user_type, solved, 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 diff --git a/app/views/request_for_comments/index.html.slim b/app/views/request_for_comments/index.html.slim index 3bdbe6d0..b7ada0a2 100644 --- a/app/views/request_for_comments/index.html.slim +++ b/app/views/request_for_comments/index.html.slim @@ -27,6 +27,6 @@ h1 = RequestForComment.model_name.human(count: 2) 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) \ 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 8e52150c..c1d71672 100644 --- a/app/views/request_for_comments/show.html.erb +++ b/app/views/request_for_comments/show.html.erb @@ -1,11 +1,11 @@
-

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

+

<%= link_to(@request_for_comment.exercise.title, [:implement, @request_for_comment.exercise]) %>

<% user = @request_for_comment.user submission = @request_for_comment.last_submission_before_creation %> - <%= user.displayname %> | <%= @request_for_comment.requested_at.localtime %> + <%= user.displayname %> | <%= @request_for_comment.created_at.localtime %>

<%= t('activerecord.attributes.exercise.description') %>: "<%= render_markdown(@request_for_comment.exercise.description) %>" diff --git a/db/schema.rb b/db/schema.rb index 5cad22c6..79d13498 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -173,10 +173,9 @@ ActiveRecord::Schema.define(version: 20160704143402) do add_index "internal_users", ["reset_password_token"], name: "index_internal_users_on_reset_password_token", using: :btree create_table "request_for_comments", force: true do |t| - t.integer "user_id", null: false - t.integer "exercise_id", null: false - t.integer "file_id", null: false - t.datetime "requested_at" + t.integer "user_id", null: false + t.integer "exercise_id", null: false + t.integer "file_id", null: false t.datetime "created_at" t.datetime "updated_at" t.string "user_type" From 39a253997d3d578b3055d232be231e49789f2204 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Thu, 14 Jul 2016 16:59:42 +0200 Subject: [PATCH 32/34] use submission from model --- app/models/request_for_comment.rb | 5 +---- app/views/request_for_comments/show.html.erb | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/models/request_for_comment.rb b/app/models/request_for_comment.rb index cd57c5c2..63d932fc 100644 --- a/app/models/request_for_comment.rb +++ b/app/models/request_for_comment.rb @@ -1,5 +1,6 @@ class RequestForComment < ActiveRecord::Base include Creation + belongs_to :submission belongs_to :exercise belongs_to :file, class_name: 'CodeOcean::File' @@ -13,10 +14,6 @@ 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 diff --git a/app/views/request_for_comments/show.html.erb b/app/views/request_for_comments/show.html.erb index c1d71672..4089d878 100644 --- a/app/views/request_for_comments/show.html.erb +++ b/app/views/request_for_comments/show.html.erb @@ -3,7 +3,7 @@

<% user = @request_for_comment.user - submission = @request_for_comment.last_submission_before_creation + submission = @request_for_comment.submission %> <%= user.displayname %> | <%= @request_for_comment.created_at.localtime %>

From 09533a55562bea87e8a9056ce620a1853857163e Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Fri, 15 Jul 2016 18:25:58 +0200 Subject: [PATCH 33/34] download complete submission as one zip file --- Gemfile | 1 + Gemfile.lock | 1 + app/assets/javascripts/editor.js.erb | 5 ++++- app/controllers/submissions_controller.rb | 18 ++++++++++++++++-- app/models/submission.rb | 6 +++++- app/policies/submission_policy.rb | 2 +- app/views/submissions/show.json.jbuilder | 2 +- config/routes.rb | 3 ++- 8 files changed, 31 insertions(+), 7 deletions(-) diff --git a/Gemfile b/Gemfile index abdca69c..8cd13375 100644 --- a/Gemfile +++ b/Gemfile @@ -38,6 +38,7 @@ gem 'faye-websocket' gem 'nokogiri' gem 'd3-rails' gem 'rest-client' +gem 'rubyzip' group :development do gem 'better_errors', platform: :ruby diff --git a/Gemfile.lock b/Gemfile.lock index 9f115474..e440fd0b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -397,6 +397,7 @@ DEPENDENCIES rubocop rubocop-rspec rubytree + rubyzip sass-rails (~> 4.0.3) sdoc (~> 0.4.0) selenium-webdriver diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index 658ff3cd..a8297cfe 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -179,7 +179,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; }); }; diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 4f9d6a5b..a59c17cc 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -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) diff --git a/app/models/submission.rb b/app/models/submission.rb index 28e98555..5a95587f 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -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 diff --git a/app/policies/submission_policy.rb b/app/policies/submission_policy.rb index 18d39f4c..861f5695 100644 --- a/app/policies/submission_policy.rb +++ b/app/policies/submission_policy.rb @@ -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 diff --git a/app/views/submissions/show.json.jbuilder b/app/views/submissions/show.json.jbuilder index f137f874..3b860684 100644 --- a/app/views/submissions/show.json.jbuilder +++ b/app/views/submissions/show.json.jbuilder @@ -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 diff --git a/config/routes.rb b/config/routes.rb index f13d1c9f..b9f33826 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -89,7 +89,8 @@ Rails.application.routes.draw do resources :submissions, only: [:create, :index, :show] do member do - get 'download/:filename', as: :download, constraints: {filename: FILENAME_REGEXP}, to: :download_file + get 'download', as: :download, to: :download + get 'download/:filename', as: :download_file, constraints: {filename: FILENAME_REGEXP}, to: :download_file get 'render/:filename', as: :render, constraints: {filename: FILENAME_REGEXP}, to: :render_file get 'run/:filename', as: :run, constraints: {filename: FILENAME_REGEXP}, to: :run get :score From 79309f6697725f67521ce2cf8f56d4ebbd53e7a1 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Thu, 28 Jul 2016 15:49:23 +0200 Subject: [PATCH 34/34] prevent printing out "undefined" if no container could be fetched and thus the response has no stdout or stderr. --- app/assets/javascripts/editor.js.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index d173198f..2d73bbf0 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -604,11 +604,11 @@ $(function() { // output_mode_is_streaming = false; //} if (!colorize) { - if(output.stdout != ''){ + if(output.stdout != undefined && output.stdout != ''){ element.append(output.stdout) } - if(output.stderr != ''){ + if(output.stderr != undefined && output.stderr != ''){ element.append('There was an error: StdErr: ' + output.stderr); }