summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThibault Delavallée <tde@odoo.com>2019-11-26 12:27:48 +0000
committerThibault Delavallée <tde@odoo.com>2019-12-05 15:21:28 +0000
commit351a4439b3a28d3832cd9bf17840dc9feaad7231 (patch)
tree9c038856bc8170e50945465991f83934d1a80ac1
parentbb3d4634df39dec2f899397628641d96525e5f6b (diff)
[REF] survey: clean results page and its code generating statistics
PURPOSE As new features are about to land in survey, notably live interactions [1] and new survey building [2] performing a pre cleaning is necessary. In this PR we clean survey models by: removing unnecessary fields, cleaning some code and finally renaming models. SPECIFICATIONS Purpose of this commit is to globally rewrite the way statistics are pre computed and used in results page of surveys. Indeed current code is very complex, having a lot of calls to the same methods while taking only part of its results. It also have a mix of model and controller methods, data is computed several times a bit differently, ... In this commit we rewrite that part. Including * a simpler code flow; * a simpler data structure; * use a better naming for methods instead of "prepare_results" called randomly within the code and re-using part of its returned content; * use record sets / records instead of giving sub-elements of a record like question.title, label.value, ... Data structure * simply give a question_and_page_ids to the template that will iterate on pages / questions; * have a graph_data, table_data dictionaries available for graph and table display with simple entries; * graph_data: holding all necessary but minimalist data for graph display if question allows it; * table_data: holding all necessary but minimalist data for table / summary display; * hold record sets of done / failed answers and answer lines; In this commit we also simplify and clean pagination in survey graph results * remove page_range helper tool that can be inlined in the templates directly, doing a simple range; * fix and improve pagination code; LINKS [0] Related to Task ID 2061901 (survey models cleaning and preparation) [1] Task ID 1972640 (live interactions) [2] Task ID 2119587 (new frontend for building surveys) PR #40765
-rw-r--r--addons/survey/controllers/main.py212
-rw-r--r--addons/survey/models/survey_question.py152
-rw-r--r--addons/survey/models/survey_survey.py160
-rw-r--r--addons/survey/models/survey_user.py1
-rw-r--r--addons/survey/static/src/js/survey_result.js25
-rw-r--r--addons/survey/tests/test_survey.py50
-rw-r--r--addons/survey/views/survey_templates_management.xml7
-rw-r--r--addons/survey/views/survey_templates_statistics.xml487
8 files changed, 492 insertions, 602 deletions
diff --git a/addons/survey/controllers/main.py b/addons/survey/controllers/main.py
index b6147ca937f..af74e0f911c 100644
--- a/addons/survey/controllers/main.py
+++ b/addons/survey/controllers/main.py
@@ -7,13 +7,13 @@ import werkzeug
from datetime import datetime
from dateutil.relativedelta import relativedelta
-from math import ceil
from odoo import fields, http, _
from odoo.addons.base.models.ir_ui_view import keep_query
from odoo.exceptions import UserError
from odoo.http import request, content_disposition
-from odoo.tools import ustr, format_datetime, format_date
+from odoo.osv import expression
+from odoo.tools import format_datetime, format_date
_logger = logging.getLogger(__name__)
@@ -484,161 +484,65 @@ class Survey(http.Controller):
def survey_report(self, survey, answer_token=None, **post):
""" Display survey Results & Statistics for given survey.
- Quick retroengineering of what is injected into the template for now:
- (TODO: flatten and simplify this)
-
- survey: a browse record of the survey
- survey_dict: very messy dict containing all the info to display answers
- {'page_ids': [
-
- ...
-
- {'page': browse record of the page,
- 'question_ids': [
-
- ...
-
- {'graph_data': data to be displayed on the graph
- 'input_summary': number of answered, skipped...
- 'prepare_result': {
- answers displayed in the tables
- }
- 'question': browse record of the question_ids
- }
-
- ...
-
- ]
- }
-
- ...
-
- ]
- }
-
- page_range: pager helper function
- current_filters: a list of ids
- filter_display_data: [{'labels': ['a', 'b'], question_text} ... ]
- filter_finish: boolean => only finished surveys or not
+ New structure: {
+ 'survey': current survey browse record,
+ 'question_and_page_data': see ``SurveyQuestion._prepare_statistics()``,
+ 'survey_data'= see ``SurveySurvey._prepare_statistics()``
+ 'search_filters': [],
+ 'search_finished': either filter on finished inputs only or not,
+ }
"""
- current_filters = []
- filter_display_data = []
-
- answers = survey.user_input_ids.filtered(lambda answer: answer.state != 'new' and not answer.test_entry)
- filter_finish = post.get('finished') == 'true'
- if post or filter_finish:
- filter_data = self._get_filter_data(post)
- current_filters = survey.filter_input_ids(filter_data, filter_finish)
- filter_display_data = survey.get_filter_display_data(filter_data)
+ user_input_lines, search_filters = self._extract_filters_data(survey, post)
+ survey_data = survey._prepare_statistics(user_input_lines)
+ question_and_page_data = survey.question_and_page_ids._prepare_statistics(user_input_lines)
+
return request.render('survey.survey_page_statistics', {
+ # survey and its statistics
'survey': survey,
- 'answers': answers,
- 'survey_dict': self._prepare_result_dict(survey, current_filters),
- 'page_range': self.page_range,
- 'current_filters': current_filters,
- 'filter_display_data': filter_display_data,
- 'filter_finish': filter_finish
+ 'question_and_page_data': question_and_page_data,
+ 'survey_data': survey_data,
+ # search
+ 'search_filters': search_filters,
+ 'search_finished': post.get('finished') == 'true',
})
- def _prepare_result_dict(self, survey, current_filters=None):
- """Returns dictionary having values for rendering template"""
- current_filters = current_filters if current_filters else []
- result = {'page_ids': []}
-
- # First append questions without page
- questions_without_page = [self._prepare_question_values(question,current_filters) for question in survey.question_ids if not question.page_id]
- if questions_without_page:
- result['page_ids'].append({'page': request.env['survey.question'], 'question_ids': questions_without_page})
-
- # Then, questions in sections
- for page in survey.page_ids:
- page_dict = {'page': page, 'question_ids': [self._prepare_question_values(question,current_filters) for question in page.question_ids]}
- result['page_ids'].append(page_dict)
-
- if survey.scoring_type in ['scoring_with_answers', 'scoring_without_answers']:
- scoring_data = self._get_scoring_data(survey)
- result['success_rate'] = scoring_data['success_rate']
- result['scoring_graph_data'] = json.dumps(scoring_data['graph_data'])
-
- return result
-
- def _prepare_question_values(self, question, current_filters):
- Survey = request.env['survey.survey']
- return {
- 'question': question,
- 'input_summary': Survey.get_input_summary(question, current_filters),
- 'prepare_result': Survey.prepare_result(question, current_filters),
- 'graph_data': self._get_graph_data(question, current_filters),
- }
-
- def _get_filter_data(self, post):
- """Returns data used for filtering the result"""
- filters = []
- filters_data = post.get('filters')
- if filters_data:
- for data in filters_data.split('|'):
- try:
- row_id, answer_id = data.split(',')
- filters.append({'row_id': int(row_id), 'answer_id': int(answer_id)})
- except:
- return filters
- return filters
-
- def page_range(self, total_record, limit):
- '''Returns number of pages required for pagination'''
- total = ceil(total_record / float(limit))
- return range(1, int(total + 1))
-
- def _get_graph_data(self, question, current_filters=None):
- '''Returns formatted data required by graph library on basis of filter'''
- # TODO refactor this terrible method and merge it with _prepare_result_dict
- current_filters = current_filters if current_filters else []
- Survey = request.env['survey.survey']
- result = []
- if question.question_type == 'multiple_choice':
- result.append({'key': ustr(question.title),
- 'values': Survey.prepare_result(question, current_filters)['answers']
- })
- if question.question_type == 'simple_choice':
- result = Survey.prepare_result(question, current_filters)['answers']
- if question.question_type == 'matrix':
- data = Survey.prepare_result(question, current_filters)
- for answer in data['answers']:
- values = []
- for row in data['rows']:
- values.append({'text': data['rows'].get(row), 'count': data['result'].get((row, answer))})
- result.append({'key': data['answers'].get(answer), 'values': values})
- return json.dumps(result)
-
- def _get_scoring_data(self, survey):
- """Performs a read_group to fetch the count of failed/passed tests in a single query."""
-
- count_data = request.env['survey.user_input'].read_group(
- [('survey_id', '=', survey.id), ('state', '=', 'done'), ('test_entry', '=', False)],
- ['scoring_success', 'id:count_distinct'],
- ['scoring_success']
- )
-
- scoring_success_count = 0
- scoring_failed_count = 0
- for count_data_item in count_data:
- if count_data_item['scoring_success']:
- scoring_success_count = count_data_item['scoring_success_count']
+ def _extract_filters_data(self, survey, post):
+ search_filters = []
+ line_filter_domain, line_choices = [], []
+ for data in post.get('filters', '').split('|'):
+ try:
+ row_id, answer_id = (int(item) for item in data.split(','))
+ except:
+ pass
else:
- scoring_failed_count = count_data_item['scoring_success_count']
-
- graph_data = [{
- 'text': _('Passed'),
- 'count': scoring_success_count,
- 'color': '#2E7D32'
- }, {
- 'text': _('Missed'),
- 'count': scoring_failed_count,
- 'color': '#C62828'
- }]
-
- total_scoring_success = scoring_success_count + scoring_failed_count
- return {
- 'success_rate': round((scoring_success_count / total_scoring_success) * 100, 1) if total_scoring_success > 0 else 0,
- 'graph_data': graph_data
- }
+ if row_id and answer_id:
+ line_filter_domain = expression.AND([
+ ['&', ('matrix_row_id', '=', row_id), ('suggested_answer_id', '=', answer_id)],
+ line_filter_domain
+ ])
+ answers = request.env['survey.question.answer'].browse([row_id, answer_id])
+ elif answer_id:
+ line_choices.append(answer_id)
+ answers = request.env['survey.question.answer'].browse([answer_id])
+ if answer_id:
+ search_filters.append({
+ 'question': answers[0].question_id.title,
+ 'answers': '%s%s' % (answers[0].value, ': %s' % answers[1].value if len(answers) > 1 else '')
+ })
+ if line_choices:
+ line_filter_domain = expression.AND([[('suggested_answer_id', 'in', line_choices)], line_filter_domain])
+
+ user_input_domain = ['&', ('test_entry', '=', False), ('survey_id', '=', survey.id)]
+ if line_filter_domain:
+ matching_line_ids = request.env['survey.user_input.line'].sudo().search(line_filter_domain).ids
+ user_input_domain = expression.AND([
+ [('user_input_line_ids', 'in', matching_line_ids)],
+ user_input_domain
+ ])
+ if post.get('finished'):
+ user_input_domain = expression.AND([[('state', '=', 'done')], user_input_domain])
+ else:
+ user_input_domain = expression.AND([[('state', '!=', 'new')], user_input_domain])
+ user_input_lines = request.env['survey.user_input'].sudo().search(user_input_domain).mapped('user_input_line_ids')
+
+ return user_input_lines, search_filters
diff --git a/addons/survey/models/survey_question.py b/addons/survey/models/survey_question.py
index e5685564e8f..c0c9d6a4bef 100644
--- a/addons/survey/models/survey_question.py
+++ b/addons/survey/models/survey_question.py
@@ -1,6 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import collections
+import json
+import itertools
+import operator
+
from odoo import api, fields, models, tools, _
from odoo.exceptions import ValidationError
@@ -92,7 +97,7 @@ class SurveyQuestion(models.Model):
display_mode = fields.Selection(
[('columns', 'Radio Buttons'), ('dropdown', 'Selection Box')],
string='Display Mode', default='columns', help='Display mode of simple choice questions.')
- # -- comments
+ # -- comments (simple choice, multiple choice, matrix (without count as an answer))
comments_allowed = fields.Boolean('Show Comments Field')
comments_message = fields.Char('Comment Message', translate=True, default=lambda self: _("If other, please specify:"))
comment_count_as_answer = fields.Boolean('Comment Field is an Answer Choice')
@@ -273,10 +278,149 @@ class SurveyQuestion(models.Model):
self.ensure_one()
return list(self.survey_id.question_and_page_ids).index(self)
- def get_correct_answer_ids(self):
- self.ensure_one()
+ # ------------------------------------------------------------
+ # STATISTICS / REPORTING
+ # ------------------------------------------------------------
+
+ def _prepare_statistics(self, user_input_lines):
+ """ Compute statistical data for questions by counting number of vote per choice on basis of filter """
+ all_questions_data = []
+ for question in self:
+ question_data = {'question': question, 'is_page': question.is_page}
+
+ if question.is_page:
+ all_questions_data.append(question_data)
+ continue
+
+ # fetch answer lines, separate comments from real answers
+ all_lines = user_input_lines.filtered(lambda line: line.question_id == question)
+ if question.question_type in ['simple_choice', 'multiple_choice', 'matrix']:
+ answer_lines = all_lines.filtered(
+ lambda line: line.answer_type == 'suggestion' or (
+ line.answer_type == 'char_box' and question.comment_count_as_answer)
+ )
+ comment_line_ids = all_lines.filtered(lambda line: line.answer_type == 'char_box')
+ else:
+ answer_lines = all_lines
+ comment_line_ids = self.env['survey.user_input.line']
+ skipped_lines = answer_lines.filtered(lambda line: line.skipped)
+ done_lines = answer_lines - skipped_lines
+ question_data.update(
+ answer_line_ids=answer_lines,
+ answer_line_done_ids=done_lines,
+ answer_input_done_ids=done_lines.mapped('user_input_id'),
+ answer_input_skipped_ids=skipped_lines.mapped('user_input_id'),
+ comment_line_ids=comment_line_ids)
+ question_data.update(question._get_stats_summary_data(answer_lines))
+
+ # prepare table and graph data
+ table_data, graph_data = question._get_stats_data(answer_lines)
+ question_data['table_data'] = table_data
+ question_data['graph_data'] = json.dumps(graph_data)
+
+ all_questions_data.append(question_data)
+ return all_questions_data
+
+ def _get_stats_data(self, user_input_lines):
+ if self.question_type in ['simple_choice']:
+ return self._get_stats_data_answers(user_input_lines)
+ elif self.question_type in ['multiple_choice']:
+ table_data, graph_data = self._get_stats_data_answers(user_input_lines)
+ return table_data, [{'key': self.title, 'values': graph_data}]
+ elif self.question_type in ['matrix']:
+ return self._get_stats_graph_data_matrix(user_input_lines)
+ return [line for line in user_input_lines], []
+
+ def _get_stats_data_answers(self, user_input_lines):
+ """ Statistics for question.answer based questions (simple choice, multiple
+ choice.). A corner case with a void record survey.question.answer is added
+ to count comments that should be considered as valid answers. This small hack
+ allow to have everything available in the same standard structure. """
+ suggested_answers = [answer for answer in self.mapped('suggested_answer_ids')]
+ if self.comment_count_as_answer:
+ suggested_answers += [self.env['survey.question.answer']]
+
+ count_data = dict.fromkeys(suggested_answers, 0)
+ for line in user_input_lines:
+ if line.suggested_answer_id or (line.value_char_box and self.comment_count_as_answer):
+ count_data[line.suggested_answer_id] += 1
+
+ table_data = [{
+ 'value': _('Other (see comments)') if not sug_answer else sug_answer.value,
+ 'suggested_answer': sug_answer,
+ 'count': count_data[sug_answer]
+ }
+ for sug_answer in suggested_answers]
+ graph_data = [{
+ 'text': _('Other (see comments)') if not sug_answer else sug_answer.value,
+ 'count': count_data[sug_answer]
+ }
+ for sug_answer in suggested_answers]
+
+ return table_data, graph_data
+
+ def _get_stats_graph_data_matrix(self, user_input_lines):
+ suggested_answers = self.mapped('suggested_answer_ids')
+ matrix_rows = self.mapped('matrix_row_ids')
+
+ count_data = dict.fromkeys(itertools.product(matrix_rows, suggested_answers), 0)
+ for line in user_input_lines:
+ if line.matrix_row_id and line.suggested_answer_id:
+ count_data[(line.matrix_row_id, line.suggested_answer_id)] += 1
+
+ table_data = [{
+ 'row': row,
+ 'columns': [{
+ 'suggested_answer': sug_answer,
+ 'count': count_data[(row, sug_answer)]
+ } for sug_answer in suggested_answers],
+ } for row in matrix_rows]
+ graph_data = [{
+ 'key': sug_answer.value,
+ 'values': [{
+ 'text': row.value,
+ 'count': count_data[(row, sug_answer)]
+ }
+ for row in matrix_rows
+ ]
+ } for sug_answer in suggested_answers]
+
+ return table_data, graph_data
+
+ def _get_stats_summary_data(self, user_input_lines):
+ if self.question_type in ['simple_choice', 'multiple_choice']:
+ return self._get_stats_summary_data_choice(user_input_lines)
+ if self.question_type in ['numerical_box']:
+ return self._get_stats_summary_data_numerical(user_input_lines)
+ return {}
- return self.suggested_answer_ids.filtered(lambda label: label.is_correct)
+ def _get_stats_summary_data_choice(self, user_input_lines):
+ right_inputs, partial_inputs = self.env['survey.user_input'], self.env['survey.user_input']
+ right_answers = self.suggested_answer_ids.filtered(lambda label: label.is_correct)
+ if self.question_type == 'multiple_choice':
+ for user_input, lines in tools.groupby(user_input_lines, operator.itemgetter('user_input_id')):
+ user_input_answers = self.env['survey.user_input.line'].concat(*lines).filtered(lambda l: l.answer_is_correct).mapped('suggested_answer_id')
+ if user_input_answers and user_input_answers < right_answers:
+ partial_inputs += user_input
+ elif user_input_answers:
+ right_inputs += user_input
+ else:
+ right_inputs = user_input_lines.filtered(lambda line: line.answer_is_correct).mapped('user_input_id')
+ return {
+ 'right_answers': right_answers,
+ 'right_inputs': right_inputs,
+ 'partial_inputs': partial_inputs,
+ }
+
+ def _get_stats_summary_data_numerical(self, user_input_lines):
+ all_values = user_input_lines.filtered(lambda line: not line.skipped).mapped('value_numerical_box')
+ lines_sum = sum(all_values)
+ return {
+ 'numerical_max': max(all_values, default=0),
+ 'numerical_min': min(all_values, default=0),
+ 'numerical_average': round(lines_sum / len(all_values) or 1, 2),
+ 'numerical_common_lines': collections.Counter(all_values).most_common(5),
+ }
class SurveyQuestionAnswer(models.Model):
diff --git a/addons/survey/models/survey_survey.py b/addons/survey/models/survey_survey.py
index ac33d8a6d50..a321b1853ba 100644
--- a/addons/survey/models/survey_survey.py
+++ b/addons/survey/models/survey_survey.py
@@ -1,13 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import json
import random
import uuid
import werkzeug
-from collections import Counter, OrderedDict
-from itertools import product
-
from odoo import api, exceptions, fields, models, _
from odoo.osv import expression
@@ -554,129 +552,43 @@ class Survey(models.Model):
# GRAPH / RESULTS
# ------------------------------------------------------------
- def filter_input_ids(self, filters, finished=False):
- """If user applies any filters, then this function returns list of
- filtered user_input_id and label's strings for display data in web.
- :param filters: list of dictionary (having: row_id, ansewr_id)
- :param finished: True for completely filled survey,Falser otherwise.
- :returns list of filtered user_input_ids.
- """
- self.ensure_one()
- if filters:
- domain_filter, choice = [], []
- for current_filter in filters:
- row_id, answer_id = current_filter['row_id'], current_filter['answer_id']
- if row_id == 0:
- choice.append(answer_id)
- else:
- domain_filter.extend(['|', ('matrix_row_id.id', '=', row_id), ('suggested_answer_id.id', '=', answer_id)])
- if choice:
- domain_filter.insert(0, ('suggested_answer_id.id', 'in', choice))
- else:
- domain_filter = domain_filter[1:]
- input_lines = self.env['survey.user_input.line'].search(domain_filter)
- filtered_input_ids = [input_line.user_input_id.id for input_line in input_lines]
+ def _prepare_statistics(self, user_input_lines=None):
+ if user_input_lines:
+ user_input_domain = [
+ ('survey_id', 'in', self.ids),
+ ('id', 'in', user_input_lines.mapped('user_input_id').ids)
+ ]
else:
- filtered_input_ids = []
- if finished:
- UserInput = self.env['survey.user_input']
- if not filtered_input_ids:
- user_inputs = UserInput.search([('survey_id', '=', self.id)])
+ user_input_domain = [
+ ('survey_id', 'in', self.ids),
+ ('state', '=', 'done'),
+ ('test_entry', '=', False)
+ ]
+ count_data = self.env['survey.user_input'].sudo().read_group(user_input_domain, ['scoring_success', 'id:count_distinct'], ['scoring_success'])
+
+ scoring_success_count = 0
+ scoring_failed_count = 0
+ for count_data_item in count_data:
+ if count_data_item['scoring_success']:
+ scoring_success_count += count_data_item['scoring_success_count']
else:
- user_inputs = UserInput.browse(filtered_input_ids)
- return user_inputs.filtered(lambda input_item: input_item.state == 'done').ids
- return filtered_input_ids
-
- @api.model
- def get_filter_display_data(self, filters):
- """Returns data to display current filters
- :param filters: list of dictionary (having: row_id, answer_id)
- :returns list of dict having data to display filters.
- """
- filter_display_data = []
- if filters:
- Label = self.env['survey.question.answer']
- for current_filter in filters:
- row_id, answer_id = current_filter['row_id'], current_filter['answer_id']
- label = Label.browse(answer_id)
- question = label.question_id
- if row_id == 0:
- labels = label
- else:
- labels = Label.browse([row_id, answer_id])
- filter_display_data.append({'question_text': question.title,
- 'labels': labels.mapped('value')})
- return filter_display_data
-
- @api.model
- def prepare_result(self, question, current_filters=None):
- """ Compute statistical data for questions by counting number of vote per choice on basis of filter """
- current_filters = current_filters if current_filters else []
- result_summary = {}
- input_lines = question.user_input_line_ids.filtered(lambda line: not line.user_input_id.test_entry)
-
- # Calculate and return statistics for choice
- if question.question_type in ['simple_choice', 'multiple_choice']:
- comments = []
- answers = OrderedDict((label.id, {'text': label.value, 'count': 0, 'answer_id': label.id, 'answer_score': label.answer_score}) for label in question.suggested_answer_ids)
- for input_line in input_lines:
- if input_line.answer_type == 'suggestion' and answers.get(input_line.suggested_answer_id.id) and (not(current_filters) or input_line.user_input_id.id in current_filters):
- answers[input_line.suggested_answer_id.id]['count'] += 1
- if input_line.answer_type == 'char_box' and (not(current_filters) or input_line.user_input_id.id in current_filters):
- comments.append(input_line)
- result_summary = {'answers': list(answers.values()), 'comments': comments}
-
- # Calculate and return statistics for matrix
- if question.question_type == 'matrix':
- rows = OrderedDict()
- answers = OrderedDict()
- res = dict()
- comments = []
- [rows.update({label.id: label.value}) for label in question.matrix_row_ids]
- [answers.update({label.id: label.value}) for label in question.suggested_answer_ids]
- for cell in product(rows, answers):
- res[cell] = 0
- for input_line in input_lines:
- if input_line.answer_type == 'suggestion' and (not(current_filters) or input_line.user_input_id.id in current_filters) and input_line.matrix_row_id:
- res[(input_line.matrix_row_id.id, input_line.suggested_answer_id.id)] += 1
- if input_line.answer_type == 'char_box' and (not(current_filters) or input_line.user_input_id.id in current_filters):
- comments.append(input_line)
- result_summary = {'answers': answers, 'rows': rows, 'result': res, 'comments': comments}
-
- # Calculate and return statistics for text_box, char_box, date
- if question.question_type in ['text_box', 'char_box', 'date', 'datetime']:
- result_summary = []
- for input_line in input_lines:
- if not(current_filters) or input_line.user_input_id.id in current_filters:
- result_summary.append(input_line)
-
- # Calculate and return statistics for numerical_box
- if question.question_type == 'numerical_box':
- result_summary = {'input_lines': []}
- all_inputs = []
- for input_line in input_lines:
- if not(current_filters) or input_line.user_input_id.id in current_filters:
- all_inputs.append(input_line.value_numerical_box)
- result_summary['input_lines'].append(input_line)
- if all_inputs:
- result_summary.update({'average': round(sum(all_inputs) / len(all_inputs), 2),
- 'max': round(max(all_inputs), 2),
- 'min': round(min(all_inputs), 2),
- 'sum': sum(all_inputs),
- 'most_common': Counter(all_inputs).most_common(5)})
- return result_summary
-
- @api.model
- def get_input_summary(self, question, current_filters=None):
- """ Returns overall summary of question e.g. answered, skipped, total_inputs on basis of filter """
- current_filters = current_filters if current_filters else []
- result = {}
- search_line_ids = current_filters if current_filters else question.user_input_line_ids.ids
-
- result['answered'] = len([line for line in question.user_input_line_ids if line.user_input_id.state != 'new' and not line.user_input_id.test_entry and not line.skipped])
- result['skipped'] = len([line for line in question.user_input_line_ids if line.user_input_id.state != 'new' and not line.user_input_id.test_entry and line.skipped])
-
- return result
+ scoring_failed_count += count_data_item['scoring_success_count']
+
+ success_graph = json.dumps([{
+ 'text': _('Passed'),
+ 'count': scoring_success_count,
+ 'color': '#2E7D32'
+ }, {
+ 'text': _('Missed'),
+ 'count': scoring_failed_count,
+ 'color': '#C62828'
+ }])
+
+ total = scoring_success_count + scoring_failed_count
+ return {
+ 'global_success_rate': round((scoring_success_count / total) * 100, 1) if total > 0 else 0,
+ 'global_success_graph': success_graph
+ }
# ------------------------------------------------------------
# GAMIFICATION / BADGES
diff --git a/addons/survey/models/survey_user.py b/addons/survey/models/survey_user.py
index eb45c2a16e0..cea87343a89 100644
--- a/addons/survey/models/survey_user.py
+++ b/addons/survey/models/survey_user.py
@@ -342,6 +342,7 @@ class SurveyUserInputLine(models.Model):
value_text_box = fields.Text('Free Text answer')
suggested_answer_id = fields.Many2one('survey.question.answer', string="Suggested answer")
matrix_row_id = fields.Many2one('survey.question.answer', string="Row answer")
+ # scoring
answer_score = fields.Float('Score')
answer_is_correct = fields.Boolean('Correct', compute='_compute_answer_is_correct')
diff --git a/addons/survey/static/src/js/survey_result.js b/addons/survey/static/src/js/survey_result.js
index 3919015af00..dd7e1d5773d 100644
--- a/addons/survey/static/src/js/survey_result.js
+++ b/addons/survey/static/src/js/survey_result.js
@@ -14,7 +14,7 @@ var D3_COLORS = ["#1f77b4","#ff7f0e","#aec7e8","#ffbb78","#2ca02c","#98df8a","#d
// -> this is ugly / not efficient, needs to be refactored
publicWidget.registry.SurveyResultPagination = publicWidget.Widget.extend({
events: {
- 'click li a': '_onPageClick',
+ 'click li.o_survey_js_results_pagination a': '_onPageClick',
},
//--------------------------------------------------------------------------
@@ -38,7 +38,6 @@ publicWidget.registry.SurveyResultPagination = publicWidget.Widget.extend({
var self = this;
return this._super.apply(this, arguments).then(function () {
self.limit = self.$el.data("record_limit");
- self._changePage(1);
});
},
@@ -52,24 +51,9 @@ publicWidget.registry.SurveyResultPagination = publicWidget.Widget.extend({
*/
_onPageClick: function (ev) {
ev.preventDefault();
+ this.$('li.o_survey_js_results_pagination').removeClass('active');
- var pageNumber = $(ev.currentTarget).text();
- this._changePage(pageNumber);
- },
-
- // -------------------------------------------------------------------------
- // Private
- // -------------------------------------------------------------------------
-
- /**
- * Shows / hides the records based on the selected page.
- *
- * @private
- * @param {string} pageNumber
- */
- _changePage: function (pageNumber) {
- this.$('li').removeClass('active');
- var $target = this.$(_.str.sprintf('li:contains("%s")', pageNumber));
+ var $target = $(ev.currentTarget);
$target.closest('li').addClass('active');
this.$questionsEl.find('tbody tr').addClass('d-none');
@@ -82,6 +66,7 @@ publicWidget.registry.SurveyResultPagination = publicWidget.Widget.extend({
this.$questionsEl.find('tbody tr:lt('+ this.limit * num +'):gt(' + min + ')')
.removeClass('d-none');
}
+
},
});
@@ -343,7 +328,7 @@ publicWidget.registry.SurveyResultWidget = publicWidget.Widget.extend({
self.$('.pagination').each(function (){
var questionId = $(this).data("question_id");
new publicWidget.registry.SurveyResultPagination(self, {
- 'questionsEl': self.$('#table_question_'+ questionId)
+ 'questionsEl': self.$('#survey_table_question_'+ questionId)
}).attachTo($(this));
});
diff --git a/addons/survey/tests/test_survey.py b/addons/survey/tests/test_survey.py
index 23ccab5f83e..77ed53f6a69 100644
--- a/addons/survey/tests/test_survey.py
+++ b/addons/survey/tests/test_survey.py
@@ -120,53 +120,3 @@ class TestSurveyInternals(common.TestSurveyCommon):
question.validate_question('valid'),
{}
)
-
- @users('survey_manager')
- def test_result_data_simple_multiple_choice(self):
- question = self._add_question(
- self.page_0, 'Q0', 'simple_choice',
- labels=[{'value': 'Choice0'}, {'value': 'Choice1'}]
- )
- for i in range(3):
- answer = self._add_answer(self.survey, False, email='public@example.com')
- self._add_answer_line(
- question, answer, random.choice(question.suggested_answer_ids.ids),
- answer_type='suggestion', answer_fname='suggested_answer_id')
- lines = [line.suggested_answer_id.id for line in question.user_input_line_ids]
- answers = [{'text': label.value, 'count': lines.count(label.id), 'answer_id': label.id, 'answer_score': label.answer_score} for label in question.suggested_answer_ids]
- prp_result = self.env['survey.survey'].prepare_result(question)['answers']
- self.assertItemsEqual(prp_result, answers)
-
- @users('survey_manager')
- def test_result_data_matrix(self):
- question = self._add_question(
- self.page_0, 'Q0', 'matrix', matrix_subtype='simple',
- labels=[{'value': 'Column0'}, {'value': 'Column1'}],
- labels_2=[{'value': 'Row0'}, {'value': 'Row1'}]
- )
- for i in range(3):
- answer = self._add_answer(self.survey, False, email='public@example.com')
- self._add_answer_line(
- question, answer, random.choice(question.suggested_answer_ids.ids),
- answer_type='suggestion', answer_fname='suggested_answer_id', matrix_row_id=random.choice(question.matrix_row_ids.ids)
- )
- lines = [(line.matrix_row_id.id, line.suggested_answer_id.id) for line in question.user_input_line_ids]
- res = {}
- for i in product(question.matrix_row_ids.ids, question.suggested_answer_ids.ids):
- res[i] = lines.count((i))
- self.assertEqual(self.env['survey.survey'].prepare_result(question)['result'], res)
-
- @users('survey_manager')
- def test_result_data_numeric_box(self):
- question = self._add_question(self.page_0, 'Q0', 'numerical_box')
- num = [float(n) for n in random.sample(range(1, 100), 3)]
- nsum = sum(num)
- for i in range(3):
- answer = self._add_answer(self.survey, False, email='public@example.com')
- self._add_answer_line(question, answer, num[i])
- exresult = {
- 'average': round((nsum / len(num)), 2), 'max': round(max(num), 2),
- 'min': round(min(num), 2), 'sum': nsum, 'most_common': Counter(num).most_common(5)}
- result = self.env['survey.survey'].prepare_result(question)
- for key in exresult:
- self.assertEqual(result[key], exresult[key])
diff --git a/addons/survey/views/survey_templates_management.xml b/addons/survey/views/survey_templates_management.xml
index c2635e34cc4..c4684e76093 100644
--- a/addons/survey/views/survey_templates_management.xml
+++ b/addons/survey/views/survey_templates_management.xml
@@ -112,7 +112,12 @@
</div>
<div class="container o_survey_result p-4" t-if="graph_data">
<div class="tab-content">
- <div role="tabpanel" class="tab-pane active survey_graph" t-att-data-scoring_percentage="answer.scoring_percentage" t-att-id="'graph_question_%d' % answer.id" t-att-data-question_id="answer.id" data-graph-type="doughnut" t-att-data-graph-data="graph_data">
+ <div role="tabpanel" class="tab-pane active survey_graph"
+ t-att-data-scoring_percentage="answer.scoring_percentage"
+ t-att-id="'survey_graph_question_%d' % answer.id"
+ t-att-data-question_id="answer.id"
+ data-graph-type="doughnut"
+ t-att-data-graph-data="graph_data">
<canvas id="doughnut_chart"></canvas>
<span class="o_overall_performance"></span>
</div>
diff --git a/addons/survey/views/survey_templates_statistics.xml b/addons/survey/views/survey_templates_statistics.xml
index cc1f66fd782..63b0b4ecd10 100644
--- a/addons/survey/views/survey_templates_statistics.xml
+++ b/addons/survey/views/survey_templates_statistics.xml
@@ -3,111 +3,106 @@
<data>
<template id="survey_page_statistics" name="Survey: result statistics page">
<t t-call="survey.layout">
+ <t t-set="page_record_limit" t-value="10"/><!-- Change this record_limit to change number of record per page-->
<div class="oe_structure" id="oe_structure_survey_result_1"/>
<div class="container o_survey_result">
<t t-call="survey.survey_button_form_view" />
<div class="jumbotron mt32">
- <h1><span t-field="survey.title" />
- <span style="font-size:1.5em;" t-attf-class="fa fa-bar-chart-o #{'fa-bar-chart-o' if survey.scoring_type == 'no_scoring' else 'fa-trophy' if survey.certification else 'fa-question-circle-o'} float-right " role="img" aria-label="Chart" title="Chart"/></h1>
- <div t-field="survey.description" class="oe_no_empty" />
- <h2 t-if="not answers">
+ <h1>
+ <span t-field="survey.title"/>
+ <span style="font-size:1.5em;"
+ t-attf-class="fa fa-bar-chart-o #{'fa-bar-chart-o' if survey.scoring_type == 'no_scoring' else 'fa-trophy' if survey.certification else 'fa-question-circle-o'} float-right " role="img" aria-label="Chart" title="Chart"/>
+ </h1>
+ <div t-field="survey.description" class="oe_no_empty"/>
+ <h2 t-if="not question_and_page_data">
Sorry, no one answered this survey yet.
</h2>
</div>
- <div t-if="answers" class="card d-print-none">
- <div class="card-header"><span class="fa fa-filter"></span> Filters <span t-if="filter_display_data" class="float-right text-primary clear_survey_filter"><i class="fa fa-times"></i> Clear All Filters</span></div>
+ <div t-if="question_and_page_data" class="card d-print-none">
+ <div class="card-header"><span class="fa fa-filter"></span> Filters <span t-if="search_filters" class="float-right text-primary clear_survey_filter"><i class="fa fa-times"></i> Clear All Filters</span></div>
<div class="card-body">
- <span t-if="filter_finish == True">
- <span class="badge badge-secondary only_left_radius filter-all">All surveys</span><span class="badge badge-primary only_right_radius filter-finished o_active_filter">Finished surveys</span>
- </span>
- <span t-if="filter_finish == False">
- <span class="badge badge-primary only_left_radius filter-all o_active_filter">All surveys</span><span class="badge badge-secondary only_right_radius filter-finished">Finished surveys</span>
- </span>
- <span t-foreach="filter_display_data" t-as="filter_data">
+ <span t-att-class="'badge only_left_radius filter-all %s' % 'badge-secondary' if search_finished else 'badge-primary o_active_filter'">All surveys</span>
+ <span t-att-class="'badge only_right_radius filter-finished %s' % 'badge-secondary' if not search_finished else 'badge-primary o_active_filter'">Finished surveys</span>
+ <span t-foreach="search_filters" t-as="filter_data">
<span class="badge badge-primary only_left_radius"><i class="fa fa-filter" role="img" aria-label="Filter" title="Filter"></i></span><span class="badge badge-primary no_radius" t-esc="filter_data['question_text']"></span><span class="badge badge-success only_right_radius" t-esc="' > '.join(filter_data['labels'])"></span>
</span>
</div>
</div>
<div t-if="survey.scoring_type in ['scoring_with_answers', 'scoring_without_answers']">
<h1 class="mt16">Results Overview</h1>
- <div>Success rate: <mark class="font-weight-bold"><t t-esc="survey_dict['success_rate']"></t>%</mark></div>
- <div class="survey_graph" data-graph-type="pie" t-att-data-graph-data="survey_dict['scoring_graph_data']">
+ <div>Success rate: <mark class="font-weight-bold"><t t-esc="survey_data['global_success_rate']"></t>%</mark></div>
+ <div class="survey_graph" data-graph-type="pie" t-att-data-graph-data="survey_data['global_success_graph']">
<!-- canvas element for drawing pie chart -->
<canvas/>
</div>
<hr/>
</div>
- <div t-if="answers" t-foreach="survey_dict['page_ids']" t-as='page_ids'>
- <t t-set="page" t-value="page_ids['page']"/>
- <h1 class="mt16" t-field='page.title'></h1>
- <div t-field="page.description" class="oe_no_empty" />
- <hr/>
- <div t-foreach="page_ids['question_ids']" t-as='question_ids' class="mt16">
- <t t-set="input_summary" t-value="question_ids['input_summary']"/>
- <t t-set="question" t-value="question_ids['question']"/>
- <t t-set="graph_data" t-value="question_ids['graph_data']"/>
- <t t-set="prepare_result" t-value="question_ids['prepare_result']"/>
- <t t-set="question_with_correct_answers" t-value="survey.scoring_type in ['scoring_with_answers', 'scoring_without_answers'] and question.question_type in ['simple_choice', 'multiple_choice']" />
- <h4>
- <b>Question </b>
- <span t-field='question.title'></span>
- <t t-if="question.question_type == 'matrix'">
- <small><span class="badge badge-secondary">Matrix: <span t-field='question.matrix_subtype'/></span></small>
- </t>
- <t t-if="question.question_type in ['simple_choice', 'multiple_choice']">
- <small><span t-field='question.question_type' class="badge badge-secondary"></span></small>
- </t>
- <span class="float-right">
- <span class="badge badge-success"><span t-esc="input_summary['answered']"></span> Answered</span>
- <span class="badge badge-warning"><span t-esc="input_summary['skipped']"></span> Skipped</span>
- </span>
- </h4>
- <t t-if="question_with_correct_answers">
- <t t-set="correct_answers" t-value="question.get_correct_answer_ids()" />
- <div t-if="correct_answers">
- <t t-if="question.question_type == 'simple_choice'">
- <p><span>Correct answer</span>: <span class="font-weight-bold" t-esc="correct_answers[0].value"></span></p>
- </t>
- <t t-if="question.question_type == 'multiple_choice'">
- <span>Correct answers</span>:
- <ul>
- <li t-foreach="correct_answers" t-as="correct_answer">
- <t t-esc="correct_answer.value" />
- </li>
- </ul>
- </t>
+
+ <div t-foreach="question_and_page_data" t-as='question_data'>
+ <t t-set="question" t-value="question_data['question']"/>
+ <t t-if="question_data['is_page']">
+ <h2 class="mt16 text-uppercase text-muted" t-field="question.title"/>
+ <div t-field="question.description" class="oe_no_empty" />
+ <hr class="mt-2 pb-1 mb-1"/>
+ </t>
+ <div t-else="" class="ml-4 mt-4">
+ <t t-set="question_scoring" t-value="survey.scoring_type in ['scoring_with_answers', 'scoring_without_answers'] and question.question_type in ['simple_choice', 'multiple_choice']"/>
+ <t t-set="comment_lines" t-value="question_data['comment_line_ids']"/>
+ <t t-set="graph_data" t-value="question_data['graph_data']"/>
+ <t t-set="table_data" t-value="question_data['table_data']"/>
+
+ <div class="d-flex align-items-start mb-2">
+ <div class="mr-auto">
+ <h5 t-field="question.title" class="mb-1"/>
+ <div t-if="question_scoring">
+ <t t-if="len(question_data['right_answers']) >= 1">
+ <span t-if="len(question_data['right_answers']) == 1">Right answer: </span>
+ <span t-if="len(question_data['right_answers']) > 1">Right answers: </span>
+ <strong>
+ <t t-foreach="question_data['right_answers']" t-as="answer">
+ <span t-if="answer_index != 0">, </span><span t-esc="answer.value"/>
+ </t>
+ </strong>
+ </t>
+ </div>
+ <div class="ml-3 text-muted" t-field="question.description"/>
</div>
- </t>
- <t t-if="input_summary['answered'] != 0">
- <t t-if="question.description">
- <div class="text-muted oe_no_empty" t-field="question.description" />
- </t>
- <t t-if="question.question_type in ['char_box', 'text_box', 'date', 'datetime']">
- <t t-call="survey.question_result_text"></t>
- </t>
- <t t-if="question.question_type in ['simple_choice', 'multiple_choice']">
- <t t-call="survey.question_result_choice"></t>
- </t>
+ <!-- Question info -->
+ <span class="badge badge-info" t-field='question.question_type'/>
<t t-if="question.question_type == 'matrix'">
- <t t-call="survey.question_result_matrix"></t>
+ <span class="badge badge-info ml-2" t-field='question.matrix_subtype'/>
</t>
- <t t-if="question.question_type == 'numerical_box'">
- <t t-call="survey.question_result_number"></t>
+ <!-- Scoring info -->
+ <t t-if="question_scoring">
+ <span class="badge badge-success ml-3"><span t-esc="len(question_data['right_inputs'])"></span> Correct</span>
+ <span class="badge badge-warning ml-1" t-if="question.question_type == 'multiple_choice'">
+ <span t-esc="len(question_data['partial_inputs'])"></span> Partial
+ </span>
</t>
+ <!-- Inputs info -->
+ <span class="badge badge-info ml-3"><span t-esc="len(question_data['answer_input_done_ids'])"></span> Answered</span>
+ <span class="badge badge-info ml-1"><span t-esc="len(question_data['answer_input_skipped_ids'])"></span> Skipped</span>
+ </div>
+ <t t-if="question.question_type in ['text_box', 'char_box', 'date', 'datetime']">
+ <t t-call="survey.question_result_text"/>
+ </t>
+ <t t-elif="question.question_type in ['numerical_box']">
+ <t t-call="survey.question_result_number"></t>
</t>
- <t t-if="input_summary['answered'] == 0">
- <h2 style="padding-top:30px;padding-bottom:30px;text-align:center;" class="text-muted">Sorry, No one answered this question.</h2>
+ <t t-elif="question.question_type in ['simple_choice', 'multiple_choice']">
+ <t t-call="survey.question_result_choice"></t>
+ </t>
+ <t t-elif="question.question_type in ['matrix']">
+ <t t-call="survey.question_result_matrix"></t>
</t>
</div>
</div>
</div>
- <div class="oe_structure" id="oe_structure_survey_result_2"/>
</t>
</template>
- <!-- Result for text_box,char_box and date -->
- <template id="question_result_text" name="Question: text result">
- <table class="table table-hover table-sm" t-att-id="'table_question_%d' % question.id">
+ <template id="question_result_text" name="Question: text result (text_box, char_box, date, datetime)">
+ <table class="table table-hover table-sm" t-att-id="'survey_table_question_%d' % question.id">
<thead>
<tr>
<th>#</th>
@@ -115,245 +110,239 @@
</tr>
</thead>
<tbody>
- <t t-set="text_result" t-value="prepare_result"/>
- <tr t-foreach="text_result" t-as="user_input">
- <td><a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.access_token)"><t t-esc="user_input_index + 1"></t></a></td>
- <t t-if="question.question_type == 'text_box'">
+ <t t-foreach="table_data" t-as="input_line">
+ <tr t-att-class="'d-none' if input_line_index >= page_record_limit else ''">
<td>
- <a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.access_token)" t-field="user_input.value_text_box"></a><br/>
+ <a t-att-href="input_line.user_input_id.get_print_url()"><t t-esc="input_line_index + 1"></t></a>
</td>
- </t>
- <t t-if="question.question_type == 'char_box'">
<td>
- <a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.access_token)" t-field="user_input.value_char_box"></a><br/>
- </td>
- </t>
- <t t-if="question.question_type == 'date'">
- <td>
- <a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.access_token)" t-field="user_input.value_date"></a><br/>
- </td>
- </t>
- <t t-if="question.question_type == 'datetime'">
- <td>
- <a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.access_token)" t-field="user_input.value_datetime"></a><br/>
+ <t t-if="question.question_type == 'text_box'">
+ <a t-att-href="input_line.user_input_id.get_print_url()" t-field="input_line.value_text_box"></a>
+ </t>
+ <t t-if="question.question_type == 'char_box'">
+ <a t-att-href="input_line.user_input_id.get_print_url()" t-field="input_line.value_char_box"></a>
+ </t>
+ <t t-if="question.question_type == 'date'">
+ <a t-att-href="input_line.user_input_id.get_print_url()" t-field="input_line.value_date"></a>
+ </t>
+ <t t-if="question.question_type == 'datetime'">
+ <a t-att-href="input_line.user_input_id.get_print_url()" t-field="input_line.value_datetime"></a>
+ </t>
</td>
- </t>
- </tr>
+ </tr>
+ </t>
</tbody>
</table>
- <t t-call="survey.question_pagination"/>
+ <t t-call="survey.question_table_pagination"/>
</template>
- <template id="question_result_comments" name="Question: text tesult">
- <!-- a 'comments' variable must be set an must contain a list of browse records of user input lines -->
- <table class="table table-hover table-sm" t-att-id="'table_question_%d' % question.id">
- <thead>
- <tr>
- <th>#</th>
- <th>Comment</th>
- </tr>
- </thead>
- <tbody>
- <tr t-foreach="comments" t-as="user_input">
- <td><a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.access_token)"><t t-esc="user_input_index + 1"></t></a></td>
- <td>
- <span t-field="user_input.value_char_box"></span><br/>
- </td>
- </tr>
- </tbody>
- </table>
- </template>
+ <template id="question_result_number" name="Question: number result (numerical_box)">
+ <span class="float-right mt8">
+ <span class="badge badge-secondary only_left_radius">Maximum </span> <span class="badge badge-success only_right_radius" t-esc="question_data['numerical_max']"></span>
+ <span class="badge badge-secondary only_left_radius">Minimum </span> <span class="badge badge-danger only_right_radius" t-esc="question_data['numerical_min']"></span>
+ <span class="badge badge-secondary only_left_radius">Average </span> <span class="badge badge-warning only_right_radius" t-esc="question_data['numerical_average']"></span>
+ </span>
- <template id="question_result_choice" name="Question: choice result">
- <div>
- <!-- Tabs -->
- <ul class="nav nav-tabs d-print-none" role="tablist">
- <li class="nav-item" t-if="question.question_type != 'simple_choice'">
- <a t-att-href="'#graph_question_%d' % question.id" t-att-aria-controls="'graph_question_%d' % question.id" class="nav-link active" data-toggle="tab" role="tab">
- <i class="fa fa-bar-chart-o"></i> Graph
- </a>
- </li>
- <li class="nav-item" t-if="question.question_type == 'simple_choice'">
- <a t-att-href="'#graph_question_%d' % question.id" t-att-aria-controls="'graph_question_%d' % question.id" class="nav-link active" data-toggle="tab" role="tab">
- <i class="fa fa-bar-chart-o"></i> Pie Chart
- </a>
- </li>
- <li class="nav-item">
- <a t-att-href="'#data_question_%d' % question.id" t-att-aria-controls="'data_question_%d' % question.id" class="nav-link" data-toggle="tab" role="tab">
- <i class="fa fa-list-alt"></i> Data
- </a>
- </li>
- </ul>
- <div class="tab-content">
- <div role="tabpanel" class="tab-pane active survey_graph" t-if="question.question_type != 'simple_choice'" t-att-id="'graph_question_%d' % question.id" t-att-data-question_id="question.id" data-graph-type="bar" t-att-data-graph-data="graph_data">
- <!-- canvas element for drawing bar chart -->
- <canvas/>
- </div>
- <div role="tabpanel" class="tab-pane active survey_graph" t-if="question.question_type == 'simple_choice'" t-att-id="'graph_question_%d' % question.id" t-att-data-question_id="question.id" data-graph-type="pie" t-att-data-graph-data="graph_data">
- <!-- canvas element for drawing pie chart -->
- <canvas/>
- </div>
- <div role="tabpanel" class="tab-pane" t-att-id="'data_question_%d' % question.id">
- <table class="table table-hover table-sm">
- <thead>
- <tr>
- <th>Answer Choices</th>
- <th>User Responses</th>
- <th t-if="question_with_correct_answers">Answer Score</th>
- </tr>
- </thead>
- <tbody>
- <tr t-foreach="prepare_result['answers']" t-as="user_input">
+ <ul class="nav nav-tabs d-print-none" role="tablist">
+ <li class="nav-item">
+ <a t-att-href="'#survey_stats_question_%d' % question.id"
+ t-att-aria-controls="'survey_stats_question_%d' % question.id"
+ class="nav-link active" data-toggle="tab" role="tab">
+ <i class="fa fa-list-ol"></i> Most Common
+ </a>
+ </li>
+ <li class="nav-item">
+ <a t-att-href="'#survey_data_question_%d' % question.id"
+ t-att-aria-controls="'survey_data_question_%d' % question.id"
+ class="nav-link" data-toggle="tab" role="tab">
+ <i class="fa fa-list-alt"></i> All Data
+ </a>
+ </li>
+ </ul>
+ <div class="tab-content">
+ <div role="tabpanel" class="tab-pane active with-3d-shadow with-transitions" t-att-id="'survey_stats_question_%d' % question.id">
+ <table class="table table-hover table-sm">
+ <thead>
+ <tr>
+ <th>User Responses</th>
+ <th>Occurence</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr t-foreach="question_data['numerical_common_lines']" t-as="common_line">
+ <td><span t-esc="common_line[0]"></span></td>
+ <td><span t-esc="common_line[1]"></span></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <div role="tabpanel" class="tab-pane" t-att-id="'survey_data_question_%d' % question.id">
+ <table class="table table-hover table-sm" t-att-id="'survey_table_question_%d' % question.id">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>User Responses</th>
+ </tr>
+ </thead>
+ <tbody>
+ <t t-foreach="table_data" t-as="input_line">
+ <tr t-att-class="'d-none' if input_line_index >= page_record_limit else ''">
<td>
- <p t-esc="user_input['text']"></p>
- </td>
- <td class="survey_answer">
- <span t-esc="round(user_input['count']*100.0/(input_summary['answered'] or 1),2)"></span> %
- <span t-esc="user_input['count']" class="badge badge-primary">Vote</span>
- <i class="fa fa-filter text-primary survey_filter" t-att-data-question_id="question.id" t-att-data-answer_id="user_input['answer_id']" role="img" aria-label="Filter question" title="Filter question"/>
- </td>
- <td t-if="question_with_correct_answers" t-esc="user_input['answer_score']">
+ <a t-att-href="input_line.user_input_id.get_print_url()"><t t-esc="input_line_index + 1"></t></a>
</td>
+ <td><span t-field="input_line.value_numerical_box"></span><br/></td>
</tr>
- </tbody>
- </table>
- </div>
- </div>
- <!-- handle comments -->
- <div>
- <t t-set="comments" t-value="prepare_result['comments']" />
- <t t-if="comments">
- <t t-call="survey.question_result_comments" />
- </t>
+ </t>
+ </tbody>
+ </table>
+ <t t-call="survey.question_table_pagination"/>
</div>
-
</div>
</template>
- <template id="question_result_matrix" name="Question: matrix result">
- <t t-set="matrix_result" t-value="prepare_result"/>
- <!-- Tabs -->
+ <template id="question_result_choice" name="Question: choice result (simple_choice, multiple_choice)">
<ul class="nav nav-tabs d-print-none" role="tablist">
<li class="nav-item">
- <a t-att-href="'#graph_question_%d' % question.id" t-att-aria-controls="'graph_question_%d' % question.id" class="nav-link active" data-toggle="tab" role="tab">
- <i class="fa fa-bar-chart"></i>
- Graph
+ <a t-att-href="'#survey_graph_question_%d' % question.id"
+ t-att-aria-controls="'survey_graph_question_%d' % question.id"
+ class="nav-link active" data-toggle="tab" role="tab">
+ <i class="fa fa-bar-chart-o"></i>
+ <span t-if="question.question_type == 'simple_choice'">Pie Graph</span>
+ <span t-else="">Graph</span>
</a>
</li>
<li class="nav-item">
- <a t-att-href="'#data_question_%d' % question.id" t-att-aria-controls="'data_question_%d' % question.id" class="nav-link" data-toggle="tab" role="tab">
- <i class="fa fa-list-alt"></i>
- Data
+ <a t-att-href="'#survey_data_question_%d' % question.id"
+ t-att-aria-controls="'survey_data_question_%d' % question.id"
+ class="nav-link" data-toggle="tab" role="tab">
+ <i class="fa fa-list-alt"></i> Data
</a>
</li>
</ul>
<div class="tab-content">
- <div role="tabpanel" class="tab-pane active with-3d-shadow with-transitions survey_graph" t-att-id="'graph_question_%d' % question.id" t-att-data-question_id= "question.id" data-graph-type= "multi_bar" t-att-data-graph-data="graph_data">
- <!-- canvas element for drawing Multibar chart -->
+ <div role="tabpanel" class="tab-pane active survey_graph"
+ t-att-id="'survey_graph_question_%d' % question.id"
+ t-att-data-question_id="question.id"
+ t-att-data-graph-type="'pie' if question.question_type == 'simple_choice' else 'bar'"
+ t-att-data-graph-data="graph_data">
+ <!-- canvas element for drawing bar chart -->
<canvas/>
</div>
- <div role="tabpanel" class="tab-pane" t-att-id="'data_question_%d' % question.id">
- <table class="table table-hover table-sm text-right">
+ <div role="tabpanel" class="tab-pane" t-att-id="'survey_data_question_%d' % question.id">
+ <table class="table table-hover table-sm">
<thead>
<tr>
- <th></th>
- <th class="text-right" t-foreach="matrix_result['answers']" t-as="answer_id">
- <span t-esc="matrix_result['answers'][answer_id]"></span>
- </th>
+ <th>Answer</th>
+ <th>User Choice</th>
+ <th t-if="question_scoring">Score</th>
</tr>
</thead>
<tbody>
- <tr t-foreach="matrix_result['rows']" t-as="row_id">
+ <tr t-foreach="table_data" t-as="choice_data">
<td>
- <span t-esc="matrix_result['rows'][row_id]"></span>
+ <p t-esc="choice_data['value']"></p>
</td>
- <td class="survey_answer" t-foreach="matrix_result['answers']" t-as="answer_id">
- <span t-esc="round(matrix_result['result'][(row_id,answer_id)]*100.0/(input_summary['answered'] or 1),2)"></span> %
- <span class="badge badge-primary" t-esc="matrix_result['result'][(row_id,answer_id)]"></span><i class="fa fa-filter text-primary survey_filter" t-att-data-question_id="question.id" t-att-data-row_id="row_id" t-att-data-answer_id="answer_id" role="img" aria-label="Survey filter" title="Survey filter"></i>
+ <td class="survey_answer">
+ <span t-esc="round(choice_data['count'] * 100.0/ (len(question_data['answer_line_done_ids']) or 1), 2)"></span> %
+ <span t-esc="'%s Votes' % choice_data['count']" class="badge badge-primary"/>
+ <i class="fa fa-filter text-primary survey_filter"
+ t-att-data-question_id="question.id"
+ t-att-data-answer_id="choice_data['suggested_answer'].id"
+ role="img" aria-label="Filter question" title="Filter question"/>
</td>
+ <td t-if="question_scoring" t-esc="choice_data['suggested_answer'].answer_score"/>
</tr>
</tbody>
</table>
</div>
- <!-- handle comments to matrix -->
- <div>
- <t t-set="comments" t-value="matrix_result['comments']" />
- <t t-if="comments">
- <t t-call="survey.question_result_comments" />
- </t>
- </div>
</div>
+ <div t-if="comment_lines" t-call="survey.question_result_comments" />
</template>
- <template id="question_result_number" name="Question:; number result">
- <t t-set="number_result" t-value="prepare_result"/>
- <t t-set="text_result" t-value="number_result['input_lines']" />
- <span class="float-right mt8">
- <span class="badge badge-secondary only_left_radius">Sum </span> <span class="badge badge-info only_right_radius" t-esc="number_result['sum']"></span>
- <span class="badge badge-secondary only_left_radius">Maximum </span> <span class="badge badge-success only_right_radius" t-esc="number_result['max']"></span>
- <span class="badge badge-secondary only_left_radius">Minimum </span> <span class="badge badge-danger only_right_radius" t-esc="number_result['min']"></span>
- <span class="badge badge-secondary only_left_radius">Average </span> <span class="badge badge-warning only_right_radius" t-esc="number_result['average']"></span>
- </span>
+ <template id="question_result_matrix" name="Question: matrix result (matrix)">
+ <t t-set="graph_data" t-value="question_data['graph_data']"/>
+ <t t-set="table_data" t-value="question_data['table_data']"/>
<ul class="nav nav-tabs d-print-none" role="tablist">
<li class="nav-item">
- <a t-att-href="'#most_common_%d' % question.id" t-att-aria-controls="'most_common_%d' % question.id" class="nav-link active" data-toggle="tab" role="tab">
- <i class="fa fa-list-ol"></i>
- Most Common <span t-esc="len(number_result['most_common'])"></span>
+ <a t-att-href="'#survey_graph_question_%d' % question.id"
+ t-att-aria-controls="'survey_graph_question_%d' % question.id"
+ class="nav-link active" data-toggle="tab" role="tab">
+ <i class="fa fa-bar-chart"></i> Graph
</a>
</li>
<li class="nav-item">
- <a t-att-href="'#data_question_%d' % question.id" t-att-aria-controls="'data_question_%d' % question.id" class="nav-link" data-toggle="tab" role="tab">
- <i class="fa fa-list-alt"></i>
- All Data
+ <a t-att-href="'#survey_data_question_%d' % question.id"
+ t-att-aria-controls="'survey_data_question_%d' % question.id"
+ class="nav-link" data-toggle="tab" role="tab">
+ <i class="fa fa-list-alt"></i> Data
</a>
</li>
</ul>
<div class="tab-content">
- <div role="tabpanel" class="tab-pane active with-3d-shadow with-transitions" t-att-id="'most_common_%d' % question.id">
- <table class="table table-hover table-sm">
- <thead>
- <tr>
- <th>User Responses</th>
- <th>Occurence</th>
- </tr>
- </thead>
- <tbody>
- <tr t-foreach="number_result['most_common']" t-as="row">
- <td>
- <span t-esc="row[0]"></span>
- </td>
- <td>
- <span t-esc="row[1]"></span>
- </td>
- </tr>
- </tbody>
- </table>
+ <div role="tabpanel" class="tab-pane active with-3d-shadow with-transitions survey_graph"
+ t-att-id="'survey_graph_question_%d' % question.id"
+ t-att-data-question_id= "question.id"
+ data-graph-type= "multi_bar"
+ t-att-data-graph-data="graph_data">
+ <!-- canvas element for drawing Multibar chart -->
+ <canvas/>
</div>
- <div role="tabpanel" class="tab-pane" t-att-id="'data_question_%d' % question.id">
- <table class="table table-hover table-sm" t-att-id="'table_question_%d' % question.id">
+ <div role="tabpanel" class="tab-pane" t-att-id="'survey_data_question_%d' % question.id">
+ <table class="table table-hover table-sm text-right">
<thead>
<tr>
- <th>#</th>
- <th>User Responses</th>
+ <th></th>
+ <th class="text-right" t-foreach="table_data[0]['columns']" t-as="column_data">
+ <span t-esc="column_data['suggested_answer'].value"></span>
+ </th>
</tr>
</thead>
<tbody>
- <tr class="d-none" t-foreach="number_result['input_lines']" t-as="user_input">
- <td><a t-att-href="'/survey/print/%s?answer_token=%s' % (user_input.survey_id.access_token, user_input.user_input_id.access_token)"><t t-esc="user_input_index + 1"></t></a></td>
- <td><span t-field="user_input.value_numerical_box"></span><br/></td>
+ <tr t-foreach="table_data" t-as="choice_data">
+ <td>
+ <span t-esc="choice_data['row'].value"></span>
+ </td>
+ <td class="survey_answer" t-foreach="choice_data['columns']" t-as="column_data">
+ <span t-esc="round(column_data['count'] * 100.0/ (len(question_data['answer_line_done_ids']) or 1), 2)"></span> %
+ <span class="badge badge-primary" t-esc="column_data['count']"></span>
+ <i class="fa fa-filter text-primary survey_filter"
+ t-att-data-question_id="question.id"
+ t-att-data-row_id="choice_data['row'].id"
+ t-att-data-answer_id="column_data['suggested_answer'].id" role="img" aria-label="Survey filter" title="Survey filter"></i>
+ </td>
</tr>
</tbody>
</table>
- <t t-call="survey.question_pagination"/>
</div>
+ <div t-if="comment_lines" t-call="survey.question_result_comments" />
</div>
</template>
- <template id="question_pagination" name="Survey: statistics pages pagination">
- <t t-set="record_limit" t-value="10"/><!-- Change This record_limit to change number of record per page-->
- <ul t-att-id="'pagination_%d' % question.id" class="pagination" t-att-data-question_id="question.id" t-att-data-record_limit="record_limit">
- <t t-if="len(text_result) > record_limit">
- <li t-foreach="page_range(len(text_result), record_limit)" t-as="num" class="page-item">
+ <template id="question_result_comments" name="Question: comments">
+ <table class="table table-hover table-sm" t-att-id="'survey_table_question_%d' % question.id">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>Comment</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr t-foreach="comment_lines" t-as="input_line">
+ <td><a t-att-href="input_line.user_input_id.get_print_url()"><t t-esc="input_line_index + 1"></t></a></td>
+ <td>
+ <span t-field="input_line.value_char_box"></span><br/>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </template>
+
+ <template id="question_table_pagination" name="Survey: statistics table pagination">
+ <ul t-att-id="'pagination_%d' % question.id" class="pagination" t-att-data-question_id="question.id" t-att-data-record_limit="page_record_limit">
+ <t t-if="len(table_data) > page_record_limit">
+ <t t-set="total" t-value="ceil(len(table_data) / page_record_limit) + 1"/>
+ <li t-foreach="range(1, total)" t-as="num"
+ t-att-class="'page-item o_survey_js_results_pagination %s' % ('active' if num == 1 else '')">
<a href="#" class="page-link" t-esc="num"></a>
</li>
</t>