diff --git a/app/assets/javascripts/editor/editor.js.erb b/app/assets/javascripts/editor/editor.js.erb index aa7fee59..1e254dd3 100644 --- a/app/assets/javascripts/editor/editor.js.erb +++ b/app/assets/javascripts/editor/editor.js.erb @@ -29,9 +29,10 @@ var CodeOceanEditor = { lastCopyText: null, + <% self.class.include Rails.application.routes.url_helpers %> <% @config ||= CodeOcean::Config.new(:code_ocean).read(erb: false) %> - sendLearningAnalyticEvents: <%= @config['codeocean_events'] ? @config['codeocean_events']['enabled'] : false %>, - learningAnalyticsURL: "<%= @config['codeocean_events'] ? @config['codeocean_events']['url'] : "" %>", + sendEvents: <%= @config['codeocean_events'] ? @config['codeocean_events']['enabled'] : false %>, + eventURL: "<%= @config['codeocean_events'] ? events_path : '' %>", configureEditors: function () { @@ -149,15 +150,15 @@ configureEditors: function () { }, handlePasteEvent: function (pasteObject) { - var same = (this.lastCopyText === pasteObject.text); + var same = (CodeOceanEditor.lastCopyText === pasteObject.text); - // if the text is not copied from within the editor (from any file), send an event to lanalytics + // if the text is not copied from within the editor (from any file), send an event to the backend if (!same) { - this.publishCodeOceanEvent("codeocean_editor_paste", { - text: pasteObject.text, - codeocean_user_id: $('#editor').data('user-id'), - exercise: $('#editor').data('exercise-id'), - file_id: "1" + CodeOceanEditor.publishCodeOceanEvent({ + category: 'editor_paste', + data: pasteObject.text, + exercise_id: $('#editor').data('exercise-id'), + file_id: $(this).data('file-id') }); } }, @@ -247,8 +248,8 @@ configureEditors: function () { */ // editor itself - editor.on("paste", this.handlePasteEvent.bind(this)); - editor.on("copy", this.handleCopyEvent.bind(this)); + editor.on("paste", this.handlePasteEvent.bind(element)); + editor.on("copy", this.handleCopyEvent.bind(element)); // listener for autosave session.on("change", function (deltaObject) { @@ -381,38 +382,18 @@ configureEditors: function () { //panel.find('.row .col-sm-9').eq(4).find('a').attr('href', '#output-' + index); }, - // move URL to config file, only fire event if desired. - publishCodeOceanEvent: function (eventName, contextData) { - if(this.sendLearningAnalyticEvents){ - - // enhance contextData hash with the user agent - contextData['user_agent'] = navigator.userAgent; - - var payload = { - user: { - uuid: $('#editor').data('user-external-id') - }, - verb: { - type: eventName - }, - resource: { - type: 'page', - uuid: document.location.href - }, - timestamp: new Date().toISOString(), - with_result: {}, - in_context: contextData - }; - - $.ajax(this.learningAnalyticsURL, { + publishCodeOceanEvent: function (payload) { + if(this.sendEvents){ + $.ajax(this.eventURL, { type: 'POST', cache: false, dataType: 'JSON', - data: payload, - success: {}, - error: {} - }) - + data: { + event: payload + }, + success: _.noop, + error: _.noop + }); } }, diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb new file mode 100644 index 00000000..3a3b02a4 --- /dev/null +++ b/app/controllers/events_controller.rb @@ -0,0 +1,28 @@ +class EventsController < ApplicationController + + def authorize! + authorize(@event || @events) + end + private :authorize! + + def create + @event = Event.new(event_params) + authorize! + respond_to do |format| + if @event.save + format.html { head :created } + format.json { head :created } + else + format.html { head :unprocessable_entity } + format.json { head :unprocessable_entity } + end + end + end + + def event_params + params[:event]&.permit(:category, :data, :exercise_id, :file_id) + &.merge(user_id: current_user&.id, user_type: current_user&.class.name) + end + private :event_params + +end diff --git a/app/models/event.rb b/app/models/event.rb new file mode 100644 index 00000000..325fecb8 --- /dev/null +++ b/app/models/event.rb @@ -0,0 +1,8 @@ +class Event < ActiveRecord::Base + belongs_to :user, polymorphic: true + belongs_to :exercise + belongs_to :file + + validates :category, presence: true + validates :data, presence: true +end diff --git a/app/policies/event_policy.rb b/app/policies/event_policy.rb new file mode 100644 index 00000000..a648b901 --- /dev/null +++ b/app/policies/event_policy.rb @@ -0,0 +1,7 @@ +class EventPolicy < AdminOnlyPolicy + + def create? + everyone + end + +end diff --git a/config/routes.rb b/config/routes.rb index 6703d8c1..7c1f317b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -168,6 +168,8 @@ Rails.application.routes.draw do end end + resources :events, only: [:create] + post "/evaluate", to: 'remote_evaluation#evaluate', via: [:post] end diff --git a/db/migrate/20180814145059_create_events.rb b/db/migrate/20180814145059_create_events.rb new file mode 100644 index 00000000..b6bc8745 --- /dev/null +++ b/db/migrate/20180814145059_create_events.rb @@ -0,0 +1,12 @@ +class CreateEvents < ActiveRecord::Migration + def change + create_table :events do |t| + t.string :type + t.string :data + t.belongs_to :user, polymorphic: true, index: true + t.belongs_to :exercise, index: true + t.belongs_to :file, index: true + t.timestamps null: false + end + end +end diff --git a/db/migrate/20180814154055_rename_events_type_to_category.rb b/db/migrate/20180814154055_rename_events_type_to_category.rb new file mode 100644 index 00000000..f97200c8 --- /dev/null +++ b/db/migrate/20180814154055_rename_events_type_to_category.rb @@ -0,0 +1,5 @@ +class RenameEventsTypeToCategory < ActiveRecord::Migration + def change + rename_column :events, :type, :category + end +end diff --git a/db/migrate/20180815115351_remove_event_indices.rb b/db/migrate/20180815115351_remove_event_indices.rb new file mode 100644 index 00000000..2be20c1d --- /dev/null +++ b/db/migrate/20180815115351_remove_event_indices.rb @@ -0,0 +1,7 @@ +class RemoveEventIndices < ActiveRecord::Migration + def change + remove_index :events, [:user_type, :user_id] + remove_index :events, :exercise_id + remove_index :events, :file_id + end +end diff --git a/db/schema.rb b/db/schema.rb index ae1398a6..2646f080 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: 20180703125302) do +ActiveRecord::Schema.define(version: 20180815115351) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -95,6 +95,17 @@ ActiveRecord::Schema.define(version: 20180703125302) do add_index "errors", ["submission_id"], name: "index_errors_on_submission_id", using: :btree + create_table "events", force: :cascade do |t| + t.string "category" + t.string "data" + t.integer "user_id" + t.string "user_type" + t.integer "exercise_id" + t.integer "file_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "execution_environments", force: :cascade do |t| t.string "docker_image", limit: 255 t.string "name", limit: 255 diff --git a/spec/controllers/events_controller_spec.rb b/spec/controllers/events_controller_spec.rb new file mode 100644 index 00000000..bd9e11a8 --- /dev/null +++ b/spec/controllers/events_controller_spec.rb @@ -0,0 +1,33 @@ +require 'rails_helper' + +describe EventsController do + let(:user) { FactoryBot.create(:admin) } + let(:exercise) {FactoryBot.create(:fibonacci)} + before(:each) { allow(controller).to receive(:current_user).and_return(user) } + + describe 'POST #create' do + context 'with a valid event' do + let(:request) { proc { post :create, event: {category: 'foo', data: 'bar', exercise_id: exercise.id, file_id: exercise.files[0].id} } } + before(:each) { request.call } + + expect_assigns(event: Event) + + it 'creates the Event' do + expect { request.call }.to change(Event, :count).by(1) + end + + expect_status(201) + end + + context 'with an invalid event' do + before(:each) { post :create, event: {exercise_id: 847482} } + expect_assigns(event: Event) + expect_status(422) + end + + context 'with no event' do + before(:each) { post :create } + expect_status(422) + end + end +end