Skip to content

Commit

Permalink
client app: canvas_quiz_statistics
Browse files Browse the repository at this point in the history
This commit brings in canvas_quiz_statistics, a client app that can be
embedded in Canvas.

Closes CNVS-14781, CNVS-14846, CNVS-14847, CNVS-14850

> What's done

  - full build integration, meaning the app runs with ?optimized_js=1:
    + JavaScript "built" source gets piped into the r.js build pipeline
      like any other Canvas JS source in public/javascripts/*
    + SCSS sources get picked up by the sass-compiler like Canvas style
      sources and they get compiled from the grounds-up
    + I18n: phrases are extracted properly, with default values and
      options, by our i18n rake tasks
    - new rake task js:build_client_apps that builds client apps and
      injects them as input to the rest of the JS build process
  - support for using Canvas JS packages, like d3, jQuery, and Backbone
  - support for using Canvas SASS variables & helpers
  - super i18n support: use raw I18n.t() calls like you are in Canvas,
    with development-time interpolation, as well as super new
    Handlebars-like block-style translations in React, perfect for very
    long phrases (mini-articles)

> Docs and References

The code was originally developed in its own github repository. While I
won't be pushing code to that repo anymore, the Wiki will still house
the docs until we find a better place.

  - Repo: https://github.com/amireh/canvas_quiz_statistics
  - Development guide: http://bit.ly/1sNOhER
  - Integration guide: http://bit.ly/1m9kA9V

> TESTING

  - login as a teacher
  - go to /courses/:course_id/quizzes/:quiz_id/statistics_cqs
    + make sure you see something that looks like the Ember stats
    + click one of those little "?" help icons, you get a dialog:
      - verify the contents within the dialog are actual English text,
        not code gibberish
      - there's also a link at the end of that dialog, click it and
        verify it takes you to an Instructure help article
  - build the assets: `bundle exec rake canvas:compile_assets` then:
    + add ?optimized_js=1 to the URL and reload the page:
      - verify the app still works

Change-Id: Ic474650dfb06a1c22869ed9680dd04d1ca0f651d
Reviewed-on: https://gerrit.instructure.com/39105
Tested-by: Jenkins <[email protected]>
Reviewed-by: Hannah Bottalla <[email protected]>
Reviewed-by: Adam Ard <[email protected]>
QA-Review: Trevor deHaan <[email protected]>
Product-Review: Ahmad Amireh <[email protected]>
  • Loading branch information
amireh committed Aug 19, 2014
1 parent e101bb9 commit 80d627e
Show file tree
Hide file tree
Showing 154 changed files with 7,692 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ Gemfile.lock3
/spec/coffeescripts/plugins/
/spec/plugins/

# generate client app stuff
/public/javascripts/client_apps/

#remove this once we move jqeury into bower
/public/javascripts/bower/jquery/

Expand Down
1 change: 1 addition & 0 deletions app/coffeescripts/bundles/quiz_statistics_cqs.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require [ 'quiz_statistics_cqs' ]
19 changes: 18 additions & 1 deletion app/controllers/quizzes/quizzes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Quizzes::QuizzesController < ApplicationController
before_filter :require_context
add_crumb(proc { t('#crumbs.quizzes', "Quizzes") }) { |c| c.send :named_context_url, c.instance_variable_get("@context"), :context_quizzes_url }
before_filter { |c| c.active_tab = "quizzes" }
before_filter :require_quiz, :only => [:statistics, :edit, :show, :history, :update, :destroy, :moderate, :read_only, :managed_quiz_data, :submission_versions, :submission_html]
before_filter :require_quiz, :only => [:statistics, :statistics_cqs, :edit, :show, :history, :update, :destroy, :moderate, :read_only, :managed_quiz_data, :submission_versions, :submission_html]
before_filter :set_download_submission_dialog_title , only: [:show,:statistics]
after_filter :lock_results, only: [ :show, :submission_html ]
# The number of questions that can display "details". After this number, the "Show details" option is disabled
Expand Down Expand Up @@ -468,6 +468,23 @@ def statistics
end
end

def statistics_cqs
if authorized_action(@quiz, @current_user, :read_statistics)
respond_to do |format|
format.html {
add_crumb(@quiz.title, named_context_url(@context, :context_quiz_url, @quiz))
add_crumb(t(:statistics_cqs_crumb, "Statistics CQS"), named_context_url(@context, :context_quiz_statistics_cqs_url, @quiz))

js_env({
quiz_url: api_v1_course_quiz_url(@context, @quiz),
quiz_statistics_url: api_v1_course_quiz_statistics_url(@context, @quiz),
quiz_reports_url: api_v1_course_quiz_reports_url(@context, @quiz),
})
}
end
end
end

def managed_quiz_data
extend Api::V1::User
if authorized_action(@quiz, @current_user, [:grade, :read_statistics])
Expand Down
35 changes: 33 additions & 2 deletions app/serializers/quizzes/quiz_statistics_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ class Input < Struct.new(:quiz, :student_analysis, :item_analysis)
# PS: this is always true for item analysis
:includes_all_versions,

:points_possible,

:speed_grader_url,
:quiz_submissions_zip_url,

# an aggregate of question stats from both student and item analysis
:question_statistics,

Expand All @@ -47,13 +52,15 @@ class Input < Struct.new(:quiz, :student_analysis, :item_analysis)
# - score_low
# - score_stdev
# - user_ids (id set)
:submission_statistics
:submission_statistics,
]

def_delegators :@controller,
:course_quiz_statistics_url,
:api_v1_course_quiz_url,
:api_v1_course_quiz_statistics_url
:api_v1_course_quiz_statistics_url,
:speed_grader_course_gradebook_url,
:course_quiz_quiz_submissions_url

has_one :quiz, embed: :ids

Expand Down Expand Up @@ -118,14 +125,38 @@ def includes_all_versions
object[:student_analysis].includes_all_versions
end

def points_possible
quiz.points_possible
end

def speed_grader_url
if show_speed_grader?
speed_grader_course_gradebook_url(quiz.context, {
assignment_id: quiz.assignment.id
})
end
end

def quiz_submissions_zip_url
course_quiz_quiz_submissions_url(quiz.context, quiz.id, zip: 1)
end

private

def show_speed_grader?
quiz.assignment.present? && quiz.published? && context.allows_speed_grader?
end

def student_analysis_report
@student_analysis_report ||= object[:student_analysis].report.generate(false)
end

def item_analysis_report
@item_analysis_report ||= object[:item_analysis].report.generate(false)
end

def quiz
object.quiz
end
end
end
1 change: 1 addition & 0 deletions app/stylesheets/client_apps/canvas_quiz_statistics.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "../../../client_apps/canvas_quiz_statistics/src/css/app";
1 change: 1 addition & 0 deletions app/views/layouts/_foot.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
paths: <%= raw Canvas::RequireJs.paths(true) %>,
packages : <%= raw Canvas::RequireJs.packages %>,
shim: <%= raw Canvas::RequireJs.shims %>,
map: <%= raw Canvas::RequireJs.map %>,
waitSeconds: 60
};
</script>
Expand Down
3 changes: 3 additions & 0 deletions app/views/quizzes/quizzes/statistics_cqs.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<% content_for :page_title, join_title(@quiz.title, t(:page_title, "Statistics CQS")) %>
<% jammit_css :quizzes, :canvas_quiz_statistics %>
<% js_bundle :quiz_statistics_cqs %>
5 changes: 5 additions & 0 deletions client_apps/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# --------------------------------------------------------------------------- #
# TOTALLY EXPERIMENTAL. DO NOT TOUCH. TOTALLY EXPERIMENTAL.
# --------------------------------------------------------------------------- #

Yes.
30 changes: 30 additions & 0 deletions client_apps/canvas_quiz_statistics/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[autogen]
/.sublime-project.sublime-workspace
/.jshint.html
/doc/api
/doc/coverage
/tests.html
*.swp

[build]
/tmp/*

[assets]
exclude
*.zip
*.gz

[env]
/.ruby-version
/.grunt/
/node_modules
/assets
/www/dist
/www/fixtures
/www/font
/www/images
/www/src
/www/vendor
/vendor/js/rsvp.js
/src/js/config/environments/development_local.js
/dist
2 changes: 2 additions & 0 deletions client_apps/canvas_quiz_statistics/.jshintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
src/js/main.js
src/js/bundles/**/*.js
78 changes: 78 additions & 0 deletions client_apps/canvas_quiz_statistics/.jshintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{
"maxerr" : 50,

"bitwise" : true,
"camelcase" : false,
"curly" : true,
"eqeqeq" : false,
"forin" : true,
"immed" : false,
"indent" : false,
"latedef" : true,
"newcap" : false,
"noarg" : true,
"noempty" : true,
"nonew" : false,
"plusplus" : false,
"quotmark" : false,

"undef" : true,
"unused" : true,
"strict" : false,
"trailing" : false,
"maxparams" : false,
"maxdepth" : false,
"maxstatements" : false,
"maxcomplexity" : false,
"maxlen" : false,

"asi" : false,
"boss" : false,
"debug" : false,
"eqnull" : false,
"es5" : false,
"esnext" : false,
"moz" : false,

"evil" : false,
"expr" : false,
"funcscope" : false,
"globalstrict" : false,
"iterator" : false,
"lastsemic" : false,
"laxbreak" : false,
"laxcomma" : false,
"loopfunc" : false,
"multistr" : false,
"proto" : false,
"scripturl" : false,
"smarttabs" : false,
"shadow" : false,
"sub" : false,
"supernew" : false,
"validthis" : false,

"browser" : true,
"couch" : false,
"devel" : false,
"dojo" : false,
"jquery" : false,
"mootools" : false,
"node" : false,
"nonstandard" : false,
"prototypejs" : false,
"rhino" : false,
"worker" : false,
"wsh" : false,
"yui" : false,

"nomen" : false,
"onevar" : false,
"passfail" : false,
"white" : false,

"globals" : {
"require": false,
"define": false
}
}
7 changes: 7 additions & 0 deletions client_apps/canvas_quiz_statistics/.stylishcolors
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"meta": "gray",
"reason": "cyan",
"verbose": "gray",
"error": "red",
"noproblem": "green"
}
37 changes: 37 additions & 0 deletions client_apps/canvas_quiz_statistics/.sublime-project
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"folders": [
{
"path": ".",
"folder_exclude_patterns": [
"build",
"node_modules",
"doc/api",
"doc/assets",
"doc/src",
"doc/vendor",
"doc/dist",
"./assets",
"www/dist",
"www/vendor",
"www/src",
"www/fixtures",
".bundle",
".git",
".grunt",
".yardoc",
"tmp",
"vendor/canvas",
".sass-cache"
],
"file_exclude_patterns": [
"*.sublime-workspace"
]
}
],
"settings": {
"tab_size": 2,
"translate_tabs_to_spaces": true,
"rulers": [ 80 ],
"trim_automatic_white_space": true
}
}
29 changes: 29 additions & 0 deletions client_apps/canvas_quiz_statistics/Gruntfile.development.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* jshint node:true */
var grunt = require('grunt');
var readPackage = function() {
return grunt.file.readJSON('package.json');
};

grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-connect-rewrite');
grunt.loadNpmTasks('grunt-connect-proxy');
grunt.loadNpmTasks('grunt-contrib-jasmine');
grunt.loadNpmTasks('grunt-jsduck');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-notify');
grunt.loadNpmTasks('grunt-newer');
grunt.loadNpmTasks('grunt-sass');

grunt.registerTask('default', [
'server',
'connect:tests',
'watch'
]);

grunt.registerTask('updatePkg', function () {
grunt.config.set('pkg', readPackage());
});

grunt.util.loadOptions('./tasks/development/options/');
grunt.util.loadTasks('./tasks/development');
62 changes: 62 additions & 0 deletions client_apps/canvas_quiz_statistics/Gruntfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* jshint node:true */

var glob = require('glob');
var grunt = require('grunt');

var readPackage = function() {
return grunt.file.readJSON('package.json');
};

var loadOptions = function(path) {
glob.sync('*', { cwd: path }).forEach(function(option) {
var key = option.replace(/\.js$/,'').replace(/^grunt\-/, '');
grunt.config.set(key, require(path + option));
});
};

var loadTasks = function(path) {
glob.sync('*.js', { cwd: path }).forEach(function(taskFile) {
var taskRunner;
var task = require(path + '/' + taskFile);
var taskName = taskFile.replace(/\.js$/, '');

taskRunner = task.runner;

if (taskRunner instanceof Function) {
taskRunner = taskRunner.bind(null, grunt);
}

grunt.registerTask(taskName, task.description, taskRunner);
});
};

module.exports = function() {
'use strict';

grunt.initConfig({
pkg: readPackage()
});

grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.loadNpmTasks('grunt-react');
grunt.loadNpmTasks('grunt-contrib-symlink');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');

grunt.appName = 'Canvas Quiz Statistics';
grunt.moduleId = 'canvas_quiz_statistics';
grunt.paths = {
canvasPackageShims: 'tmp/canvas_package_shims.json'
};

grunt.util.loadOptions = loadOptions;
grunt.util.loadTasks = loadTasks;

loadOptions('./tasks/options/');
loadTasks('./tasks');

// Unless invoked using `npm run [sub-script] --production`
if (process.env.NODE_ENV !== 'production') {
require('./Gruntfile.development');
}
};
Loading

0 comments on commit 80d627e

Please sign in to comment.