Skip to content

Commit db99de4

Browse files
committed
Quiz Submission Questions - Index/Show
This patch makes it possible to list the "question records" for a QS which clients can use to tell what questions are answered, flagged, etc. closes CNVS-10025 TEST PLAN ---- ---- Fire up some computer, launch a Canvas instance, create a quiz with a question or two, take it as a student, answer it, then get ready for API business: > Index action - Listing all question records [GET] api/v1/quiz_submissions/:quiz_submission_id/questions - Perform the request - Verify you get N entries, one for each question you answered or marked - Set an "include" parameter with "quiz_question" (ie, "?include=user") - Verify that the returned set now contains 3 entries: 1. "quiz_submission_questions" what you saw earlier 2. "quiz_questions" with Quiz Questions that you answered 3. "meta" that you can safely ignore > Show action - Fetching a single question record [GET] /api/v1/quiz_submissions/:quiz_submission_id/questions/:id - Perform the request - Verify you get 1 entry - Try the "include" parameter as you did in the Index case As a sidenote, the output of these endpoints should be the same as the stuff you get when you answer a question (see CNVS-9844), they basically should tell you what answer you provided if you did, and if you marked the question or not. Change-Id: If39b84d8fda77870c3990822ade06bd28b276c3d Reviewed-on: https://gerrit.instructure.com/27598 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Derek DeVries <ddevries@instructure.com> QA-Review: Myller de Araujo <myller@instructure.com> Product-Review: Ahmad Amireh <ahmad@instructure.com>
1 parent df1efd6 commit db99de4

5 files changed

Lines changed: 242 additions & 14 deletions

File tree

app/controllers/quiz_submission_questions_controller.rb

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,91 @@
1919
# @API Quiz Submission Questions
2020
# @beta
2121
#
22-
22+
# API for answering and flagging questions in a quiz-taking session.
23+
#
24+
# @model QuizSubmissionQuestion
25+
# {
26+
# "id": "QuizSubmissionQuestion",
27+
# "required": ["id"],
28+
# "properties": {
29+
# "id": {
30+
# "description": "The ID of the QuizQuestion this answer is for.",
31+
# "example": 1,
32+
# "type": "integer",
33+
# "format": "int64"
34+
# },
35+
# "flagged": {
36+
# "description": "Whether this question is flagged.",
37+
# "example": true,
38+
# "type": "boolean"
39+
# },
40+
# "answer": {
41+
# "description": "The provided answer (if any) for this question. The format of this parameter depends on the type of the question, see the Appendix for more information."
42+
# }
43+
# }
44+
# }
45+
#
2346
class QuizSubmissionQuestionsController < ApplicationController
2447
include Api::V1::QuizSubmissionQuestion
2548
include Api::V1::Helpers::QuizzesApiHelper
2649
include Api::V1::Helpers::QuizSubmissionsApiHelper
2750

28-
before_filter :require_user,
29-
:require_quiz_submission,
30-
:export_scopes,
31-
:prepare_service,
32-
:validate_ldb_status!,
33-
:require_question
51+
before_filter :require_user, :require_quiz_submission, :export_scopes
52+
before_filter :require_question, except: [ :index ]
53+
before_filter :prepare_service, except: [ :index, :show ]
54+
before_filter :validate_ldb_status!, except: [ :index, :show ]
55+
56+
# @API Get all quiz submission questions.
57+
# @beta
58+
#
59+
# Get a list of all the question records for this quiz submission.
60+
#
61+
# @argument include[] [String, "quiz_question"]
62+
# Associations to include with the quiz submission question.
63+
#
64+
# <b>200 OK</b> response code is returned if the request was successful.
65+
#
66+
# @example_response
67+
# {
68+
# "quiz_submission_questions": [QuizSubmissionQuestion]
69+
# }
70+
def index
71+
if authorized_action(@quiz_submission, @current_user, :read)
72+
render json: quiz_submission_questions_json(@quiz.quiz_questions,
73+
@quiz_submission.submission_data,
74+
{
75+
user: @current_user,
76+
session: session,
77+
includes: extract_includes
78+
})
79+
end
80+
end
81+
82+
# @API Get a single quiz submission question.
83+
# @beta
84+
#
85+
# Get a single question record.
86+
#
87+
# @argument include[] [String, "quiz_question"]
88+
# Associations to include with the quiz submission question.
89+
#
90+
# <b>200 OK</b> response code is returned if the request was successful.
91+
#
92+
# @example_response
93+
# {
94+
# "quiz_submission_questions": [QuizSubmissionQuestion]
95+
# }
96+
def show
97+
if authorized_action(@quiz_submission, @current_user, :read)
98+
render json: quiz_submission_questions_json(@question,
99+
@quiz_submission.submission_data,
100+
{
101+
user: @current_user,
102+
session: session,
103+
includes: extract_includes
104+
})
105+
end
106+
end
34107

35108
# @API Answering a question.
36109
# @beta
@@ -191,6 +264,10 @@ def flag_current_question(flagged_unflagged)
191264
false)
192265
end
193266

267+
def extract_includes(key = :include, hash = params)
268+
Array(hash[key])
269+
end
270+
194271
# @!appendix Question Answer Formats
195272
#
196273
# {include:file:doc/examples/quiz_question_answers.md}

config/routes.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,6 +1353,8 @@ def ef_routes(context)
13531353
end
13541354

13551355
scope(:controller => :quiz_submission_questions) do
1356+
get '/quiz_submissions/:quiz_submission_id/questions', :action => :index, :path_name => 'quiz_submission_questions'
1357+
get '/quiz_submissions/:quiz_submission_id/questions/:id', :action => :show, :path_name => 'quiz_submission_question'
13561358
put '/quiz_submissions/:quiz_submission_id/questions/:id', :action => :answer, :path_name => 'quiz_submission_question_answer'
13571359
put '/quiz_submissions/:quiz_submission_id/questions/:id/flag', :action => :flag, :path_name => 'quiz_submission_question_flag'
13581360
put '/quiz_submissions/:quiz_submission_id/questions/:id/unflag', :action => :unflag, :path_name => 'quiz_submission_question_unflag'

lib/api/v1/quiz_submission_question.rb

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,38 @@
1717
#
1818

1919
module Api::V1::QuizSubmissionQuestion
20+
include Api::V1::QuizQuestion
2021

21-
def quiz_submission_questions_json(quiz_questions, submission_data)
22-
{
23-
quiz_submission_questions: quiz_questions.map do |qq|
24-
quiz_submission_question_json(qq, submission_data)
25-
end
26-
}
22+
Includables = %w[ quiz_question ]
23+
24+
# @param [Array<QuizQuestion>] quiz_questions
25+
# @param [Hash] submission_data
26+
# @param [Hash] meta
27+
# @param [Array<String>] meta[:includes]
28+
# @param [User] meta[:user]
29+
# @param [Hash] meta[:session]
30+
def quiz_submission_questions_json(quiz_questions, submission_data, meta = {})
31+
quiz_questions = [ quiz_questions ] unless quiz_questions.kind_of?(Array)
32+
includes = (meta[:includes] || []) & Includables
33+
34+
data = {}
35+
data[:quiz_submission_questions] = quiz_questions.map do |qq|
36+
quiz_submission_question_json(qq, submission_data)
37+
end
38+
39+
if includes.include? 'quiz_question'
40+
data[:quiz_questions] = questions_json(quiz_questions,
41+
meta[:user],
42+
meta[:session])
43+
end
44+
45+
unless includes.empty?
46+
data[:meta] = {
47+
primaryCollection: 'quiz_submission_questions'
48+
}
49+
end
50+
51+
data
2752
end
2853

2954
# A renderable version of a QuizQuestion's "answer" record in a submission's

spec/apis/api_spec_helper.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,13 @@ def assert_jsonapi_compliance(json, primary_set, associations = [])
239239
required_keys << 'meta'
240240
end
241241

242+
# test key values instead of nr. of keys so we get meaningful failures
243+
json.keys.sort.should == required_keys.sort
244+
242245
required_keys.each do |key|
243246
json.should be_has_key(key)
244247
json[key].is_a?(Array).should be_true unless key == 'meta'
245248
end
246-
json.size.should == required_keys.size
247249

248250
if associations.any?
249251
json['meta']['primaryCollection'].should == primary_set

spec/apis/v1/quiz_submission_questions_api_spec.rb

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,39 @@ def create_question(type, factory_options = {}, quiz=@quiz)
3737
qq
3838
end
3939

40+
def ask_and_answer_stuff
41+
@qq1 = create_question 'multiple_choice'
42+
@qq2 = create_question 'true_false'
43+
44+
@quiz_submission.submission_data = {
45+
"question_#{@qq1.id}" => "1658",
46+
"question_#{@qq2.id}" => "8950"
47+
}
48+
end
49+
50+
def api_index(data = {}, options = {})
51+
helper = method(options[:raw] ? :raw_api_call : :api_call)
52+
helper.call(:get,
53+
"/api/v1/quiz_submissions/#{@quiz_submission.id}/questions",
54+
{ :controller => 'quiz_submission_questions',
55+
:action => 'index',
56+
:format => 'json',
57+
:quiz_submission_id => @quiz_submission.id.to_s
58+
}, data)
59+
end
60+
61+
def api_show(data = {}, options = {})
62+
helper = method(options[:raw] ? :raw_api_call : :api_call)
63+
helper.call(:get,
64+
"/api/v1/quiz_submissions/#{@quiz_submission.id}/questions/#{@question[:id]}",
65+
{ :controller => 'quiz_submission_questions',
66+
:action => 'show',
67+
:format => 'json',
68+
:quiz_submission_id => @quiz_submission.id.to_s,
69+
:id => @question[:id].to_s
70+
}, data)
71+
end
72+
4073
def api_update(data = {}, options = {})
4174
data = {
4275
validation_token: @quiz_submission.validation_token,
@@ -97,6 +130,95 @@ def api_unflag(data = {}, options = {})
97130
@quiz_submission = @quiz.generate_submission(@student)
98131
end
99132

133+
describe 'GET /quiz_submissions/:quiz_submission_id/questions [index]' do
134+
it 'should return an empty list' do
135+
json = api_index
136+
json.has_key?('quiz_submission_questions').should be_true
137+
json['quiz_submission_questions'].size.should == 0
138+
end
139+
140+
it 'should list all items' do
141+
ask_and_answer_stuff
142+
143+
json = api_index
144+
json['quiz_submission_questions'].size.should == 2
145+
end
146+
147+
it 'should restrict access to itself' do
148+
student_in_course
149+
json = api_index({}, { raw: true })
150+
response.status.to_i.should == 401
151+
end
152+
end
153+
154+
describe 'GET /quiz_submissions/:quiz_submission_id/questions/:id [show]' do
155+
before :each do
156+
ask_and_answer_stuff
157+
@question = @qq1
158+
end
159+
160+
it 'should grant access to its student' do
161+
json = api_show
162+
json.has_key?('quiz_submission_questions').should be_true
163+
json['quiz_submission_questions'].length.should == 1
164+
end
165+
166+
it 'should deny access by other students' do
167+
student_in_course
168+
api_show({}, { raw: true })
169+
response.status.to_i.should == 401
170+
end
171+
172+
context 'Output' do
173+
it 'should include the quiz question id' do
174+
json = api_show
175+
json.has_key?('quiz_submission_questions').should be_true
176+
json['quiz_submission_questions'][0]['id'].should ==
177+
@question.id
178+
end
179+
180+
it 'should include the flagged status' do
181+
json = api_show
182+
json.has_key?('quiz_submission_questions').should be_true
183+
json['quiz_submission_questions'][0].has_key?('flagged').should be_true
184+
json['quiz_submission_questions'][0]['flagged'].should be_false
185+
end
186+
end
187+
188+
context 'Links' do
189+
it 'should include its linked quiz_question' do
190+
json = api_show({
191+
:include => %w[ quiz_question ]
192+
})
193+
194+
json.has_key?('quiz_submission_questions').should be_true
195+
json['quiz_submission_questions'].size.should == 1
196+
197+
json.has_key?('quiz_questions').should be_true
198+
json['quiz_questions'].size.should == 1
199+
json['quiz_questions'][0]['id'].should ==
200+
json['quiz_submission_questions'][0]['id']
201+
end
202+
end
203+
204+
context 'JSON-API compliance' do
205+
it 'should conform to the JSON-API spec when returning the object' do
206+
json = api_show
207+
assert_jsonapi_compliance(json, 'quiz_submission_questions')
208+
end
209+
210+
it 'should conform to the JSON-API spec when returning linked objects' do
211+
includables = Api::V1::QuizSubmissionQuestion::Includables
212+
213+
json = api_show({
214+
:include => includables
215+
})
216+
217+
assert_jsonapi_compliance(json, 'quiz_submission_questions', includables)
218+
end
219+
end
220+
end
221+
100222
describe 'PUT /quiz_submissions/:quiz_submission_id/questions/:id [update]' do
101223
context 'answering questions' do
102224
it 'should answer a MultipleChoice question' do

0 commit comments

Comments
 (0)