/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. */ package org.olat.course.statistic; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.chart.BarChartComponent; import org.olat.core.gui.components.chart.BarSeries; import org.olat.core.gui.components.table.ColumnDescriptor; import org.olat.core.gui.components.table.CustomRenderColumnDescriptor; import org.olat.core.gui.components.table.TableController; import org.olat.core.gui.components.table.TableEvent; import org.olat.core.gui.components.table.TableGuiConfiguration; import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.gui.translator.Translator; import org.olat.core.id.OLATResourceable; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.StringResourceableType; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.core.util.resource.OresHelper; import org.olat.course.CourseModule; import org.olat.course.ICourse; import org.olat.course.assessment.AssessmentHelper; import org.olat.course.nodes.CourseNode; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; import org.olat.util.logging.activity.LoggingResourceable; import org.springframework.beans.factory.annotation.Autowired; /** * Base class for Statistic Display Controllers - subclass this * to show a simple table with the result of a statistic query. * <p> * Initial Date: 10.02.2010 <br> * * @author eglis */ public class StatisticDisplayController extends BasicController { private static class Graph { public List<Integer> values; public List<String> labelList; public String chartIntroStr; private int numElements = 0; public List<Integer> getValues() { return values; } public List<String> getLabels() { return labelList; } } /** the logging object used in this class **/ private static final OLog log_ = Tracing.createLoggerFor(StatisticDisplayController.class); private final static String CLICK_NODE_ACTION = "clicknodeaction"; public final static String CLICK_TOTAL_ACTION = "clicktotalaction"; /** a possible value of statisticType in the user activity logging **/ private static final String STATISTIC_TYPE_VIEW_NODE_STATISTIC = "VIEW_NODE_STATISTIC"; /** a possible value of statisticType in the user activity logging **/ private static final String STATISTIC_TYPE_VIEW_TOTAL_OF_NODES_STATISTIC = "VIEW_TOTAL_OF_NODES_STATISTIC"; /** a possible value of statisticType in the user activity logging **/ private static final String STATISTIC_TYPE_VIEW_TOTAL_BY_VALUE_STATISTIC = "VIEW_TOTAL_BY_VALUE_STATISTIC"; /** a possible value of statisticType in the user activity logging **/ private static final String STATISTIC_TYPE_VIEW_TOTAL_TOTAL_STATISTIC = "VIEW_TOTAL_TOTAL_STATISTIC"; private final ICourse course; private final IStatisticManager statisticManager; private TableController tableCtr_; private TableController tableController; private VelocityContainer statisticVc_; private Translator headerTranslator_; @Autowired private SimpleStatisticInfoHelper statisticInfoHelper; public StatisticDisplayController(UserRequest ureq, WindowControl windowControl, ICourse course, IStatisticManager statisticManager) { super(ureq, windowControl); addLoggingResourceable(LoggingResourceable.wrap(course)); addLoggingResourceable(LoggingResourceable.wrapNonOlatResource(StringResourceableType.statisticManager, "", statisticManager.getClass().getSimpleName())); if (course==null) { throw new IllegalArgumentException("Course must not be null"); } this.course = course; this.statisticManager = statisticManager; this.headerTranslator_ = Util.createPackageTranslator(statisticManager.getClass(), ureq.getLocale()); // statistic.html is under org.olat.course.statistic - no matter who subclasses BaseStatisticDisplayController setVelocityRoot(Util.getPackageVelocityRoot(StatisticDisplayController.class)); setTranslator(Util.createPackageTranslator(statisticManager.getClass(), ureq.getLocale(), Util.createPackageTranslator(StatisticDisplayController.class, ureq.getLocale()))); putInitialPanel(createInitialComponent(ureq)); } protected Component createInitialComponent(UserRequest ureq) { statisticVc_ = createVelocityContainer("statistic"); statisticVc_.contextPut("statsSince", getStatsSinceStr()); recreateTableController(ureq); return statisticVc_; } protected void recreateTableController(UserRequest ureq) { StatisticResult result = recalculateStatisticResult(ureq); tableCtr_ = createTableController(ureq, result); statisticVc_.put("statisticResult", tableCtr_.getInitialComponent()); statisticVc_.contextPut("hasChart", Boolean.FALSE); Graph graph = calculateNodeGraph(ureq, result.getRowCount()-1); generateCharts(graph); } protected StatisticResult recalculateStatisticResult(UserRequest ureq) { return statisticManager.generateStatisticResult(ureq, course, getCourseRepositoryEntryKey()); } private TableController createTableController(UserRequest ureq, StatisticResult result) { TableGuiConfiguration tableConfig = new TableGuiConfiguration(); tableConfig.setDisplayTableHeader(true); tableConfig.setDisplayRowCount(true); tableConfig.setPageingEnabled(true); tableConfig.setDownloadOffered(true); tableConfig.setSortingEnabled(true); removeAsListenerAndDispose(tableController); tableController = new TableController(tableConfig, ureq, getWindowControl(), getTranslator()); listenTo(tableController); // tableCtr.addColumnDescriptor(statisticManager.createColumnDescriptor(ureq, 0, null)); IndentedStatisticNodeRenderer indentedNodeRenderer = new IndentedStatisticNodeRenderer(Util.createPackageTranslator(statisticManager.getClass(), ureq.getLocale())); indentedNodeRenderer.setSimpleRenderingOnExport(true); CustomRenderColumnDescriptor nodeCD = new CustomRenderColumnDescriptor("stat.table.header.node", 0, CLICK_NODE_ACTION, ureq.getLocale(), ColumnDescriptor.ALIGNMENT_LEFT, indentedNodeRenderer) { @Override public int compareTo(int rowa, int rowb) { // order by original row order return new Integer(rowa).compareTo(rowb); } }; tableController.addColumnDescriptor(nodeCD); int column = 1; List<String> headers = result.getHeaders(); for (Iterator<String> it = headers.iterator(); it.hasNext();) { final String aHeader = it.next(); final int aColumnId = column++; tableController.addColumnDescriptor(statisticManager.createColumnDescriptor(ureq, aColumnId, aHeader)); } tableController.addColumnDescriptor(new CustomRenderColumnDescriptor("stat.table.header.total", column, StatisticDisplayController.CLICK_TOTAL_ACTION+column, ureq.getLocale(), ColumnDescriptor.ALIGNMENT_RIGHT, new TotalColumnRenderer()) { @Override public String getAction(int row) { if (row==table.getTableDataModel().getRowCount()-1) { return super.getAction(row); } else { return null; } } }); tableController.setTableDataModel(result); return tableController; } /** * Returns the ICourse which this controller is showing statistics for * @return the ICourse which this controller is showing statistics for */ protected ICourse getCourse() { return course; } /** * Returns the IStatisticManager associated to this controller via spring. * @return the IStatisticManager associated to this controller via spring */ protected IStatisticManager getStatisticManager() { return statisticManager; } protected long getCourseRepositoryEntryKey() { OLATResourceable ores = OresHelper.createOLATResourceableInstance(CourseModule.class, course.getResourceableId()); RepositoryEntry re = RepositoryManager.getInstance().lookupRepositoryEntry(ores, false); long resid = 0; if (re != null) { resid = re.getKey(); } return resid; } @Override protected void event(UserRequest ureq, Component source, Event event) { // nothing to be done here yet } @Override protected void event(UserRequest ureq, Controller source, Event event) { if (source==tableCtr_ && event instanceof TableEvent) { TableEvent tableEvent = (TableEvent)event; if (CLICK_NODE_ACTION.equals(tableEvent.getActionId())) { int rowId = tableEvent.getRowId(); Graph graph = calculateNodeGraph(ureq, rowId); generateCharts(graph); } else if (tableEvent.getActionId().startsWith(CLICK_TOTAL_ACTION)) { try{ int columnId = Integer.parseInt(tableEvent.getActionId().substring(CLICK_TOTAL_ACTION.length())); Graph graph = calculateTotalGraph(ureq, columnId); generateCharts(graph); } catch(NumberFormatException e) { log_.warn("event: Could not convert event into columnId for rendering graph: "+tableEvent.getActionId()); return; } } } } private Graph calculateNodeGraph(UserRequest ureq, int rowId) { Object o = tableCtr_.getTableDataModel().getValueAt(rowId, 0); String selectionInfo = ""; if (o instanceof Map) { Map map = (Map)o; CourseNode node = (CourseNode) map.get(StatisticResult.KEY_NODE); ThreadLocalUserActivityLogger.log(StatisticLoggingAction.VIEW_NODE_STATISTIC, getClass(), LoggingResourceable.wrap(node), LoggingResourceable.wrapNonOlatResource(StringResourceableType.statisticType, "", STATISTIC_TYPE_VIEW_NODE_STATISTIC)); String shortTitle = StringHelper.escapeHtml((String) map.get(AssessmentHelper.KEY_TITLE_SHORT)); selectionInfo = getTranslator().translate("statistic.chart.selectioninfo.node", new String[] { shortTitle }); } else { ThreadLocalUserActivityLogger.log(StatisticLoggingAction.VIEW_TOTAL_OF_NODES_STATISTIC, getClass(), LoggingResourceable.wrapNonOlatResource(StringResourceableType.statisticType, "", STATISTIC_TYPE_VIEW_TOTAL_OF_NODES_STATISTIC)); selectionInfo = getTranslator().translate("statistic.chart.selectioninfo.total"); } String chartIntroStr = headerTranslator_.translate("statistic.chart.intro", new String[] { selectionInfo, getStatsSinceStr() }); StringBuffer chd = new StringBuffer(); List<Integer> values = new ArrayList<Integer>(); int max = 10; int columnCnt = tableCtr_.getTableDataModel().getColumnCount(); List<String> labelList = new LinkedList<String>(); for(int column=1/*we ignore the node itself*/; column<columnCnt-1/*we ignore the total*/; column++) { Object cellValue = tableCtr_.getTableDataModel().getValueAt(rowId, column); Integer v = 0; if (cellValue instanceof Integer) { v = (Integer)cellValue; } max = Math.max(max, v); if (chd.length()!=0) { chd.append(","); } chd.append(v); values.add(v); ColumnDescriptor cd = tableCtr_.getColumnDescriptor(column); String headerKey = cd.getHeaderKey(); if (cd.translateHeaderKey()) { headerKey = headerTranslator_.translate(headerKey); } labelList.add(headerKey); } Graph result = new Graph(); result.values = values; result.labelList = labelList; result.chartIntroStr = chartIntroStr; result.numElements = columnCnt-2; return result; } private Graph calculateTotalGraph(UserRequest ureq, int columnId) { ColumnDescriptor cd = tableCtr_.getColumnDescriptor(columnId); String headerKey = cd.getHeaderKey(); if (cd.translateHeaderKey()) { headerKey = headerTranslator_.translate(headerKey); } String selectionInfo = headerKey; String chartIntroStr; if (columnId==tableCtr_.getTableDataModel().getColumnCount()-1) { ThreadLocalUserActivityLogger.log(StatisticLoggingAction.VIEW_TOTAL_TOTAL_STATISTIC, getClass(), LoggingResourceable.wrapNonOlatResource(StringResourceableType.statisticType, "", STATISTIC_TYPE_VIEW_TOTAL_TOTAL_STATISTIC)); chartIntroStr = headerTranslator_.translate("statistic.chart.pernode.total.intro", new String[] { getStatsSinceStr() }); } else { ThreadLocalUserActivityLogger.log(StatisticLoggingAction.VIEW_TOTAL_BY_VALUE_STATISTIC, getClass(), LoggingResourceable.wrapNonOlatResource(StringResourceableType.statisticType, "", STATISTIC_TYPE_VIEW_TOTAL_BY_VALUE_STATISTIC), LoggingResourceable.wrapNonOlatResource(StringResourceableType.statisticColumn, "", selectionInfo)); chartIntroStr = headerTranslator_.translate("statistic.chart.pernode.intro", new String[] { selectionInfo }); } StringBuffer chd = new StringBuffer(); int max = 10; List<String> labelList = new LinkedList<String>(); for(int row=0; row<tableCtr_.getTableDataModel().getRowCount()-1; row++) { Object cellValue = tableCtr_.getTableDataModel().getValueAt(row, columnId); Integer v = 0; if (cellValue instanceof Integer) { v = (Integer)cellValue; } max = Math.max(max, v); if (chd.length()!=0) { chd.append(","); } chd.append(v); Map m = (Map)tableCtr_.getTableDataModel().getValueAt(row, 0); headerKey = "n/a"; if (m!=null) { headerKey = (String) m.get(AssessmentHelper.KEY_TITLE_SHORT); } labelList.add(headerKey); } Graph result = new Graph(); result.labelList = labelList; result.chartIntroStr = chartIntroStr; result.numElements = tableCtr_.getTableDataModel().getRowCount()-1; return result; } private void generateCharts(Graph graph) { statisticVc_.contextPut("hasChart", Boolean.FALSE); statisticVc_.contextPut("hasChartError", Boolean.FALSE); if (graph==null || graph.numElements==0) { Component ic = getInitialComponent(); if (ic!=null) { ic.setDirty(true); } return; } try{ statisticVc_.contextPut("chartAlt", getTranslator().translate("chart.alt")); statisticVc_.contextPut("chartIntro", graph.chartIntroStr); statisticVc_.contextPut("hasChart", Boolean.TRUE); statisticVc_.contextPut("hasChartError", Boolean.FALSE); BarChartComponent chartCmp = new BarChartComponent("stats"); List<String> labels = graph.getLabels(); List<Integer> values = graph.getValues(); BarSeries serie = new BarSeries(); for(int i=0; i<labels.size(); i++) { double value = values.get(i).doubleValue(); String category = labels.get(i); serie.add(value, category); } chartCmp.addSeries(serie); statisticVc_.put("chart", chartCmp); } catch(RuntimeException re) { log_.warn("generateCharts: RuntimeException during chart generation: "+re, re); } Component ic = getInitialComponent(); if (ic!=null) { ic.setDirty(true); } } protected String getStatsSinceStr() { Date d = statisticInfoHelper.getFirstLoggingTableCreationDate(); if (d==null) { return "n/a"; } return Formatter.getInstance(getLocale()).formatDate(d); } @Override protected void doDispose() { // } }