log the remote_ip in page views

refs CNVS-8392

also update the page view API documentation

test plan:
* with this commit applied, page views will now log the remote
  ip of requests, and return that ip in the api and csv downloads
* regenerate api docs with rake doc:api, and verify that the user
  section now has expanded information on the api response

Change-Id: If463e211515881db6ce1f75c9bbca8c7fbcf7589
Reviewed-on: https://gerrit.instructure.com/24880
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Cody Cutrer <cody@instructure.com>
QA-Review: Jeremy Putnam <jeremyp@instructure.com>
QA-Review: August Thornton <august@instructure.com>
Product-Review: Brian Palmer <brianp@instructure.com>
This commit is contained in:
Brian Palmer 2013-10-01 14:45:53 -05:00
parent 588b3f02d2
commit e1b650f53f
5 changed files with 150 additions and 8 deletions

View File

@ -17,6 +17,123 @@
#
# @API Users
#
# @model PageView
# {
# "id": "PageView",
# "description": "The record of a user page view access in Canvas",
# "required": ["request_id"],
# "properties": {
# "request_id": {
# "description": "A UUID representing the page view",
# "example": "3e246700-e305-0130-51de-02e33aa501ef",
# "type": "string",
# "format": "uuid"
# },
# "user_id": {
# "description": "The ID of the user for this page view",
# "example": "1234",
# "type": "integer",
# "format": "int64"
# },
# "url": {
# "description": "The URL requested",
# "example": "https://canvas.instructure.com/conversations",
# "type": "string"
# },
# "context_type": {
# "description": "The type of context for the request",
# "example": "Course",
# "type": "string"
# },
# "context_id": {
# "description": "The ID of the context for the request (course id if context_type is Course, etc)",
# "example": "1234",
# "type": "integer",
# "format": "int64"
# },
# "asset_type": {
# "description": "The type of asset in the context for the request, if any",
# "example": "Discussion",
# "type": "string"
# },
# "asset_id": {
# "description": "The ID of the asset for the request, if any",
# "example": "1234",
# "type": "integer",
# "format": "int64"
# },
# "controller": {
# "description": "The rails controller that handled the request",
# "example": "discussions",
# "type": "string"
# },
# "action": {
# "description": "The rails action that handled the request",
# "example": "index",
# "type": "string"
# },
# "contributed": {
# "description": "True if the request counted as contributing, such as editing a wiki page",
# "example": "false",
# "type": "boolean"
# },
# "interaction_seconds": {
# "description": "An approximation of how long the user spent on the page, in seconds",
# "example": "7.21",
# "type": "float"
# },
# "created_at": {
# "description": "When the request was made",
# "example": "2013-10-01T19:49:47Z",
# "type": "datetime",
# "format": "iso8601"
# },
# "user_request": {
# "description": "A flag indicating whether the request was user-initiated, or automatic (such as an AJAX call)",
# "example": "true",
# "type": "boolean"
# },
# "render_time": {
# "description": "How long the response took to render, in seconds",
# "example": "0.369",
# "type": "float"
# },
# "user_agent": {
# "description": "The user-agent of the browser or program that made the request",
# "example": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/536.30.1 (KHTML, like Gecko) Version/6.0.5 Safari/536.30.1",
# "type": "string"
# },
# "participated": {
# "description": "True if the request counted as participating, such as submitting homework",
# "example": "false",
# "type": "boolean"
# },
# "account_id": {
# "description": "The ID of the account context for this page view",
# "example": "1234",
# "type": "integer",
# "format": "int64"
# },
# "real_user_id": {
# "description": "The ID of the actual user who made this request, if the request was made by a user who was masquerading",
# "example": "1234",
# "type": "integer",
# "format": "int64"
# },
# "http_method": {
# "description": "The HTTP method such as GET or POST",
# "example": "GET",
# "type": "string"
# },
# "remote_ip": {
# "description": "The origin IP address of the request",
# "example": "173.194.46.71",
# "type": "string"
# }
# }
# }
class PageViewsController < ApplicationController
before_filter :require_user, :only => [:index]
@ -38,12 +155,7 @@ class PageViewsController < ApplicationController
# @argument end_time [Optional, DateTime]
# The end of the time range from which you want page views.
#
# @response_field interaction_seconds The number of seconds the user actively interacted with the page. This is a best guess, using heuristics such as browser input events.
# @response_field url The full canvas URL of the page view.
# @response_field user_agent The browser identifier or other user agent that was used to make the request.
# @response_field controller The Rails controller that processed the request.
# @response_field action The action in the Rails controller that processed the request.
# @response_field context_type The type of "context" of the request, e.g. Account or Course.
# @returns [PageView]
def index
@user = api_find(User, params[:user_id])
if authorized_action(@user, @current_user, :view_statistics)

View File

@ -45,6 +45,7 @@ class PageView < ActiveRecord::Base
self.new(attributes).tap do |p|
p.url = LoggingFilter.filter_uri(request.url)[0,255]
p.http_method = request.method.to_s
p.remote_ip = request.remote_ip
p.controller = request.path_parameters['controller']
p.action = request.path_parameters['action']
p.session_id = request.session_options[:id].to_s.force_encoding(Encoding::UTF_8).presence
@ -79,7 +80,7 @@ class PageView < ActiveRecord::Base
end
# the list of columns we display to users, export to csv, etc
EXPORTED_COLUMNS = %w(request_id user_id url context_id context_type asset_id asset_type controller action contributed interaction_seconds created_at user_request render_time user_agent participated account_id real_user_id http_method)
EXPORTED_COLUMNS = %w(request_id user_id url context_id context_type asset_id asset_type controller action contributed interaction_seconds created_at user_request render_time user_agent participated account_id real_user_id http_method remote_ip)
def self.page_views_enabled?
!!page_view_method

View File

@ -0,0 +1,17 @@
class AddCassandraPageViewsRemoteIp < ActiveRecord::Migration
tag :predeploy
include Canvas::Cassandra::Migration
def self.cassandra_cluster
'page_views'
end
def self.up
cassandra.execute %{ ALTER TABLE page_views ADD remote_ip text; }
end
def self.down
cassandra.execute %{ ALTER TABLE page_views DROP remote_ip; }
end
end

View File

@ -0,0 +1,11 @@
class AddPageViewsRemoteIp < ActiveRecord::Migration
tag :predeploy
def self.up
add_column :page_views, :remote_ip, :string
end
def self.down
remove_column :page_views, :remote_ip
end
end

View File

@ -340,7 +340,7 @@ describe PageView do
describe '.generate' do
let(:params) { {'action' => 'path', 'controller' => 'some'} }
let(:session) { {:id => '42'} }
let(:request) { stub(:url => (@url || 'host.com/some/path'), :path_parameters => params, :user_agent => 'Mozilla', :session_options => session, :method => :get) }
let(:request) { stub(:url => (@url || 'host.com/some/path'), :path_parameters => params, :user_agent => 'Mozilla', :session_options => session, :method => :get, :remote_ip => '0.0.0.0') }
let(:user) { User.new }
let(:attributes) { {:real_user => user, :user => user } }
@ -360,6 +360,7 @@ describe PageView do
its(:created_at) { should_not be_nil }
its(:updated_at) { should_not be_nil }
its(:http_method) { should == 'get' }
its(:remote_ip) { should == '0.0.0.0' }
it "should filter sensitive url params" do
@url = 'http://canvas.example.com/api/v1/courses/1?access_token=SUPERSECRET'