diff --git a/Gemfile b/Gemfile index 2a5dfe2f1..b05d78955 100644 --- a/Gemfile +++ b/Gemfile @@ -68,6 +68,13 @@ gem "webpacker" # Application secrets checker gem "nuclear_secrets" +# Caliper +gem "rest-client" +gem "uuid" +git "https://github.com/IMSGlobal/caliper-ruby.git", branch: "develop" do + gem "ims_caliper" +end + # This is only here because we are on ruby 2.4. When we upgrade ruby we can remove this gem "sprockets", "~>3.7.2" diff --git a/Gemfile.lock b/Gemfile.lock index 67ed48562..d8fb0323d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,10 @@ +GIT + remote: https://github.com/IMSGlobal/caliper-ruby.git + revision: 16256300ecdf13bd2438ffc166b1a93f240b9839 + branch: develop + specs: + ims_caliper (1.1) + GIT remote: https://github.com/alexpeattie/heroku_secrets.git revision: 717af8e0acf399bc88d2c8c1a7e230023942af0f @@ -122,6 +129,8 @@ GEM actionmailer (>= 5.0) devise (>= 4.6) diff-lcs (1.4.4) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) dotenv (2.7.6) dotenv-rails (2.7.6) dotenv (= 2.7.6) @@ -169,6 +178,9 @@ GEM rspec (>= 2.99.0, < 4.0) hashdiff (1.0.1) hashie (4.1.0) + http-accept (1.7.0) + http-cookie (1.0.3) + domain_name (~> 0.5) httparty (0.18.1) mime-types (~> 3.0) multi_xml (>= 0.5.2) @@ -203,6 +215,8 @@ GEM crass (~> 1.0.2) nokogiri (>= 1.5.9) lumberjack (1.2.7) + macaddr (1.7.2) + systemu (~> 2.6.5) mail (2.7.1) mini_mime (>= 0.1.1) mail_view (2.0.4) @@ -222,6 +236,7 @@ GEM multi_xml (0.6.0) multipart-post (2.1.1) nenv (0.3.0) + netrc (0.11.0) nio4r (2.5.4) nokogiri (1.10.10) mini_portile2 (~> 2.4.0) @@ -322,6 +337,11 @@ GEM responders (3.0.1) actionpack (>= 5.0) railties (>= 5.0) + rest-client (2.1.0) + http-accept (>= 1.7.0, < 2.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) rexml (3.2.4) rolify (5.3.0) rollbar (2.27.0) @@ -404,6 +424,7 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) strong_password (0.0.9) + systemu (2.6.5) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thor (1.0.1) @@ -413,7 +434,12 @@ GEM thread_safe (~> 0.1) uglifier (4.2.0) execjs (>= 0.3.0, < 3) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.7) unicode-display_width (1.7.0) + uuid (2.3.9) + macaddr (~> 1.0) warden (1.2.9) rack (>= 2.0.9) web-console (3.7.0) @@ -468,6 +494,7 @@ DEPENDENCIES httparty hub ims-lti (~> 2.1.5) + ims_caliper! json-jwt jwt launchy @@ -495,6 +522,7 @@ DEPENDENCIES rb-fchange rb-fsevent rb-inotify + rest-client rolify rollbar rspec-rails @@ -513,6 +541,7 @@ DEPENDENCIES strong_password tzinfo-data uglifier + uuid web-console (~> 3.7.0) webmock webpacker @@ -520,4 +549,4 @@ DEPENDENCIES yajl-ruby BUNDLED WITH - 1.17.3 + 2.1.4 diff --git a/app/controllers/lti_launches_controller.rb b/app/controllers/lti_launches_controller.rb index 7f4a38f8c..638b98d38 100644 --- a/app/controllers/lti_launches_controller.rb +++ b/app/controllers/lti_launches_controller.rb @@ -7,6 +7,7 @@ class LtiLaunchesController < ApplicationController skip_before_action :verify_authenticity_token before_action :do_lti, except: [:init, :launch] + after_action :do_caliper def index if current_application_instance.disabled_at @@ -60,4 +61,19 @@ def setup_lti_response set_lti_launch_values end + def do_caliper + events = Kaliper::LtiUtils.from_lti_1_2( + application_instance: current_application_instance, + user: current_user, + params: params, + ) + options = Caliper::Options.new + sensor = Caliper::Sensor.new('https://www.atomicjolt.com/sensors/1', options) + requestor = Caliper::Request::HttpRequestor.new({ + 'host' => "http://learncaliper.herokuapp.com/api/consumer/events/1ebb1f70-5e82-0137-e4ac-4a8f9eeae0c8", + 'auth_token' => "1ebb1f70-5e82-0137-e4ac-4a8f9eeae0c8", + }) + requestor.send(sensor, events.tool_use_event) + end + end diff --git a/app/lib/kaliper/events.rb b/app/lib/kaliper/events.rb new file mode 100644 index 000000000..d2c541db3 --- /dev/null +++ b/app/lib/kaliper/events.rb @@ -0,0 +1,48 @@ +require "caliper" + +module Kaliper + class Events + + attr_accessor :actor + attr_accessor :ed_app + attr_accessor :group + attr_accessor :membership + attr_accessor :session + + def initialize(application_instance:, user:) + @application_instance = application_instance + @user = user + + @actor = Caliper::Entities::Agent::Person.new( + id: "#{@application_instance.domain}/users/#{@user.id}", + name: @user.display_name, + ) + + @ed_app = Caliper::Entities::Agent::SoftwareApplication.new( + id: @application_instance.domain, + name: @application_instance.application.name, + description: @application_instance.application.description, + ) + end + + # tool use Event docs: https://www.imsglobal.org/sites/default/files/caliper/v1p1/caliper-spec-v1p1/caliper-spec-v1p1.html#toolUseEvent + def tool_use_event(event_time: Time.now.utc.iso8601) + event = Caliper::Events::ToolUseEvent.new( + id: "urn:uuid:#{SecureRandom.uuid}", + object: @ed_app, + actor: @actor, + action: Caliper::Actions::USED, + edApp: @ed_app, + eventTime: event_time + ) + + event.group = @group if @group + event.membership = @membership if @membership + event.session = @session if @session + event.federated_session = @federated_session if @federated_session + + event + end + + end +end diff --git a/app/lib/kaliper/lti_utils.rb b/app/lib/kaliper/lti_utils.rb new file mode 100644 index 000000000..32d836c52 --- /dev/null +++ b/app/lib/kaliper/lti_utils.rb @@ -0,0 +1,49 @@ +require "caliper" + +module Kaliper + class LtiUtils + + def self.from_lti_1_2(application_instance:, user:, params:) + event = Kaliper::Events.new(application_instance: application_instance, user: user) + + if params[:customer_caliper_federated_session_id].present? + # https://www.imsglobal.org/sites/default/files/caliper/v1p1/caliper-spec-v1p1/caliper-spec-v1p1.html#ltiSession + event.federatedSession = Caliper::Entities::Session::LtiSession.new( + id: params[:customer_caliper_federated_session_id], + user: event.actor, + ) + end + + #return from_canvas_lti(event, params) if params[:custom_canvas_api_domain].present? + event + end + + # TODO + # def self.from_canvas_lti(event, params) + + # event.group = Caliper::Entities::LIS::CourseSection.new( + # id: "https://#{params[:custom_canvas_api_domain]}/courses/#{params[:custom_canvas_course_id]}/sections/#{params[:custom_canvas_section_id]}", + # courseNumber: params[:context_title], + # academicSession: params[:canvas_term_start_at], + # ) + + # event.membership = Caliper::Entities::LIS::Membership.new( + # id: membership_id, + # member: event.actor, + # organization: event.group, + # roles: caliper_role_from(params), + # status: Caliper::Entities::LIS::Status::ACTIVE, + # ) + + # event.session = Caliper::Entities::Session::Session.new( + # id: id, + # startedAtTime: started_at.utc.iso8601, + # ) + # end + + # def self.caliper_role_from(params) + # roles = [ Caliper::Entities::LIS::Role::LEARNER ] + # end + + end +end diff --git a/db/seeds.rb b/db/seeds.rb index d383d2997..6c498feaf 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -126,6 +126,9 @@ canvas_course_id: "$Canvas.course.id", canvas_api_domain: "$Canvas.api.domain", canvas_user_id: "$Canvas.user.id", + customer_caliper_endpoint_url: "$Caliper.url", + canvas_course_section_ids: "$Canvas.course.sectionIds", + canvas_term_start_at: "$Canvas.term.startAt",, }, course_navigation: { text: "LTI Starter App",