diff options
Diffstat (limited to 'addons')
| -rw-r--r-- | addons/survey/controllers/main.py | 212 | ||||
| -rw-r--r-- | addons/survey/models/survey_question.py | 152 | ||||
| -rw-r--r-- | addons/survey/models/survey_survey.py | 160 | ||||
| -rw-r--r-- | addons/survey/models/survey_user.py | 1 | ||||
| -rw-r--r-- | addons/survey/static/src/js/survey_result.js | 25 | ||||
| -rw-r--r-- | addons/survey/tests/test_survey.py | 50 | ||||
| -rw-r--r-- | addons/survey/views/survey_templates_management.xml | 7 | ||||
| -rw-r--r-- | addons/survey/views/survey_templates_statistics.xml | 487 |
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> |
