Skip to content

Commit cb402e1

Browse files
author
Nick Cloward
committed
ui - add option for course content changes (log auditing)
fixes: CNVS-10263 Adds a ui for CNVS-392. Since the event data is dynamic I added an option to view the details in a dialog window. We can add more types of events later. Test Plan: Setup: - Create a course - Change some details on the course. - Goto the course settings and change some fields in there. - Change the details a few more times to generate some data. - Conclude the course Security: - Open Admin tools, logging tab. - Drop down should include Course Activity. - Remove account level permissions for viewing course changes. - Open Admin tools, logging tab. - Drop down should not include Course Activity. - Add credentials back. Course Details: - Open Admin tools, logging tab. - Select the Course Acivity option from the drop down. - Search on a non existing course Id - Should return a popup for the invalid course id. - Search on the course id created above. - Should return events for details changed on the course. - One should be a created event. - A few should be updated events. - One should be a concluded event. - Click the View Details for one of the events. - Should show a dialog with the event information and event data. - Dialog should behave normally. - Search for events with a specific date range. - Should limit the results accordingly. Change-Id: I1a2e90eb0b2a8c906bb7e217fa0b99814ed58ff4 Reviewed-on: https://gerrit.instructure.com/28732 Reviewed-by: Nick Cloward <ncloward@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> QA-Review: Jeremy Putnam <jeremyp@instructure.com> QA-Review: August Thornton <august@instructure.com> Product-Review: Munda Frazier <munda@instructure.com>
1 parent 98b3f89 commit cb402e1

18 files changed

Lines changed: 622 additions & 69 deletions
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#
2+
# Copyright (C) 2013 Instructure, Inc.
3+
#
4+
# This file is part of Canvas.
5+
#
6+
# Canvas is free software: you can redistribute it and/or modify it under
7+
# the terms of the GNU Affero General Public License as published by the Free
8+
# Software Foundation, version 3 of the License.
9+
#
10+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
11+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13+
# details.
14+
#
15+
# You should have received a copy of the GNU Affero General Public License along
16+
# with this program. If not, see <http://www.gnu.org/licenses/>.
17+
#
18+
19+
define [
20+
'compiled/collections/PaginatedCollection'
21+
'compiled/models/CourseEvent'
22+
], (PaginatedCollection, CourseEvent) ->
23+
24+
class CourseLoggingCollection extends PaginatedCollection
25+
model: CourseEvent
26+
27+
url: ->
28+
"/api/v1/audit/course/courses/#{@options.params.id}"
29+
30+
sideLoad:
31+
course: true
32+
user: true
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
define [
2+
'underscore'
3+
'Backbone'
4+
'i18n!course_logging'
5+
], (_, Backbone, I18n) ->
6+
class CourseEvent extends Backbone.Model
7+
present: ->
8+
json = Backbone.Model::toJSON.call(@)
9+
data = {}
10+
iterator = (dataValue, dataKey) =>
11+
dataKey = @presentLabel(dataKey)
12+
data[dataKey] = @presentField(dataValue)
13+
14+
switch json.event_type
15+
when "created"
16+
json.event_type_present = I18n.t("event_type.created", "Created")
17+
iterator = (dataValues, dataKey) =>
18+
dataKey = @presentLabel(dataKey)
19+
data[dataKey] = @presentField(_.last(dataValues))
20+
when "updated"
21+
json.event_type_present = I18n.t("event_type.updated", "Updated")
22+
iterator = (dataValues, dataKey) =>
23+
dataKey = @presentLabel(dataKey)
24+
data[dataKey] = _.object([ "from", "to" ], @presentField(dataValues))
25+
when "concluded"
26+
json.event_type_present = I18n.t("event_type.concluded", "Concluded")
27+
else
28+
json.event_type_present = json.event_type
29+
30+
_.each json.event_data, iterator
31+
json.event_data = data unless _.isEmpty(data)
32+
return json
33+
34+
presentField: (value) ->
35+
blank = "-"
36+
return blank if _.isNull(value)
37+
return value.toString() if _.isBoolean(value)
38+
if _.isArray(value)
39+
return _.map value, @presentField, @
40+
if _.isString(value)
41+
return blank if !value.length
42+
if value.match /^\d{4}-\d{2}-\d{2}(T| )\d{2}:\d{2}:\d{2}(.\d+)?Z$/
43+
return I18n.l("date.formats.medium", value) + " " + I18n.l("time.formats.tiny", value)
44+
return value
45+
46+
presentLabel: (label) ->
47+
switch label.toLowerCase()
48+
when "name"
49+
I18n.t("field_label.name", "Name")
50+
when "account_id"
51+
I18n.t("field_label.account_id", "Account Id")
52+
when "group_weighting_scheme"
53+
I18n.t("field_label.group_weighting_scheme", "Group Weighting Scheme")
54+
when "old_account_id"
55+
I18n.t("field_label.old_account_id", "Old Account Id")
56+
when "workflow_state"
57+
I18n.t("field_label.workflow_state", "Workflow State")
58+
when "uuid"
59+
I18n.t("field_label.uuid", "UUID")
60+
when "start_at"
61+
I18n.t("field_label.start_at", "Start At")
62+
when "conclude_at"
63+
I18n.t("field_label.conclude_at", "Concluded At")
64+
when "grading_standard_id"
65+
I18n.t("field_label.grading_standard_id", "Grading Standard Id")
66+
when "is_public"
67+
I18n.t("field_label.is_public", "Is Public")
68+
when "allow_student_wiki_edits"
69+
I18n.t("field_label.allow_student_wiki_edits", "Allow Student Wiki Edit")
70+
when "created_at"
71+
I18n.t("field_label.created_at", "Created At")
72+
when "updated_at"
73+
I18n.t("field_label.updated_at", "Updated At")
74+
when "show_public_context_messages"
75+
I18n.t("field_label.show_public_context_messages", "Show Public Context Message")
76+
when "syllabus_body"
77+
I18n.t("field_label.syllabus_body", "syllabus_body")
78+
when "allow_student_forum_attachments"
79+
I18n.t("field_label.allow_student_forum_attachments", "Allow Student Forum Attachments")
80+
when "default_wiki_editing_roles"
81+
I18n.t("field_label.default_wiki_editing_roles", "Default Wiki Editing Roles")
82+
when "wiki_id"
83+
I18n.t("field_label.wiki_id", "Wiki Id")
84+
when "allow_student_organized_groups"
85+
I18n.t("field_label.allow_student_organized_groups", "Allow Student Organized Groups")
86+
when "course_code"
87+
I18n.t("field_label.course_code", "Course Code")
88+
when "default_view"
89+
I18n.t("field_label.default_view", "Default View")
90+
when "abstract_course_id"
91+
I18n.t("field_label.abstract_course_id", "Abstract Course Id")
92+
when "root_account_id"
93+
I18n.t("field_label.root_account_id", "Root Account Id")
94+
when "enrollment_term_id"
95+
I18n.t("field_label.enrollment_term_id", "Enrollment Term Id")
96+
when "sis_source_id"
97+
I18n.t("field_label.sis_source_id", "SIS Source Id")
98+
when "sis_batch_id"
99+
I18n.t("field_label.sis_batch_id", "SIS Batch Id")
100+
when "show_all_discussion_entries"
101+
I18n.t("field_label.show_all_discussion_entries", "Show All Discussion Entries")
102+
when "open_enrollment"
103+
I18n.t("field_label.open_enrollment", "Open Enrollment")
104+
when "storage_quota"
105+
I18n.t("field_label.storage_quota", "Storage Quota")
106+
when "tab_configuration"
107+
I18n.t("field_label.tab_configuration", "Tab Configuration")
108+
when "allow_wiki_comments"
109+
I18n.t("field_label.allow_wiki_comments", "Allow Wiki Comments")
110+
when "turnitin_comments"
111+
I18n.t("field_label.turnitin_comments", "Turnitin Comments")
112+
when "self_enrollment"
113+
I18n.t("field_label.self_enrollment", "Self Enrollment")
114+
when "license"
115+
I18n.t("field_label.license", "License")
116+
when "indexed"
117+
I18n.t("field_label.indexed", "Indexed")
118+
when "restrict_enrollments_to_course_dates"
119+
I18n.t("field_label.restrict_enrollments_to_course_dates", "Restrict Enrollments To Course Dates")
120+
when "template_course_id"
121+
I18n.t("field_label.template_course_id", "Template Course Id")
122+
when "locale"
123+
I18n.t("field_label.locale", "Locale")
124+
when "replacement_course_id"
125+
I18n.t("field_label.replacement_course_id", "Replacement Course Id")
126+
when "public_description"
127+
I18n.t("field_label.public_description", "Public Description")
128+
when "self_enrollment_code"
129+
I18n.t("field_label.self_enrollment_code", "Self Enrollment Code")
130+
when "self_enrollment_limit"
131+
I18n.t("field_label.self_enrollment_limit", "Self Enrollment Limit")
132+
when "integration_id"
133+
I18n.t("field_label.integration_id", "Integration Id")
134+
when "hide_final_grade"
135+
I18n.t("field_label.hide_final_grade", "Hide Final Grade")
136+
when "hide_distribution_graphs"
137+
I18n.t("field_label.hide_distribution_graphs", "Hide Distribution Graphs")
138+
when "allow_student_discussion_topics"
139+
I18n.t("field_label.allow_student_discussion_topics", "Allow Student Discussion Topics")
140+
when "allow_student_discussion_editing"
141+
I18n.t("field_label.allow_student_discussion_editing", "Allow Student Discussion Editing")
142+
when "lock_all_announcements"
143+
I18n.t("field_label.lock_all_announcements", "Lock All Announcements")
144+
when "large_roster"
145+
I18n.t("field_label.large_roster", "Large Roster")
146+
when "public_syllabus"
147+
I18n.t("field_label.public_syllabus", "Public Syllabus")
148+
else
149+
label

app/coffeescripts/views/accounts/admin_tools/AutocompleteView.coffee

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ define [
1414
constructor: (@options) ->
1515
@collection = @options.collection
1616
super
17+
1718
@options.minLength ||= 3
1819
@options.labelProperty ||= 'name'
1920
@options.valueProperty ||= 'id'
@@ -26,20 +27,21 @@ define [
2627
afterRender: ->
2728
@$searchTerm.autocomplete
2829
minLength: @options.minLength
29-
source: $.proxy(@autocompleteSource, @)
3030
select: $.proxy(@autocompleteSelect, @)
31+
source: $.proxy(@autocompleteSource, @)
3132
change: $.proxy(@autocompleteSelect, @)
3233

3334
autocompleteSource: (request, response) ->
3435
@$searchTerm.addClass("loading")
3536
params = data:
3637
search_term: request.term
3738
labelProperty = @options.labelProperty
38-
valueProperty = @options.valueProperty
3939
success = ->
4040
items = @collection.map (item) ->
41+
label = labelProperty(item) if $.isFunction(labelProperty)
42+
label ||= item.get(labelProperty)
4143
model: item
42-
label: item.get(labelProperty)
44+
label: label
4345
@$searchTerm.removeClass("loading")
4446
response(items)
4547
@collection.fetch(params).success $.proxy(success, @)
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
define [
2+
'Backbone'
3+
'jquery'
4+
'compiled/views/PaginatedCollectionView'
5+
'compiled/views/accounts/admin_tools/DateRangeSearchView'
6+
'compiled/views/accounts/admin_tools/AutocompleteView'
7+
'compiled/views/ValidatedMixin'
8+
'compiled/views/accounts/admin_tools/CourseLoggingItemView'
9+
'compiled/collections/CourseLoggingCollection'
10+
'compiled/collections/CourseCollection'
11+
'jst/accounts/admin_tools/courseLoggingContent'
12+
'jst/accounts/admin_tools/courseLoggingResults'
13+
'jst/accounts/admin_tools/courseLoggingDetails'
14+
'jqueryui/dialog'
15+
], (
16+
Backbone,
17+
$,
18+
PaginatedCollectionView,
19+
DateRangeSearchView,
20+
AutocompleteView,
21+
ValidatedMixin,
22+
CourseLoggingItemView,
23+
CourseLoggingCollection,
24+
CourseCollection,
25+
template,
26+
courseLoggingResultsTemplate,
27+
detailsTemplate
28+
) ->
29+
class CourseLoggingContentView extends Backbone.View
30+
@mixin ValidatedMixin
31+
32+
@child 'resultsView', '#courseLoggingSearchResults'
33+
@child 'dateRangeSearch', '#courseDateRangeSearch'
34+
@child 'courseSearch', '#courseCourseSearch'
35+
 
36+
els:
37+
'#courseLoggingForm': '$form'
38+
39+
template: template
40+
detailsTemplate: detailsTemplate
41+
42+
constructor: (@options) ->
43+
@collection = new CourseLoggingCollection
44+
super
45+
@dateRangeSearch = new DateRangeSearchView
46+
name: "courseLogging"
47+
@courseSearch = new AutocompleteView
48+
collection: new Backbone.Collection null, resourceName: 'courses'
49+
labelProperty: $.proxy(@autoCompleteItemLabel, @)
50+
fieldName: 'course_id'
51+
placeholder: 'Course'
52+
@resultsView = new PaginatedCollectionView
53+
template: courseLoggingResultsTemplate
54+
itemView: CourseLoggingItemView
55+
collection: @collection
56+
57+
events:
58+
'submit #courseLoggingForm': 'onSubmit'
59+
'click #courseLoggingSearchResults .courseLoggingDetails > a': 'showDetails'
60+
61+
onSubmit: (event) ->
62+
event.preventDefault()
63+
json = @$form.toJSON()
64+
if @validate(json)
65+
@updateCollection(json)
66+
67+
showDetails: (event) ->
68+
event.preventDefault()
69+
$target = $(event.target)
70+
id = $target.data("id")
71+
72+
model = @collection.get(id)
73+
unless model?
74+
console.warn("Could not find model for event #{id}.")
75+
return
76+
77+
type = model.get("event_type")
78+
unless type?
79+
console.warn("Could not find type for event #{id}.")
80+
return
81+
82+
@dialog = $('<div class="use-css-transitions-for-show-hide" style="padding:0;"/>')
83+
@dialog.html(@detailsTemplate(model.present()))
84+
config =
85+
title: "Event Details"
86+
width: 600
87+
resizable: true
88+
@dialog.dialog(config)
89+
90+
updateCollection: (json) ->
91+
# Update the params (which fetches the collection)
92+
json ||= @$form.toJSON()
93+
94+
params =
95+
id: null
96+
type: null
97+
start_time: ''
98+
end_time: ''
99+
100+
params.start_time = json.start_time if json.start_time
101+
params.end_time = json.end_time if json.end_time
102+
103+
params.id = json.course_id if json.course_id
104+
105+
@collection.setParams params
106+
107+
validate: (json) ->
108+
json ||= @$form.toJSON()
109+
delete json.course_submit
110+
errors = @dateRangeSearch.validate(json) || {}
111+
112+
json.course_id ||= @$el.find("#course_id-autocompleteField").val()
113+
if !json.course_id
114+
errors['course_submit'] = [{
115+
type: 'required'
116+
message: 'A valid Course is required to search events.'
117+
}]
118+
119+
@showErrors errors
120+
return $.isEmptyObject(errors)
121+
122+
attach: ->
123+
@collection.on 'setParams', @fetch
124+
125+
fetch: =>
126+
@collection.fetch().fail @onFail
127+
128+
onFail: (xhr) =>
129+
# Received a 404, empty the collection and don't let the paginated
130+
# view try to fetch more.
131+
132+
@collection.reset()
133+
@resultsView.detachScroll()
134+
@resultsView.$el.find(".paginatedLoadingIndicator").fadeOut()
135+
136+
if xhr?.status? && xhr.status == 404
137+
errors = {}
138+
errors['course_submit'] = [{
139+
type: 'required'
140+
message: 'A course with that ID could not be found for this account.'
141+
}]
142+
@showErrors errors unless $.isEmptyObject(errors)
143+
144+
autoCompleteItemLabel: (model) ->
145+
name = model.get("name")
146+
code = model.get("course_code")
147+
"#{model.id} - #{name} - #{code}"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
define [
2+
'jquery'
3+
'Backbone'
4+
'i18n!course_logging'
5+
'jst/accounts/admin_tools/courseLoggingItem'
6+
], ($, Backbone, I18n, template) ->
7+
class CourseLoggingItemView extends Backbone.View
8+
tagName: 'tr'
9+
className: 'logitem'
10+
template: template

app/coffeescripts/views/accounts/admin_tools/GradeChangeLoggingContentView.coffee

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ define [
4141
@collection = new GradeChangeLoggingCollection
4242
super
4343
@dateRangeSearch = new DateRangeSearchView
44+
name: "gradeChangeLogging"
4445
@graderSearch = new AutocompleteView
4546
collection: @options.users
4647
fieldName: 'grader_id'

app/coffeescripts/views/accounts/admin_tools/GradeChangeLoggingItemView.coffee

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
define [
22
'Backbone'
33
'jst/accounts/admin_tools/gradeChangeLoggingItem'
4-
'i18n!auth_logging'
5-
], (Backbone, template, I18n) ->
4+
], (Backbone, template) ->
65
class GradeChangeLoggingItemView extends Backbone.View
76
tagName: 'tr'
87
className: 'logitem'

0 commit comments

Comments
 (0)