Skip to content

Commit

Permalink
improving the attempt timeline view
Browse files Browse the repository at this point in the history
Elements within a second of each other are grouped together, instead of
requiring exactly the same time.

The score column is only shown when the score changes, to more easily
identify times when the score changes.

The calls to get stuff from the datamodel from a certain time now just
run through looking for a particular element, instead of building the
whole data model.
  • Loading branch information
christianp committed Nov 25, 2020
1 parent 90828ce commit 346321b
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 15 deletions.
11 changes: 10 additions & 1 deletion numbas_lti/static/attempt_timeline.css
Expand Up @@ -8,7 +8,16 @@
background: #eee;
}

.item .exam-raw-score, .item .exam-scaled-score {
.item .exam-score {
text-align: right;
}

.exam-score.changed {
position: sticky;
top: 4em;
background: white;
}

#timeline .glyphicon {
position: static;
}
43 changes: 31 additions & 12 deletions numbas_lti/static/attempt_timeline.js
Expand Up @@ -51,7 +51,7 @@ function Timeline(elements, launches) {
var groups = [];
var current_group = null;
timeline.forEach(function(item) {
if(current_group==null || item.time!=current_group.time) {
if(current_group==null || item.time.diff(current_group.time).as('seconds')>1) {
current_group = {
time: item.time,
items: []
Expand All @@ -73,16 +73,19 @@ function Timeline(elements, launches) {
function score_for_item(item) {
return item_order.indexOf(item.kind);
}
var previous_exam_score = NaN;
groups.forEach(function(g) {
var exam_score = 0;
if(g.items.length) {
var element_items = g.items.filter(function(i){ return i.css.scorm });
var element = element_items.length ? element_items[element_items.length-1].element : {time: g.items[g.items.length-1].time, counter: Infinity};
var dm = tl.datamodel_at(element);
g.exam_raw_score = parseFloat(dm['cmi.score.raw'] || 0);
g.exam_max_score = parseFloat(dm['cmi.score.max'] || 0);
var exam_scaled_score = parseFloat(dm['cmi.score.scaled'] || 0);
g.exam_raw_score = parseFloat(tl.element_at('cmi.score.raw',element) || 0);
g.exam_max_score = parseFloat(tl.element_at('cmi.score.max',element) || 0);
var exam_scaled_score = parseFloat(tl.element_at('cmi.score.scaled',element) || 0);
g.exam_scaled_score = percentage(exam_scaled_score);
if(g.exam_raw_score!=previous_exam_score) {
g.exam_score_changed = true;
previous_exam_score = g.exam_raw_score;
}
}
g.items.sort(function(a,b) {
a = score_for_item(a);
Expand All @@ -94,6 +97,24 @@ function Timeline(elements, launches) {
},this);
}
Timeline.prototype = {
element_at: function(key,element) {
var t,counter;
if(!element) {
t = Infinity;
counter = Infinity;
} else {
t = (new Date(element.time))-0;
counter = element.counter;
}
var value;
this.all_elements().forEach(function(e) {
var et = (new Date(e.time))-0;
if(e.key==key && (et<t || et==t && e.counter<counter)) {
value = e.value;
}
});
return value;
},
datamodel_at: function(element) {
var t,counter;
if(!element) {
Expand All @@ -114,8 +135,7 @@ Timeline.prototype = {
},

suspend_data_at: function(element) {
var data = this.datamodel_at(element);
var json_suspend_data = data['cmi.suspend_data'];
var json_suspend_data = this.element_at('cmi.suspend_data',element);
if(json_suspend_data) {
return JSON.parse(json_suspend_data);
} else {
Expand All @@ -124,9 +144,8 @@ Timeline.prototype = {
},

getPart: function(id,element) {
var datamodel = this.datamodel_at(element);
var id_key = 'cmi.interactions.'+id+'.id';
var path = datamodel[id_key];
var path = this.element_at(id_key,element);
var desc = parse_part_path(path);
var suspend_data = this.suspend_data_at(element);
var part;
Expand All @@ -137,8 +156,8 @@ Timeline.prototype = {
} else if(!isNaN(desc.step)) {
p = p.steps[desc.step];
}
var part_type = datamodel['cmi.interactions.'+id+'.description'];
var marks = parseFloat(datamodel['cmi.interactions.'+id+'.weighting']);
var part_type = this.element_at('cmi.interactions.'+id+'.description',element);
var marks = parseFloat(this.element_at('cmi.interactions.'+id+'.weighting',element));
part = {
id: id,
name: p.name || path,
Expand Down
10 changes: 8 additions & 2 deletions numbas_lti/templates/numbas_lti/management/attempt_timeline.html
Expand Up @@ -39,10 +39,16 @@
<td class="time" data-bind="visible: $index()==0, attr: {rowspan: $parent.items.length}"><span data-bind="text: time_string"></span></td>
<td class="icon"><span data-bind="if: icon, attr: {'class': 'glyphicon glyphicon-'+ko.unwrap(icon)}"></span></td>
<td class="message"><span data-bind="html: message"></span></td>
<td class="exam-raw-score" data-bind="visible: $index()==0, attr: {rowspan: $parent.items.length}">
<td class="exam-score raw" data-bind="visible: $index()==0, css: {changed: $parent.exam_score_changed}, attr: {rowspan: $parent.items.length}">
<!-- ko if: $parent.exam_score_changed -->
<span data-bind="text: $parent.exam_raw_score"></span> / <span data-bind="text: $parent.exam_max_score"></span>
<!-- /ko -->
</td>
<td class="exam-score scaled" data-bind="visible: $index()==0, css: {changed: $parent.exam_score_changed}, attr: {rowspan: $parent.items.length}">
<!-- ko if: $parent.exam_score_changed -->
<span data-bind="text: $parent.exam_scaled_score"></span>
<!-- /ko -->
</td>
<td class="exam-scaled-score" data-bind="visible: $index()==0, attr: {rowspan: $parent.items.length}"><span data-bind="text: $parent.exam_scaled_score"></span></td>
</tr>
<!-- /ko -->
<!-- /ko -->
Expand Down

0 comments on commit 346321b

Please sign in to comment.