diff --git a/app/assets/javascripts/error_templates.js b/app/assets/javascripts/error_templates.js new file mode 100644 index 00000000..1b9b5234 --- /dev/null +++ b/app/assets/javascripts/error_templates.js @@ -0,0 +1,18 @@ +$(function() { + if ($.isController('error_templates')) { + $('#add-attribute').find('button').on('click', function () { + $.ajax(location + '/attribute.json', { + method: 'POST', + data: { + _method: 'PUT', + dataType: 'json', + error_template_attribute_id: $('#add-attribute').find('select').val() + } + }).success(function () { + location.reload(); + }).error(function (error) { + $.flash.danger({text: error.statusText}); + }); + }); + } +}); \ No newline at end of file diff --git a/app/assets/stylesheets/error_templates.scss b/app/assets/stylesheets/error_templates.scss new file mode 100644 index 00000000..29b2f96c --- /dev/null +++ b/app/assets/stylesheets/error_templates.scss @@ -0,0 +1,9 @@ +#add-attribute { + display: flex; + max-width: 400px; + margin-top: 30px; + + button { + margin-left: 10px; + } +} \ No newline at end of file diff --git a/app/controllers/concerns/submission_scoring.rb b/app/controllers/concerns/submission_scoring.rb index 8891332a..35de4ba5 100644 --- a/app/controllers/concerns/submission_scoring.rb +++ b/app/controllers/concerns/submission_scoring.rb @@ -9,6 +9,14 @@ module SubmissionScoring assessment = assessor.assess(output) passed = ((assessment[:passed] == assessment[:count]) and (assessment[:score] > 0)) testrun_output = passed ? nil : 'message: ' + output[:message].to_s + "\n stdout: " + output[:stdout].to_s + "\n stderr: " + output[:stderr].to_s + if !testrun_output.blank? + submission.exercise.execution_environment.error_templates.each do |template| + pattern = Regexp.new(template.signature).freeze + if pattern.match(testrun_output) + StructuredError.create_from_template(template, testrun_output) + end + end + end Testrun.new(submission: submission, cause: 'assess', file: file, passed: passed, output: testrun_output).save output.merge!(assessment) output.merge!(filename: file.name_with_extension, message: feedback_message(file, output[:score]), weight: file.weight) diff --git a/app/controllers/error_template_attributes_controller.rb b/app/controllers/error_template_attributes_controller.rb new file mode 100644 index 00000000..05de2772 --- /dev/null +++ b/app/controllers/error_template_attributes_controller.rb @@ -0,0 +1,86 @@ +class ErrorTemplateAttributesController < ApplicationController + before_action :set_error_template_attribute, only: [:show, :edit, :update, :destroy] + + def authorize! + authorize(@error_template_attributes || @error_template_attribute) + end + private :authorize! + + # GET /error_template_attributes + # GET /error_template_attributes.json + def index + @error_template_attributes = ErrorTemplateAttribute.all.order('important DESC', :key, :id).paginate(page: params[:page]) + authorize! + end + + # GET /error_template_attributes/1 + # GET /error_template_attributes/1.json + def show + authorize! + end + + # GET /error_template_attributes/new + def new + @error_template_attribute = ErrorTemplateAttribute.new + authorize! + end + + # GET /error_template_attributes/1/edit + def edit + authorize! + end + + # POST /error_template_attributes + # POST /error_template_attributes.json + def create + @error_template_attribute = ErrorTemplateAttribute.new(error_template_attribute_params) + authorize! + + respond_to do |format| + if @error_template_attribute.save + format.html { redirect_to @error_template_attribute, notice: 'Error template attribute was successfully created.' } + format.json { render :show, status: :created, location: @error_template_attribute } + else + format.html { render :new } + format.json { render json: @error_template_attribute.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /error_template_attributes/1 + # PATCH/PUT /error_template_attributes/1.json + def update + authorize! + respond_to do |format| + if @error_template_attribute.update(error_template_attribute_params) + format.html { redirect_to @error_template_attribute, notice: 'Error template attribute was successfully updated.' } + format.json { render :show, status: :ok, location: @error_template_attribute } + else + format.html { render :edit } + format.json { render json: @error_template_attribute.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /error_template_attributes/1 + # DELETE /error_template_attributes/1.json + def destroy + authorize! + @error_template_attribute.destroy + respond_to do |format| + format.html { redirect_to error_template_attributes_url, notice: 'Error template attribute was successfully destroyed.' } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_error_template_attribute + @error_template_attribute = ErrorTemplateAttribute.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def error_template_attribute_params + params[:error_template_attribute].permit(:key, :description, :regex, :important) + end +end diff --git a/app/controllers/error_templates_controller.rb b/app/controllers/error_templates_controller.rb new file mode 100644 index 00000000..2632abf9 --- /dev/null +++ b/app/controllers/error_templates_controller.rb @@ -0,0 +1,104 @@ +class ErrorTemplatesController < ApplicationController + before_action :set_error_template, only: [:show, :edit, :update, :destroy, :add_attribute, :remove_attribute] + + def authorize! + authorize(@error_templates || @error_template) + end + private :authorize! + + # GET /error_templates + # GET /error_templates.json + def index + @error_templates = ErrorTemplate.all.order(:execution_environment_id, :name).paginate(page: params[:page]) + authorize! + end + + # GET /error_templates/1 + # GET /error_templates/1.json + def show + authorize! + end + + # GET /error_templates/new + def new + @error_template = ErrorTemplate.new + authorize! + end + + # GET /error_templates/1/edit + def edit + authorize! + end + + # POST /error_templates + # POST /error_templates.json + def create + @error_template = ErrorTemplate.new(error_template_params) + authorize! + + respond_to do |format| + if @error_template.save + format.html { redirect_to @error_template, notice: 'Error template was successfully created.' } + format.json { render :show, status: :created, location: @error_template } + else + format.html { render :new } + format.json { render json: @error_template.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /error_templates/1 + # PATCH/PUT /error_templates/1.json + def update + authorize! + respond_to do |format| + if @error_template.update(error_template_params) + format.html { redirect_to @error_template, notice: 'Error template was successfully updated.' } + format.json { render :show, status: :ok, location: @error_template } + else + format.html { render :edit } + format.json { render json: @error_template.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /error_templates/1 + # DELETE /error_templates/1.json + def destroy + authorize! + @error_template.destroy + respond_to do |format| + format.html { redirect_to error_templates_url, notice: 'Error template was successfully destroyed.' } + format.json { head :no_content } + end + end + + def add_attribute + authorize! + @error_template.error_template_attributes << ErrorTemplateAttribute.find(params['error_template_attribute_id']) + respond_to do |format| + format.html { redirect_to @error_template } + format.json { head :no_content } + end + end + + def remove_attribute + authorize! + @error_template.error_template_attributes.delete(ErrorTemplateAttribute.find(params['error_template_attribute_id'])) + respond_to do |format| + format.html { redirect_to @error_template } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_error_template + @error_template = ErrorTemplate.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def error_template_params + params[:error_template].permit(:name, :execution_environment_id, :signature, :description, :hint) + end +end diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 4dfa182c..8e7a8e39 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -6,7 +6,7 @@ class SubmissionsController < ApplicationController include SubmissionScoring include Tubesock::Hijack - before_action :set_submission, only: [:download, :download_file, :render_file, :run, :score, :show, :statistics, :stop, :test] + before_action :set_submission, only: [:download, :download_file, :render_file, :run, :score, :extract_errors, :show, :statistics, :stop, :test] before_action :set_docker_client, only: [:run, :test] before_action :set_files, only: [:download, :download_file, :render_file, :show] before_action :set_file, only: [:download_file, :render_file] @@ -191,6 +191,9 @@ class SubmissionsController < ApplicationController end def kill_socket(tubesock) + # search for errors and save them as StructuredError (for scoring runs see submission_scoring.rb) + extract_errors + # save the output of this "run" as a "testrun" (scoring runs are saved in submission_scoring.rb) save_run_output @@ -199,6 +202,17 @@ class SubmissionsController < ApplicationController tubesock.close end + def extract_errors + if !@message_buffer.blank? + @submission.exercise.execution_environment.error_templates.each do |template| + pattern = Regexp.new(template.signature).freeze + if pattern.match(@message_buffer) + StructuredError.create_from_template(template, @message_buffer) + end + end + end + end + def handle_message(message, tubesock, container) @run_output ||= "" # Handle special commands first diff --git a/app/helpers/error_template_attributes_helper.rb b/app/helpers/error_template_attributes_helper.rb new file mode 100644 index 00000000..9046f407 --- /dev/null +++ b/app/helpers/error_template_attributes_helper.rb @@ -0,0 +1,2 @@ +module ErrorTemplateAttributesHelper +end diff --git a/app/helpers/error_templates_helper.rb b/app/helpers/error_templates_helper.rb new file mode 100644 index 00000000..f42cf302 --- /dev/null +++ b/app/helpers/error_templates_helper.rb @@ -0,0 +1,2 @@ +module ErrorTemplatesHelper +end diff --git a/app/models/error_template.rb b/app/models/error_template.rb new file mode 100644 index 00000000..be4a3279 --- /dev/null +++ b/app/models/error_template.rb @@ -0,0 +1,8 @@ +class ErrorTemplate < ActiveRecord::Base + belongs_to :execution_environment + has_and_belongs_to_many :error_template_attributes + + def to_s + "#{id} [#{name}]" + end +end diff --git a/app/models/error_template_attribute.rb b/app/models/error_template_attribute.rb new file mode 100644 index 00000000..844c1019 --- /dev/null +++ b/app/models/error_template_attribute.rb @@ -0,0 +1,7 @@ +class ErrorTemplateAttribute < ActiveRecord::Base + has_and_belongs_to_many :error_template + + def to_s + "#{id} [#{key}]" + end +end diff --git a/app/models/execution_environment.rb b/app/models/execution_environment.rb index 3a4efdde..74177173 100644 --- a/app/models/execution_environment.rb +++ b/app/models/execution_environment.rb @@ -11,6 +11,7 @@ class ExecutionEnvironment < ActiveRecord::Base has_many :exercises belongs_to :file_type has_many :hints + has_many :error_templates scope :with_exercises, -> { where('id IN (SELECT execution_environment_id FROM exercises)') } diff --git a/app/models/structured_error.rb b/app/models/structured_error.rb new file mode 100644 index 00000000..2f03ec54 --- /dev/null +++ b/app/models/structured_error.rb @@ -0,0 +1,12 @@ +class StructuredError < ActiveRecord::Base + belongs_to :error_template + belongs_to :file, class_name: 'CodeOcean::File' + + def self.create_from_template(template, message_buffer) + instance = self.create(error_template: template) + template.error_template_attributes.each do |attribute| + StructuredErrorAttribute.create_from_template(attribute, instance, message_buffer) + end + instance + end +end diff --git a/app/models/structured_error_attribute.rb b/app/models/structured_error_attribute.rb new file mode 100644 index 00000000..6eb17fc4 --- /dev/null +++ b/app/models/structured_error_attribute.rb @@ -0,0 +1,17 @@ +class StructuredErrorAttribute < ActiveRecord::Base + belongs_to :structured_error + belongs_to :error_template_attribute + + def self.create_from_template(attribute, structured_error, message_buffer) + match = false + value = nil + result = message_buffer.match(attribute.regex) + if result != nil + match = true + if result.captures.size > 0 + value = result.captures[0] + end + end + self.create(structured_error: structured_error, error_template_attribute: attribute, value: value, match: match) + end +end diff --git a/app/policies/error_template_attribute_policy.rb b/app/policies/error_template_attribute_policy.rb new file mode 100644 index 00000000..eed0896d --- /dev/null +++ b/app/policies/error_template_attribute_policy.rb @@ -0,0 +1,3 @@ +class ErrorTemplateAttributePolicy < AdminOnlyPolicy + +end diff --git a/app/policies/error_template_policy.rb b/app/policies/error_template_policy.rb new file mode 100644 index 00000000..908be08e --- /dev/null +++ b/app/policies/error_template_policy.rb @@ -0,0 +1,9 @@ +class ErrorTemplatePolicy < AdminOnlyPolicy + def add_attribute? + admin? + end + + def remove_attribute? + admin? + end +end diff --git a/app/views/application/_navigation.html.slim b/app/views/application/_navigation.html.slim index 19f10a85..668ccab4 100644 --- a/app/views/application/_navigation.html.slim +++ b/app/views/application/_navigation.html.slim @@ -8,7 +8,8 @@ - if current_user.admin? li = link_to(t('breadcrumbs.dashboard.show'), admin_dashboard_path) li.divider - - models = [ExecutionEnvironment, Exercise, ExerciseCollection, ProxyExercise, Tag, Consumer, CodeHarborLink, ExternalUser, FileType, FileTemplate, InternalUser].sort_by { |model| model.model_name.human(count: 2) } + - models = [ExecutionEnvironment, Exercise, ExerciseCollection, ProxyExercise, Tag, Consumer, CodeHarborLink, + ErrorTemplate, ErrorTemplateAttribute, ExternalUser, FileType, FileTemplate, InternalUser].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/error_template_attributes/_form.html.slim b/app/views/error_template_attributes/_form.html.slim new file mode 100644 index 00000000..4fc28b02 --- /dev/null +++ b/app/views/error_template_attributes/_form.html.slim @@ -0,0 +1,16 @@ += form_for(@error_template_attribute) do |f| + = render('shared/form_errors', object: @error_template_attribute) + .form-group + = f.label(:key) + = f.text_field(:key, class: 'form-control', required: true) + .form-group + = f.label(:description) + = f.text_field(:description, class: 'form-control') + .form-group + = f.label(:regex) + = f.text_field(:regex, class: 'form-control', required: true) + .help-block == t('error_templates.hints.signature') + .form-group + = f.check_box(:important) + = t('activerecord.attributes.error_template_attribute.important') + .actions = render('shared/submit_button', f: f, object: @error_template_attribute) diff --git a/app/views/error_template_attributes/edit.html.slim b/app/views/error_template_attributes/edit.html.slim new file mode 100644 index 00000000..f3279442 --- /dev/null +++ b/app/views/error_template_attributes/edit.html.slim @@ -0,0 +1,3 @@ +h1 = @error_template_attribute + += render('form') diff --git a/app/views/error_template_attributes/index.html.slim b/app/views/error_template_attributes/index.html.slim new file mode 100644 index 00000000..81d5cac9 --- /dev/null +++ b/app/views/error_template_attributes/index.html.slim @@ -0,0 +1,28 @@ +h1 = ErrorTemplateAttribute.model_name.human(count: 2) + +.table-responsive + table.sortable.table + thead + tr + th + th = t('activerecord.attributes.error_template_attribute.key') + th = t('activerecord.attributes.error_template_attribute.description') + th = t('activerecord.attributes.error_template_attribute.regex') + th colspan=5 = t('shared.actions') + tbody + - @error_template_attributes.each do |error_template_attribute| + tr + td + - if error_template_attribute.important + span class="fa fa-star" aria-hidden="true" + - else + span class="fa fa-star-o" aria-hidden="true" + td = error_template_attribute.key + td = error_template_attribute.description + td = error_template_attribute.regex + td = link_to(t('shared.show'), error_template_attribute) + td = link_to(t('shared.edit'), edit_error_template_attribute_path(error_template_attribute)) + td = link_to(t('shared.destroy'), error_template_attribute, data: {confirm: t('shared.confirm_destroy')}, method: :delete) + += render('shared/pagination', collection: @error_template_attributes) +p = render('shared/new_button', model: ErrorTemplateAttribute) diff --git a/app/views/error_template_attributes/new.html.slim b/app/views/error_template_attributes/new.html.slim new file mode 100644 index 00000000..12c719f9 --- /dev/null +++ b/app/views/error_template_attributes/new.html.slim @@ -0,0 +1,3 @@ +h1 = t('shared.new_model', model: ErrorTemplateAttribute.model_name.human) + += render('form') diff --git a/app/views/error_template_attributes/show.html.slim b/app/views/error_template_attributes/show.html.slim new file mode 100644 index 00000000..2bdd01ca --- /dev/null +++ b/app/views/error_template_attributes/show.html.slim @@ -0,0 +1,8 @@ +h1 + = @error_template_attribute + = render('shared/edit_button', object: @error_template_attribute) + +- [:key, :description, :regex, :important].each do |attribute| + = row(label: "error_template_attribute.#{attribute}", value: @error_template_attribute.send(attribute)) + +// todo: used by diff --git a/app/views/error_templates/_form.html.slim b/app/views/error_templates/_form.html.slim new file mode 100644 index 00000000..f9363155 --- /dev/null +++ b/app/views/error_templates/_form.html.slim @@ -0,0 +1,19 @@ += form_for(@error_template) do |f| + = render('shared/form_errors', object: @error_template) + .form-group + = f.label(:name) + = f.text_field(:name, class: 'form-control', required: true) + .form-group + = f.label(:execution_environment_id) + = f.collection_select(:execution_environment_id, ExecutionEnvironment.all.order(:name), :id, :name, {include_blank: false}, class: 'form-control') + .form-group + = f.label(:signature) + = f.text_field(:signature, class: 'form-control') + .help-block == t('error_templates.hints.signature') + .form-group + = f.label(:description) + = f.text_field(:description, class: 'form-control') + .form-group + = f.label(:hint) + = f.text_field(:hint, class: 'form-control') + .actions = render('shared/submit_button', f: f, object: @error_template) diff --git a/app/views/error_templates/edit.html.slim b/app/views/error_templates/edit.html.slim new file mode 100644 index 00000000..2d12b363 --- /dev/null +++ b/app/views/error_templates/edit.html.slim @@ -0,0 +1,3 @@ +h1 = @error_template + += render('form') diff --git a/app/views/error_templates/index.html.slim b/app/views/error_templates/index.html.slim new file mode 100644 index 00000000..f44b3f67 --- /dev/null +++ b/app/views/error_templates/index.html.slim @@ -0,0 +1,22 @@ +h1 = ErrorTemplate.model_name.human(count: 2) + +.table-responsive + table.sortable.table + thead + tr + th = t('activerecord.attributes.error_template.name') + th = t('activerecord.attributes.error_template.description') + th = t('activerecord.attributes.exercise.execution_environment') + th colspan=3 = t('shared.actions') + tbody + - @error_templates.each do |error_template| + tr + td = error_template.name + td = error_template.description + td = link_to(error_template.execution_environment) + td = link_to(t('shared.show'), error_template) + td = link_to(t('shared.edit'), edit_error_template_path(error_template)) + td = link_to(t('shared.destroy'), error_template, data: {confirm: t('shared.confirm_destroy')}, method: :delete) + += render('shared/pagination', collection: @error_templates) +p = render('shared/new_button', model: ErrorTemplate) diff --git a/app/views/error_templates/new.html.slim b/app/views/error_templates/new.html.slim new file mode 100644 index 00000000..eeecadc8 --- /dev/null +++ b/app/views/error_templates/new.html.slim @@ -0,0 +1,3 @@ +h1 = t('shared.new_model', model: ErrorTemplate.model_name.human) + += render('form') diff --git a/app/views/error_templates/show.html.slim b/app/views/error_templates/show.html.slim new file mode 100644 index 00000000..9936ef7f --- /dev/null +++ b/app/views/error_templates/show.html.slim @@ -0,0 +1,40 @@ +h1 + = @error_template + = render('shared/edit_button', object: @error_template) + += row(label: 'error_template.name', value: @error_template.name) += row(label: 'exercise.execution_environment', value: link_to(@error_template.execution_environment)) +- [:signature, :description, :hint].each do |attribute| + = row(label: "error_template.#{attribute}", value: @error_template.send(attribute)) + +h3 + = t 'error_templates.attributes' + +.table-responsive + table.sortable.table + thead + tr + th + th = t('activerecord.attributes.error_template_attribute.key') + th = t('activerecord.attributes.error_template_attribute.description') + th = t('activerecord.attributes.error_template_attribute.regex') + th colspan=3 = t('shared.actions') + tbody + - @error_template.error_template_attributes.order('important DESC', :key).each do |attribute| + tr + td + - if attribute.important + span class="fa fa-star" aria-hidden="true" + - else + span class="fa fa-star-o" aria-hidden="true" + td = attribute.key + td = attribute.description + td = attribute.regex + td = link_to(t('shared.show'), attribute) + td = link_to(t('shared.destroy'), attribute_error_template_url(:error_template_attribute_id => attribute.id), :method => :delete) + +#add-attribute + = collection_select({}, :error_template_attribute_id, + ErrorTemplateAttribute.where.not(id: @error_template.error_template_attributes.select(:id).to_a).order('important DESC', :key), + :id, :key, {include_blank: false}, class: '') + button.btn.btn-default = t('error_templates.add_attribute') diff --git a/config/locales/de.yml b/config/locales/de.yml index 1e2b2337..afeea353 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -111,6 +111,16 @@ de: name: "Name" file_type: "Dateityp" content: "Code" + error_template: + name: Name + signature: Regulärer Ausdruck + description: Beschreibung + hint: Hinweis + error_template_attribute: + important: "Wichtig" + key: "Name" + description: "Beschreibung" + regex: "Regulärer Ausdruck" exercise_collections: id: "ID" name: "Name" @@ -126,6 +136,12 @@ de: error: one: Fehler other: Fehler + error_template: + one: Fehlertemplate + other: Fehlertemplates + error_template_attribute: + one: Fehlertemplatettribut + other: Fehlertemplatettribute execution_environment: one: Ausführungsumgebung other: Ausführungsumgebungen @@ -642,10 +658,14 @@ de: estimated_time_20_to_30: "zwischen 20 und 30 Minuten" estimated_time_more_30: "mehr als 30 Minuten" working_time: "Geschätze Bearbeitungszeit für diese Aufgabe:" + error_templates: + hints: + signature: "Ein regulärer Ausdruck in Ruby-Syntax und ohne führende und schließende \"/\"" + attributes: "Attribute" + add_attribute: "Attribut hinzufügen" comments: deleted: "Gelöscht" save_update: "Speichern" subscriptions: successfully_unsubscribed: "Ihr Abonnement für weitere Kommentare auf dieser Kommentaranfrage wurde erfolgreich beendet." subscription_not_existent: "Das Abonnement, von dem Sie sich abmelden wollen, existiert nicht." - diff --git a/config/locales/en.yml b/config/locales/en.yml index c55c90b0..6c1d91fd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -111,6 +111,16 @@ en: name: "Name" file_type: "File Type" content: "Content" + error_template: + name: Name + signature: Signature Regular Expression + description: Description + hint: Hint + error_template_attribute: + important: "Important" + key: "Identifier" + description: "Description" + regex: "Regular Expression" exercise_collections: id: "ID" name: "Name" @@ -126,6 +136,12 @@ en: error: one: Error other: Errors + error_template: + one: Error Template + other: Error Templates + error_template_attribute: + one: Error Template Attribute + other: Error Template Attributes execution_environment: one: Execution Environment other: Execution Environments @@ -642,6 +658,11 @@ en: estimated_time_20_to_30: "between 20 and 30 minutes" estimated_time_more_30: "more than 30 minutes" working_time: "Estimated time working on this exercise:" + error_templates: + hints: + signature: "A regular expression in Ruby syntax without leading and trailing \"/\"" + attributes: "Attributes" + add_attribute: "Add attribute" comments: deleted: "Deleted" save_update: "Save" diff --git a/config/routes.rb b/config/routes.rb index 7f254f1d..b6fef404 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,13 @@ FILENAME_REGEXP = /[\w\.]+/ unless Kernel.const_defined?(:FILENAME_REGEXP) Rails.application.routes.draw do + resources :error_template_attributes + resources :error_templates do + member do + put 'attribute', to: 'error_templates#add_attribute' + delete 'attribute', to: 'error_templates#remove_attribute' + end + end resources :file_templates do collection do get 'by_file_type/:file_type_id', as: :by_file_type, action: :by_file_type diff --git a/db/migrate/20170703075832_create_error_templates.rb b/db/migrate/20170703075832_create_error_templates.rb new file mode 100644 index 00000000..6f442842 --- /dev/null +++ b/db/migrate/20170703075832_create_error_templates.rb @@ -0,0 +1,11 @@ +class CreateErrorTemplates < ActiveRecord::Migration + def change + create_table :error_templates do |t| + t.belongs_to :execution_environment + t.string :name + t.string :signature + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20170703075959_create_error_template_attributes.rb b/db/migrate/20170703075959_create_error_template_attributes.rb new file mode 100644 index 00000000..3503fcac --- /dev/null +++ b/db/migrate/20170703075959_create_error_template_attributes.rb @@ -0,0 +1,11 @@ +class CreateErrorTemplateAttributes < ActiveRecord::Migration + def change + create_table :error_template_attributes do |t| + t.belongs_to :error_template + t.string :key + t.string :regex + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20170703080205_create_structured_errors.rb b/db/migrate/20170703080205_create_structured_errors.rb new file mode 100644 index 00000000..560649b4 --- /dev/null +++ b/db/migrate/20170703080205_create_structured_errors.rb @@ -0,0 +1,10 @@ +class CreateStructuredErrors < ActiveRecord::Migration + def change + create_table :structured_errors do |t| + t.references :error_template + t.belongs_to :file + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20170703080355_create_structured_error_attributes.rb b/db/migrate/20170703080355_create_structured_error_attributes.rb new file mode 100644 index 00000000..aa9ee04e --- /dev/null +++ b/db/migrate/20170703080355_create_structured_error_attributes.rb @@ -0,0 +1,11 @@ +class CreateStructuredErrorAttributes < ActiveRecord::Migration + def change + create_table :structured_error_attributes do |t| + t.belongs_to :structured_error + t.references :error_template_attribute + t.string :value + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20170711170456_add_description_and_hint_to_error_template.rb b/db/migrate/20170711170456_add_description_and_hint_to_error_template.rb new file mode 100644 index 00000000..62cbee95 --- /dev/null +++ b/db/migrate/20170711170456_add_description_and_hint_to_error_template.rb @@ -0,0 +1,9 @@ +class AddDescriptionAndHintToErrorTemplate < ActiveRecord::Migration + def change + add_column :error_templates, :description, :text + add_column :error_templates, :hint, :text + + add_column :error_template_attributes, :description, :text + add_column :error_template_attributes, :important, :boolean + end +end diff --git a/db/migrate/20170711170928_change_error_template_attribute_relationship_to_n_to_m.rb b/db/migrate/20170711170928_change_error_template_attribute_relationship_to_n_to_m.rb new file mode 100644 index 00000000..d10fbda2 --- /dev/null +++ b/db/migrate/20170711170928_change_error_template_attribute_relationship_to_n_to_m.rb @@ -0,0 +1,6 @@ +class ChangeErrorTemplateAttributeRelationshipToNToM < ActiveRecord::Migration + def change + remove_belongs_to :error_template_attributes, :error_template + create_join_table :error_templates, :error_template_attributes + end +end diff --git a/db/migrate/20170719133351_add_match_to_structured_error_attribute.rb b/db/migrate/20170719133351_add_match_to_structured_error_attribute.rb new file mode 100644 index 00000000..7ec3ccc0 --- /dev/null +++ b/db/migrate/20170719133351_add_match_to_structured_error_attribute.rb @@ -0,0 +1,5 @@ +class AddMatchToStructuredErrorAttribute < ActiveRecord::Migration + def change + add_column :structured_error_attributes, :match, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 988cd447..465ca604 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -47,6 +47,30 @@ ActiveRecord::Schema.define(version: 20170920145852) do t.string "oauth_secret", limit: 255 end + create_table "error_template_attributes", force: :cascade do |t| + t.string "key" + t.string "regex" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "description" + t.boolean "important" + end + + create_table "error_template_attributes_templates", id: false, force: :cascade do |t| + t.integer "error_template_id", null: false + t.integer "error_template_attribute_id", null: false + end + + create_table "error_templates", force: :cascade do |t| + t.integer "execution_environment_id" + t.string "name" + t.string "signature" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "description" + t.text "hint" + end + create_table "errors", force: :cascade do |t| t.integer "execution_environment_id" t.text "message" @@ -268,6 +292,22 @@ ActiveRecord::Schema.define(version: 20170920145852) do t.datetime "updated_at" end + create_table "structured_error_attributes", force: :cascade do |t| + t.integer "structured_error_id" + t.integer "error_template_attribute_id" + t.string "value" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "match" + end + + create_table "structured_errors", force: :cascade do |t| + t.integer "error_template_id" + t.integer "file_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "submissions", force: :cascade do |t| t.integer "exercise_id" t.float "score" diff --git a/test/controllers/error_template_attributes_controller_test.rb b/test/controllers/error_template_attributes_controller_test.rb new file mode 100644 index 00000000..7dd008ec --- /dev/null +++ b/test/controllers/error_template_attributes_controller_test.rb @@ -0,0 +1,49 @@ +require 'test_helper' + +class ErrorTemplateAttributesControllerTest < ActionController::TestCase + setup do + @error_template_attribute = error_template_attributes(:one) + end + + test "should get index" do + get :index + assert_response :success + assert_not_nil assigns(:error_template_attributes) + end + + test "should get new" do + get :new + assert_response :success + end + + test "should create error_template_attribute" do + assert_difference('ErrorTemplateAttribute.count') do + post :create, error_template_attribute: { } + end + + assert_redirected_to error_template_attribute_path(assigns(:error_template_attribute)) + end + + test "should show error_template_attribute" do + get :show, id: @error_template_attribute + assert_response :success + end + + test "should get edit" do + get :edit, id: @error_template_attribute + assert_response :success + end + + test "should update error_template_attribute" do + patch :update, id: @error_template_attribute, error_template_attribute: { } + assert_redirected_to error_template_attribute_path(assigns(:error_template_attribute)) + end + + test "should destroy error_template_attribute" do + assert_difference('ErrorTemplateAttribute.count', -1) do + delete :destroy, id: @error_template_attribute + end + + assert_redirected_to error_template_attributes_path + end +end diff --git a/test/controllers/error_templates_controller_test.rb b/test/controllers/error_templates_controller_test.rb new file mode 100644 index 00000000..452ad134 --- /dev/null +++ b/test/controllers/error_templates_controller_test.rb @@ -0,0 +1,49 @@ +require 'test_helper' + +class ErrorTemplatesControllerTest < ActionController::TestCase + setup do + @error_template = error_templates(:one) + end + + test "should get index" do + get :index + assert_response :success + assert_not_nil assigns(:error_templates) + end + + test "should get new" do + get :new + assert_response :success + end + + test "should create error_template" do + assert_difference('ErrorTemplate.count') do + post :create, error_template: { } + end + + assert_redirected_to error_template_path(assigns(:error_template)) + end + + test "should show error_template" do + get :show, id: @error_template + assert_response :success + end + + test "should get edit" do + get :edit, id: @error_template + assert_response :success + end + + test "should update error_template" do + patch :update, id: @error_template, error_template: { } + assert_redirected_to error_template_path(assigns(:error_template)) + end + + test "should destroy error_template" do + assert_difference('ErrorTemplate.count', -1) do + delete :destroy, id: @error_template + end + + assert_redirected_to error_templates_path + end +end diff --git a/test/factories/error_template_attributes.rb b/test/factories/error_template_attributes.rb new file mode 100644 index 00000000..24adb856 --- /dev/null +++ b/test/factories/error_template_attributes.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :error_template_attribute do + error_template nil + key "MyString" + regex "MyString" + end +end diff --git a/test/factories/error_templates.rb b/test/factories/error_templates.rb new file mode 100644 index 00000000..2abf68c9 --- /dev/null +++ b/test/factories/error_templates.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :error_template do + execution_environment nil + name "MyString" + signature "MyString" + end +end diff --git a/test/factories/structured_error_attributes.rb b/test/factories/structured_error_attributes.rb new file mode 100644 index 00000000..7485967c --- /dev/null +++ b/test/factories/structured_error_attributes.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :structured_error_attribute do + structured_error nil + error_template_attribute nil + value "MyString" + end +end diff --git a/test/factories/structured_errors.rb b/test/factories/structured_errors.rb new file mode 100644 index 00000000..4a87cec1 --- /dev/null +++ b/test/factories/structured_errors.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :structured_error do + error_template nil + file nil + end +end diff --git a/test/models/error_template_attribute_test.rb b/test/models/error_template_attribute_test.rb new file mode 100644 index 00000000..187ae1b7 --- /dev/null +++ b/test/models/error_template_attribute_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class ErrorTemplateAttributeTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/error_template_test.rb b/test/models/error_template_test.rb new file mode 100644 index 00000000..538dc19a --- /dev/null +++ b/test/models/error_template_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class ErrorTemplateTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/structured_error_attribute_test.rb b/test/models/structured_error_attribute_test.rb new file mode 100644 index 00000000..1dcb316b --- /dev/null +++ b/test/models/structured_error_attribute_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class StructuredErrorAttributeTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/structured_error_test.rb b/test/models/structured_error_test.rb new file mode 100644 index 00000000..28b03689 --- /dev/null +++ b/test/models/structured_error_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class StructuredErrorTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end