Skip to content

Commit

Permalink
SAK-34040 replace assignment stats with new chart (sakaiproject#5582)
Browse files Browse the repository at this point in the history
* SAK-34039 refresh chart when grading schema type changes

* SAK-34045 Default colour to that of the morpheus buttons

* SAK-34040 refactor the course grade chart so that the labels and titles are provided and can be internationalised. Lays the foundation for using the same chart with the assignment stats.

* SAK-34040 refactored charting from mix of Wicket and javascript to pure Wicket.
Removed the entity broker call for course grade summary data so it is encapsulated in the wicket component.
Added a new wicket component for the assignment grade chart which can follow the same pattern.
Removed unneeded Javascript calls in the grade chart js so we now only have a single responsibility and can flex based on the chart data being passed in.

* SAK-34040 additional abstraction for base chart class.
Immediately render any subclass.
Remove the id from the assignment variant as it is autogenerated now.

* SAK-34040 finish off the conversion of the assignment grade chart.
  • Loading branch information
steveswinsburg authored May 9, 2018
1 parent 51963ce commit 264947d
Show file tree
Hide file tree
Showing 11 changed files with 383 additions and 475 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,8 @@ settingspage.graderelease.heading = Grade Release Rules
settingspage.categories.heading = Categories & Weighting
settingspage.gradingschema.heading = Grading Schema
settingspage.gradingschema.chart.heading = Course Grade Distribution
settingspage.gradingschema.chart.xaxis = Course Grade
settingspage.gradingschema.chart.xaxis = Number of Students
settingspage.gradingschema.chart.yaxis = Course Grade
settingspage.gradingschema.overrides = Course grade overrides exist for the following students which may affect the chart distribution:
settingspage.gradingschema.modified.note = This grading schema has been modified from the default.
settingspage.gradingschema.modified.warning = There are unsaved changes.
Expand Down Expand Up @@ -509,6 +510,8 @@ label.statistics.chart.extracredit = EC
label.statistics.chart.xaxis = Percentage Scored
label.statistics.chart.yaxis = Number of Students
label.statistics.chart.tooltip = {2} score(s) in {1} range
label.statistics.chart.range={0}-{1}
label.statistics.chart.title=Grade Distribution

label.statistics.coursegrade.title = Course Grade Statistics
label.statistics.coursegrade.averagegpa = Course Average GPA
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@
*/
package org.sakaiproject.gradebookng.rest;

import java.lang.reflect.Type;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
Expand All @@ -40,16 +37,9 @@
import org.sakaiproject.gradebookng.business.GradebookNgBusinessService;
import org.sakaiproject.gradebookng.business.exception.GbAccessDeniedException;
import org.sakaiproject.gradebookng.business.model.GbGradeCell;
import org.sakaiproject.gradebookng.rest.model.CourseGradeSummary;
import org.sakaiproject.service.gradebook.shared.CourseGrade;
import org.sakaiproject.service.gradebook.shared.GradeMappingDefinition;
import org.sakaiproject.service.gradebook.shared.GradebookInformation;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.tool.api.SessionManager;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

Expand Down Expand Up @@ -197,51 +187,6 @@ public String getComments(final EntityView view, final Map<String, Object> param
return this.businessService.getAssignmentGradeComment(siteId, assignmentId, studentUuid);
}

@SuppressWarnings("unused")
@EntityCustomAction(action = "course-grades", viewKey = EntityView.VIEW_LIST)
public CourseGradeSummary getCourseGradeSummary(final EntityView view, final Map<String, Object> params) {

// get params
final String siteId = (String) params.get("siteId");
final String schema = (String) params.get("schema");

log.debug("Schema json:" + schema);

checkValidSite(siteId);
checkInstructor(siteId);

// if we have a schema provided, deserialise
Map<String, Double> gradingSchema = null;
if (StringUtils.isNotBlank(schema)) {
final Gson gson = new Gson();
final Type mappingType = new TypeToken<LinkedHashMap<String, Double>>() {
}.getType();
gradingSchema = gson.fromJson(schema, mappingType);

log.debug("provided gradeMap:" + gradingSchema);

if (gradingSchema == null) {
throw new IllegalArgumentException("Grading schema data was missing / invalid");
}
}

// if still null, use the persistent one for this gradebook
if (gradingSchema == null) {
log.debug("gradeMap not provided, using persistent one");
final GradebookInformation info = this.businessService.getGradebookSettings(siteId);
gradingSchema = info.getSelectedGradingScaleBottomPercents();
log.debug("persistent gradeMap:" + gradingSchema);
}

// ensure grading schema is sorted so the grade mapping works correctly
gradingSchema = GradeMappingDefinition.sortGradeMapping(gradingSchema);

// get the course grades and re-map to summary. Also sorts the data so it is ready for the consumer to use
final Map<String, CourseGrade> courseGrades = this.businessService.getCourseGrades(siteId, gradingSchema);

return reMap(courseGrades, gradingSchema.keySet());
}

/**
* Helper to check if the user is an instructor. Throws IllegalArgumentException if not. We don't currently need the value that this
* produces so we don't return it.
Expand Down Expand Up @@ -327,35 +272,6 @@ private GbRole getUserRole(final String siteId) {
return role;
}

/**
* Re-map the course grades returned from the business service into our CourseGradeSummary object for returning on the REST API.
*
* @param courseGrades map of student to course grade
* @param gradingSchema the grading schema that has the order
* @return
*/
private CourseGradeSummary reMap(final Map<String, CourseGrade> courseGrades, final Set<String> order) {
final CourseGradeSummary summary = new CourseGradeSummary();
courseGrades.forEach((k,v) -> {
summary.add(v.getDisplayGrade());
});

//sort the map based on the ordered schema
final Map<String, Integer> originalData = summary.getDataset();
final Map<String, Integer> sortedData = new LinkedHashMap<>();
order.forEach(o -> {
// data set must contain everything in the grading schema
Integer value = originalData.get(o);
if (value == null) {
value = 0;
}
sortedData.put(o, value);
});
summary.setDataset(sortedData);

return summary;
}

@Setter
private SiteService siteService;

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package org.sakaiproject.gradebookng.tool.component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.wicket.model.StringResourceModel;
import org.sakaiproject.gradebookng.business.model.GbGradeInfo;
import org.sakaiproject.gradebookng.business.model.GbStudentGradeInfo;
import org.sakaiproject.gradebookng.business.util.MessageHelper;
import org.sakaiproject.gradebookng.tool.model.GbChartData;
import org.sakaiproject.service.gradebook.shared.Assignment;
import org.sakaiproject.service.gradebook.shared.GradingType;

/**
* Panel that renders the individual assignment grade charts
*/
public class GbAssignmentGradeChart extends GbBaseChart {

private static final long serialVersionUID = 1L;

private final long assignmentId;

public GbAssignmentGradeChart(final String id, final long assignmentId) {
super(id);
this.assignmentId = assignmentId;
}

/**
* Get chart data for this site
*
* @return
*/
@Override
protected GbChartData getData() {

final GradingType gradingType = GradingType.valueOf(this.businessService.getGradebook().getGrade_type());
final Assignment assignment = this.businessService.getAssignment(this.assignmentId);
final List<GbStudentGradeInfo> gradeInfo = this.businessService.buildGradeMatrix(Arrays.asList(assignment));

// get all grades for this assignment
final List<Double> allGrades = new ArrayList<>();
for (int i = 0; i < gradeInfo.size(); i++) {
final GbStudentGradeInfo studentGradeInfo = gradeInfo.get(i);

final Map<Long, GbGradeInfo> studentGrades = studentGradeInfo.getGrades();
final GbGradeInfo grade = studentGrades.get(this.assignmentId);

if (grade == null || grade.getGrade() == null) {
continue;
}

allGrades.add(Double.valueOf(grade.getGrade()));
}
Collections.sort(allGrades);

final GbChartData data = new GbChartData();

// Add 0-50% range
data.add(buildRangeLabel(0, 50));

// Add all ranges from 50 up to 100 in increments of 10.
final int range = 10;
for (int start = 50; start < 100; start = start + range) {
data.add(buildRangeLabel(start, start + range));
}

for (final Double grade : allGrades) {
if (isExtraCredit(grade, assignment, gradingType)) {
data.add(getString("label.statistics.chart.extracredit"));
continue;
}

final double percentage;
if (GradingType.PERCENTAGE.equals(gradingType)) {
percentage = grade;
} else {
percentage = grade / assignment.getPoints() * 100;
}

//determine key for this grade
final int total = Double.valueOf(Math.ceil(percentage) / range).intValue();

int start = total * range;
if (start == 100) {
start = start - range;
}

String key;
if (start < 50) {
key = buildRangeLabel(0, 50);
} else {
key = buildRangeLabel(start, start + range);
}

data.add(key);
}

data.setChartTitle(MessageHelper.getString("label.statistics.chart.title"));
data.setXAxisLabel(MessageHelper.getString("label.statistics.chart.xaxis"));
data.setYAxisLabel(MessageHelper.getString("label.statistics.chart.yaxis"));
data.setChartType("bar");
data.setChartId(this.getMarkupId());

return data;
}

/**
* Range labels are standard labels but use a translation key here
*
* @param start first number eg 0
* @param end second number eg 50
* @return eg "0-50" as a string, depending on translation
*/
private String buildRangeLabel(final int start, final int end) {
return new StringResourceModel("label.statistics.chart.range", null, start, end).getString();
}


/**
* Check if a grade is considered extra credit
*
* @param grade
* @param assignment
* @param gradingType
* @return
*/
private boolean isExtraCredit(final Double grade, final Assignment assignment, final GradingType gradingType) {
return (GradingType.PERCENTAGE.equals(gradingType) && grade > 100)
|| (GradingType.POINTS.equals(gradingType) && grade > assignment.getPoints());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.sakaiproject.gradebookng.tool.component;

import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.OnLoadHeaderItem;
import org.apache.wicket.markup.html.WebComponent;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.sakaiproject.component.cover.ServerConfigurationService;
import org.sakaiproject.gradebookng.business.GradebookNgBusinessService;
import org.sakaiproject.gradebookng.tool.model.GbChartData;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import lombok.extern.slf4j.Slf4j;

/**
* Base panel for gradebook charts. See {@link GbCourseGradeChart} or {@link GbAssignmentGradeChart}.
*
* Immediately renders itself with the base data. Subclasses may provide a refresh option.
*/
@Slf4j
public abstract class GbBaseChart extends WebComponent {

private static final long serialVersionUID = 1L;

@SpringBean(name = "org.sakaiproject.gradebookng.business.GradebookNgBusinessService")
protected transient GradebookNgBusinessService businessService;

public GbBaseChart(final String id) {
super(id);
setOutputMarkupPlaceholderTag(true);
}

@Override
public void renderHead(final IHeaderResponse response) {
final String version = ServerConfigurationService.getString("portal.cdn.version", "");

// chart requires ChartJS
response.render(
JavaScriptHeaderItem.forUrl(String.format("/gradebookng-tool/webjars/chartjs/2.7.0/Chart.min.js?version=%s", version)));

// our chart functions
response.render(
JavaScriptHeaderItem.forUrl(String.format("/gradebookng-tool/scripts/gradebook-chart.js?version=%s", version)));

// render immediately (for all subclasses)
final GbChartData data = this.getData();
response.render(OnLoadHeaderItem.forScript("renderChart('" + toJson(data) + "');"));
}

/**
* Get the data for the current instance
*
* @return
*/
protected abstract GbChartData getData();


/**
* Helper to convert data to json
*
* @param data
* @return
*/
protected String toJson(final GbChartData data) {
final Gson gson = new GsonBuilder().disableHtmlEscaping().create();
final String json = gson.toJson(data);
log.debug(json);
return json;
}

}

Loading

0 comments on commit 264947d

Please sign in to comment.