Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
StatisticDisplayController.java 15.80 KiB
/**
* 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() {
		//
	}
}