From 4fbd4a8c6a0dbf9d0ba039c7f0d61e8b26d9b51d Mon Sep 17 00:00:00 2001 From: Jitendra Prajapati Date: Thu, 29 Aug 2019 12:04:26 +0000 Subject: [PATCH 1/4] [IMP] survey: improve wording and labels PURPOSE Perform a back2basics review of survey to improve daily use, new user onboarding and labelling. SPECIFICATIONS Improve labels of fields and wording in actions. See commit content for proper specifications. LINKS Task 2039068 PR #36229 --- addons/survey/models/survey_survey.py | 2 +- addons/survey/models/survey_user.py | 4 ++-- addons/survey/views/survey_survey_views.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/survey/models/survey_survey.py b/addons/survey/models/survey_survey.py index 64a74f57652..22d03ad9308 100644 --- a/addons/survey/models/survey_survey.py +++ b/addons/survey/models/survey_survey.py @@ -25,7 +25,7 @@ class Survey(models.Model): return str(uuid.uuid4()) # description - title = fields.Char('Title', required=True, translate=True) + title = fields.Char('Survey Title', required=True, translate=True) description = fields.Html("Description", translate=True, help="The description will be displayed on the home page of the survey. You can use this to give the purpose and guidelines to your candidates before they start it.") color = fields.Integer('Color Index', default=0) diff --git a/addons/survey/models/survey_user.py b/addons/survey/models/survey_user.py index 3a53d73f439..3dd913f2ff8 100644 --- a/addons/survey/models/survey_user.py +++ b/addons/survey/models/survey_user.py @@ -202,7 +202,7 @@ class SurveyUserInputLine(models.Model): user_input_id = fields.Many2one('survey.user_input', string='User Input', ondelete='cascade', required=True) question_id = fields.Many2one('survey.question', string='Question', ondelete='cascade', required=True) - page_id = fields.Many2one(related='question_id.page_id', string="Page", readonly=False) + page_id = fields.Many2one(related='question_id.page_id', string="Section", readonly=False) survey_id = fields.Many2one(related='user_input_id.survey_id', string='Survey', store=True, readonly=False) skipped = fields.Boolean('Skipped') answer_type = fields.Selection([ @@ -219,7 +219,7 @@ class SurveyUserInputLine(models.Model): value_free_text = fields.Text('Free Text answer') value_suggested = fields.Many2one('survey.label', string="Suggested answer") value_suggested_row = fields.Many2one('survey.label', string="Row answer") - answer_score = fields.Float('Score given for this choice') + answer_score = fields.Float('Score') @api.constrains('skipped', 'answer_type') def _answered_or_skipped(self): diff --git a/addons/survey/views/survey_survey_views.xml b/addons/survey/views/survey_survey_views.xml index 2c16f8f5992..19249cc12bd 100644 --- a/addons/survey/views/survey_survey_views.xml +++ b/addons/survey/views/survey_survey_views.xml @@ -44,7 +44,7 @@
@@ -257,7 +257,7 @@

You can create surveys for different purposes: customer opinion, services feedback, recruitment interviews, employee's periodical evaluations, marketing campaigns, etc.

- Design easily your survey, send invitations to answer by email and analyze answers. + Design easily your survey, send invitations and analyze answers.

From 06363470992fb5315976b11cd72ee627a89c75e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Thu, 29 Aug 2019 12:39:54 +0000 Subject: [PATCH 2/4] [IMP] survey: reorder some code to ease understanding PURPOSE Perform a back2basics review of survey to improve daily use, new user onboarding and labelling. SPECIFICATIONS Reorder some code to better understand module organization. LINKS Task 2039068 PR #36229 --- addons/survey/controllers/main.py | 12 + addons/survey/models/survey_survey.py | 334 ++++++++++++---------- addons/survey/models/survey_user.py | 4 +- addons/survey/views/survey_user_views.xml | 26 +- 4 files changed, 204 insertions(+), 172 deletions(-) diff --git a/addons/survey/controllers/main.py b/addons/survey/controllers/main.py index 045661c5d75..9ab1f366ac3 100644 --- a/addons/survey/controllers/main.py +++ b/addons/survey/controllers/main.py @@ -20,6 +20,10 @@ _logger = logging.getLogger(__name__) class Survey(http.Controller): + # ------------------------------------------------------------ + # ACCESS + # ------------------------------------------------------------ + def _fetch_from_access_token(self, survey_token, answer_token): """ Check that given token matches an answer from the given survey_id. Returns a sudo-ed browse record of survey in order to avoid access rights @@ -182,6 +186,10 @@ class Survey(http.Controller): 'deadline': answer.deadline, } + # ------------------------------------------------------------ + # TAKING SURVEY ROUTES + # ------------------------------------------------------------ + @http.route('/survey/start/', type='http', auth='public', website=True) def survey_start(self, survey_token, answer_token=None, email=False, **post): """ Start a survey by providing @@ -423,6 +431,10 @@ class Survey(http.Controller): return json.dumps(ret) + # ------------------------------------------------------------ + # COMPLETED SURVEY ROUTES + # ------------------------------------------------------------ + @http.route('/survey/print/', type='http', auth='public', website=True) def survey_print(self, survey_token, review=False, answer_token=None, **post): '''Display an survey in printable view; if is set, it will diff --git a/addons/survey/models/survey_survey.py b/addons/survey/models/survey_survey.py index 22d03ad9308..d3ea6462a5a 100644 --- a/addons/survey/models/survey_survey.py +++ b/addons/survey/models/survey_survey.py @@ -8,7 +8,7 @@ from itertools import product from werkzeug import urls import random -from odoo import api, fields, models, SUPERUSER_ID, _ +from odoo import api, fields, models, _ from odoo.exceptions import UserError from odoo.osv import expression @@ -188,7 +188,10 @@ class Survey(models.Model): if not self.users_login_required or not self.certificate: self.certification_give_badge = False + # ------------------------------------------------------------ # CRUD + # ------------------------------------------------------------ + @api.model def create(self, vals): survey = super(Survey, self).create(vals) @@ -202,12 +205,15 @@ class Survey(models.Model): return self.sudo()._handle_certification_badges(vals) return result - # Public methods # def copy_data(self, default=None): title = _("%s (copy)") % (self.title) default = dict(default or {}, title=title) return super(Survey, self).copy_data(default) + # ------------------------------------------------------------ + # TECHNICAL + # ------------------------------------------------------------ + def _create_answer(self, user=False, partner=False, email=False, test_entry=False, check_attempts=True, **additional_vals): """ Main entry point to get a token back or create a new one. This method does check for current user access in order to explicitely validate @@ -278,6 +284,60 @@ class Survey(models.Model): if check_attempts and not self._has_attempts_left(partner or (user and user.partner_id), email, invite_token): raise UserError(_('No attempts left.')) + def _prepare_answer_questions(self): + """ Will generate the questions for a randomized survey. + It uses the random_questions_count of every sections of the survey to + pick a random number of questions and returns the merged recordset """ + self.ensure_one() + + questions = self.env['survey.question'] + + for page in self.page_ids: + if self.questions_selection == 'all': + questions |= page.question_ids + else: + if page.random_questions_count > 0 and len(page.question_ids) > page.random_questions_count: + questions = questions.concat(*random.sample(page.question_ids, page.random_questions_count)) + else: + questions |= page.question_ids + + if not questions: + questions = self.question_ids + + return questions + + def _has_attempts_left(self, partner, email, invite_token): + self.ensure_one() + + if (self.access_mode != 'public' or self.users_login_required) and self.is_attempts_limited: + return self._get_number_of_attempts_lefts(partner, email, invite_token) > 0 + + return True + + def _get_number_of_attempts_lefts(self, partner, email, invite_token): + """ Returns the number of attempts left. """ + self.ensure_one() + + domain = [ + ('survey_id', '=', self.id), + ('test_entry', '=', False), + ('state', '=', 'done') + ] + + if partner: + domain = expression.AND([domain, [('partner_id', '=', partner.id)]]) + else: + domain = expression.AND([domain, [('email', '=', email)]]) + + if invite_token: + domain = expression.AND([domain, [('invite_token', '=', invite_token)]]) + + return self.attempts_limit - self.env['survey.user_input'].search_count(domain) + + # ------------------------------------------------------------ + # ACTIONS + # ------------------------------------------------------------ + @api.model def next_page_or_question(self, user_input, page_or_question_id, go_back=False): """ The next page to display to the user, knowing that page_id is the id @@ -329,6 +389,115 @@ class Survey(models.Model): else: return (pages_or_questions[current_page_index + 1][1], False) + def action_draft(self): + self.write({'state': 'draft'}) + + def action_open(self): + self.write({'state': 'open'}) + + def action_close(self): + self.write({'state': 'closed'}) + + def action_start_survey(self): + """ Open the website page with the survey form """ + self.ensure_one() + token = self.env.context.get('survey_token') + trail = "?answer_token=%s" % token if token else "" + return { + 'type': 'ir.actions.act_url', + 'name': "Start Survey", + 'target': 'self', + 'url': self.public_url + trail + } + + def action_send_survey(self): + """ Open a window to compose an email, pre-filled with the survey message """ + # Ensure that this survey has at least one page with at least one question. + if (not self.page_ids and self.questions_layout == 'page_per_section') or not self.question_ids: + raise UserError(_('You cannot send an invitation for a survey that has no questions.')) + + if self.state == 'closed': + raise UserError(_("You cannot send invitations for closed surveys.")) + + template = self.env.ref('survey.mail_template_user_input_invite', raise_if_not_found=False) + + local_context = dict( + self.env.context, + default_survey_id=self.id, + default_use_template=bool(template), + default_template_id=template and template.id or False, + notif_layout='mail.mail_notification_light', + ) + return { + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_model': 'survey.invite', + 'target': 'new', + 'context': local_context, + } + + def action_print_survey(self): + """ Open the website page with the survey printable view """ + self.ensure_one() + token = self.env.context.get('survey_token') + trail = "?answer_token=%s" % token if token else "" + return { + 'type': 'ir.actions.act_url', + 'name': "Print Survey", + 'target': 'self', + 'url': '/survey/print/%s%s' % (self.access_token, trail) + } + + def action_result_survey(self): + """ Open the website page with the survey results view """ + self.ensure_one() + return { + 'type': 'ir.actions.act_url', + 'name': "Results of the Survey", + 'target': 'self', + 'url': '/survey/results/%s' % self.id + } + + def action_test_survey(self): + ''' Open the website page with the survey form into test mode''' + self.ensure_one() + return { + 'type': 'ir.actions.act_url', + 'name': "Test Survey", + 'target': 'self', + 'url': '/survey/test/%s' % self.access_token, + } + + def action_survey_user_input_completed(self): + action_rec = self.env.ref('survey.action_survey_user_input_notest') + action = action_rec.read()[0] + ctx = dict(self.env.context) + ctx.update({'search_default_survey_id': self.ids[0], + 'search_default_completed': 1}) + action['context'] = ctx + return action + + def action_survey_user_input_certified(self): + action_rec = self.env.ref('survey.action_survey_user_input_notest') + action = action_rec.read()[0] + ctx = dict(self.env.context) + ctx.update({'search_default_survey_id': self.ids[0], + 'search_default_quizz_passed': 1}) + action['context'] = ctx + return action + + def action_survey_user_input(self): + action_rec = self.env.ref('survey.action_survey_user_input_notest') + action = action_rec.read()[0] + ctx = dict(self.env.context) + ctx.update({'search_default_survey_id': self.ids[0]}) + action['context'] = ctx + return action + + # ------------------------------------------------------------ + # 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. @@ -453,6 +622,10 @@ class Survey(models.Model): return result + # ------------------------------------------------------------ + # GAMIFICATION / BADGES + # ------------------------------------------------------------ + def _create_certification_badge_trigger(self): self.ensure_one() goal = self.env['gamification.goal.definition'].create({ @@ -502,160 +675,3 @@ class Survey(models.Model): # delete all challenges and goals because not needed anymore (challenge lines are deleted in cascade) challenges_to_delete.unlink() goals_to_delete.unlink() - - # Actions - - def action_draft(self): - self.write({'state': 'draft'}) - - def action_open(self): - self.write({'state': 'open'}) - - def action_close(self): - self.write({'state': 'closed'}) - - def action_start_survey(self): - """ Open the website page with the survey form """ - self.ensure_one() - token = self.env.context.get('survey_token') - trail = "?answer_token=%s" % token if token else "" - return { - 'type': 'ir.actions.act_url', - 'name': "Start Survey", - 'target': 'self', - 'url': self.public_url + trail - } - - def action_send_survey(self): - """ Open a window to compose an email, pre-filled with the survey message """ - # Ensure that this survey has at least one page with at least one question. - if (not self.page_ids and self.questions_layout == 'page_per_section') or not self.question_ids: - raise UserError(_('You cannot send an invitation for a survey that has no questions.')) - - if self.state == 'closed': - raise UserError(_("You cannot send invitations for closed surveys.")) - - template = self.env.ref('survey.mail_template_user_input_invite', raise_if_not_found=False) - - local_context = dict( - self.env.context, - default_survey_id=self.id, - default_use_template=bool(template), - default_template_id=template and template.id or False, - notif_layout='mail.mail_notification_light', - ) - return { - 'type': 'ir.actions.act_window', - 'view_mode': 'form', - 'res_model': 'survey.invite', - 'target': 'new', - 'context': local_context, - } - - def action_print_survey(self): - """ Open the website page with the survey printable view """ - self.ensure_one() - token = self.env.context.get('survey_token') - trail = "?answer_token=%s" % token if token else "" - return { - 'type': 'ir.actions.act_url', - 'name': "Print Survey", - 'target': 'self', - 'url': '/survey/print/%s%s' % (self.access_token, trail) - } - - def action_result_survey(self): - """ Open the website page with the survey results view """ - self.ensure_one() - return { - 'type': 'ir.actions.act_url', - 'name': "Results of the Survey", - 'target': 'self', - 'url': '/survey/results/%s' % self.id - } - - def action_test_survey(self): - ''' Open the website page with the survey form into test mode''' - self.ensure_one() - return { - 'type': 'ir.actions.act_url', - 'name': "Test Survey", - 'target': 'self', - 'url': '/survey/test/%s' % self.access_token, - } - - def action_survey_user_input_completed(self): - action_rec = self.env.ref('survey.action_survey_user_input_notest') - action = action_rec.read()[0] - ctx = dict(self.env.context) - ctx.update({'search_default_survey_id': self.ids[0], - 'search_default_completed': 1}) - action['context'] = ctx - return action - - def action_survey_user_input_certified(self): - action_rec = self.env.ref('survey.action_survey_user_input_notest') - action = action_rec.read()[0] - ctx = dict(self.env.context) - ctx.update({'search_default_survey_id': self.ids[0], - 'search_default_quizz_passed': 1}) - action['context'] = ctx - return action - - def action_survey_user_input(self): - action_rec = self.env.ref('survey.action_survey_user_input_notest') - action = action_rec.read()[0] - ctx = dict(self.env.context) - ctx.update({'search_default_survey_id': self.ids[0]}) - action['context'] = ctx - return action - - def _has_attempts_left(self, partner, email, invite_token): - self.ensure_one() - - if (self.access_mode != 'public' or self.users_login_required) and self.is_attempts_limited: - return self._get_number_of_attempts_lefts(partner, email, invite_token) > 0 - - return True - - def _get_number_of_attempts_lefts(self, partner, email, invite_token): - """ Returns the number of attempts left. """ - self.ensure_one() - - domain = [ - ('survey_id', '=', self.id), - ('test_entry', '=', False), - ('state', '=', 'done') - ] - - if partner: - domain = expression.AND([domain, [('partner_id', '=', partner.id)]]) - else: - domain = expression.AND([domain, [('email', '=', email)]]) - - if invite_token: - domain = expression.AND([domain, [('invite_token', '=', invite_token)]]) - - return self.attempts_limit - self.env['survey.user_input'].search_count(domain) - - def _prepare_answer_questions(self): - """ Will generate the questions for a randomized survey. - It uses the random_questions_count of every sections of the survey to - pick a random number of questions and returns the merged recordset """ - self.ensure_one() - - questions = self.env['survey.question'] - - for page in self.page_ids: - if self.questions_selection == 'all': - questions |= page.question_ids - else: - if page.random_questions_count > 0 and len(page.question_ids) > page.random_questions_count: - questions = questions.concat(*random.sample(page.question_ids, page.random_questions_count)) - else: - questions |= page.question_ids - - if not questions: - questions = self.question_ids - - return questions diff --git a/addons/survey/models/survey_user.py b/addons/survey/models/survey_user.py index 3dd913f2ff8..b7fa5f1f92a 100644 --- a/addons/survey/models/survey_user.py +++ b/addons/survey/models/survey_user.py @@ -200,10 +200,12 @@ class SurveyUserInputLine(models.Model): _description = 'Survey User Input Line' _rec_name = 'user_input_id' + # survey data user_input_id = fields.Many2one('survey.user_input', string='User Input', ondelete='cascade', required=True) + survey_id = fields.Many2one(related='user_input_id.survey_id', string='Survey', store=True, readonly=False) question_id = fields.Many2one('survey.question', string='Question', ondelete='cascade', required=True) page_id = fields.Many2one(related='question_id.page_id', string="Section", readonly=False) - survey_id = fields.Many2one(related='user_input_id.survey_id', string='Survey', store=True, readonly=False) + # answer skipped = fields.Boolean('Skipped') answer_type = fields.Selection([ ('text', 'Text'), diff --git a/addons/survey/views/survey_user_views.xml b/addons/survey/views/survey_user_views.xml index 0725db4ba88..fd7249da958 100644 --- a/addons/survey/views/survey_user_views.xml +++ b/addons/survey/views/survey_user_views.xml @@ -2,8 +2,8 @@ - - survey_user_input_search + + survey.user_input.view.search survey.user_input @@ -26,8 +26,9 @@ - - survey_user_input_form + + + survey.user_input.view.form survey.user_input
@@ -76,8 +77,9 @@
- - survey_user_input_tree + + + survey.user_input.view.tree survey.user_input @@ -96,8 +98,8 @@ - - survey.user_input.kanban + + survey.user_input.view.kanban survey.user_input @@ -135,8 +137,8 @@ Answers survey.user_input tree,kanban,form - - + + {'search_default_group_by_survey': True}

@@ -148,8 +150,8 @@ Answers survey.user_input tree,kanban,form - - + + {'search_default_group_by_survey': True} [('test_entry', '=', False)] From ee5649cdff08b0cd413a8d1d6b0d01aea290e50a Mon Sep 17 00:00:00 2001 From: Jitendra Prajapati Date: Thu, 29 Aug 2019 13:11:32 +0000 Subject: [PATCH 3/4] [IMP] survey: improve user answers model and display in backend PURPOSE Perform a back2basics review of survey to improve daily use, new user onboarding and labelling. SPECIFICATIONS Order answer lines following question order. Display section in answer lines form view to better understand question and answer organization. Add a correct flag on answer lines and display it in user input embedded view in order to know which line are correct. LINKS Task 2039068 PR #36229 --- addons/survey/models/survey_user.py | 11 +++++++++++ addons/survey/views/survey_user_views.xml | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/addons/survey/models/survey_user.py b/addons/survey/models/survey_user.py index b7fa5f1f92a..8cfa9e9dde4 100644 --- a/addons/survey/models/survey_user.py +++ b/addons/survey/models/survey_user.py @@ -199,12 +199,14 @@ class SurveyUserInputLine(models.Model): _name = 'survey.user_input_line' _description = 'Survey User Input Line' _rec_name = 'user_input_id' + _order = 'question_sequence,id' # survey data user_input_id = fields.Many2one('survey.user_input', string='User Input', ondelete='cascade', required=True) survey_id = fields.Many2one(related='user_input_id.survey_id', string='Survey', store=True, readonly=False) question_id = fields.Many2one('survey.question', string='Question', ondelete='cascade', required=True) page_id = fields.Many2one(related='question_id.page_id', string="Section", readonly=False) + question_sequence = fields.Integer('Sequence', related='question_id.sequence', store=True) # answer skipped = fields.Boolean('Skipped') answer_type = fields.Selection([ @@ -222,6 +224,15 @@ class SurveyUserInputLine(models.Model): value_suggested = fields.Many2one('survey.label', string="Suggested answer") value_suggested_row = fields.Many2one('survey.label', string="Row answer") answer_score = fields.Float('Score') + answer_is_correct = fields.Boolean('Correct', compute='_compute_answer_is_correct') + + @api.depends('value_suggested', 'question_id') + def _compute_answer_is_correct(self): + for answer in self: + if answer.value_suggested and answer.question_id.question_type in ['simple_choice', 'multiple_choice']: + answer.answer_is_correct = answer.value_suggested.is_correct + else: + answer.answer_is_correct = False @api.constrains('skipped', 'answer_type') def _answered_or_skipped(self): diff --git a/addons/survey/views/survey_user_views.xml b/addons/survey/views/survey_user_views.xml index fd7249da958..3d9384ead5d 100644 --- a/addons/survey/views/survey_user_views.xml +++ b/addons/survey/views/survey_user_views.xml @@ -66,11 +66,14 @@ + + - + + From 240e860f24832d0201e968fa30c837792a6a8816 Mon Sep 17 00:00:00 2001 From: Jitendra Prajapati Date: Thu, 29 Aug 2019 15:20:13 +0000 Subject: [PATCH 4/4] [IMP] survey: add an overall correctness graph when finishing a certification PURPOSE Perform a back2basics review of survey to improve daily use, new user onboarding and labelling. SPECIFICATIONS When finishing a certification survey, add an overall correctness circular graph. Quarters are * correct * partially (if multiple corrects answers on the survey); * incorrect * unanswered Questions without points are not displayed in this graph. LINKS Task 2039068 PR #36229 --- addons/survey/controllers/main.py | 8 +++++ addons/survey/models/survey_survey.py | 33 ++++++++++++++++++ addons/survey/static/src/js/survey_result.js | 35 ++++++++++++++++++++ addons/survey/views/survey_templates.xml | 9 +++++ 4 files changed, 85 insertions(+) diff --git a/addons/survey/controllers/main.py b/addons/survey/controllers/main.py index 9ab1f366ac3..7eabe92f733 100644 --- a/addons/survey/controllers/main.py +++ b/addons/survey/controllers/main.py @@ -651,4 +651,12 @@ class Survey(http.Controller): values = {'survey': survey, 'answer': answer} if token: values['token'] = token + if survey.scoring_type != 'no_scoring' and survey.certificate: + answer_perf = survey._get_answers_correctness(answer)[answer] + values['graph_data'] = json.dumps([ + {"text": "Correct", "count": answer_perf['correct']}, + {"text": "Partially", "count": answer_perf['partial']}, + {"text": "Incorrect", "count": answer_perf['incorrect']}, + {"text": "Unanswered", "count": answer_perf['skipped']} + ]) return values diff --git a/addons/survey/models/survey_survey.py b/addons/survey/models/survey_survey.py index d3ea6462a5a..5418d414603 100644 --- a/addons/survey/models/survey_survey.py +++ b/addons/survey/models/survey_survey.py @@ -622,6 +622,39 @@ class Survey(models.Model): return result + def _get_answers_correctness(self, user_answers): + if not user_answers.mapped('survey_id') == self: + raise UserError(_('Invalid performance computation')) + + res = dict((user_answer, { + 'correct': 0, + 'incorrect': 0, + 'partial': 0, + 'skipped': 0, + }) for user_answer in user_answers) + + scored_questions = self.question_ids.filtered( + lambda question: question.question_type in ['simple_choice', 'multiple_choice'] + ) + + for question in scored_questions: + question_answer_correct = question.labels_ids.filtered(lambda answer: answer.is_correct) + for user_answer in user_answers: + user_answer_lines_question = user_answer.user_input_line_ids.filtered(lambda line: line.question_id == question) + user_answer_correct = user_answer_lines_question.filtered(lambda line: line.answer_is_correct and not line.skipped).mapped('value_suggested') + user_answer_incorrect = user_answer_lines_question.filtered(lambda line: not line.answer_is_correct and not line.skipped) + + if user_answer_correct == question_answer_correct: + res[user_answer]['correct'] += 1 + elif user_answer_correct and user_answer_correct < question_answer_correct: + res[user_answer]['partial'] += 1 + if not user_answer_correct and user_answer_incorrect: + res[user_answer]['incorrect'] += 1 + if not user_answer_correct and not user_answer_incorrect: + res[user_answer]['skipped'] += 1 + + return res + # ------------------------------------------------------------ # GAMIFICATION / BADGES # ------------------------------------------------------------ diff --git a/addons/survey/static/src/js/survey_result.js b/addons/survey/static/src/js/survey_result.js index 6e63d473ea4..380a5c893a8 100644 --- a/addons/survey/static/src/js/survey_result.js +++ b/addons/survey/static/src/js/survey_result.js @@ -2,6 +2,7 @@ odoo.define('survey.result', function (require) { 'use strict'; require('web.dom_ready'); +var _t = require('web.core')._t; if(!$('.js_surveyresult').length) { return Promise.reject("DOM doesn't contain '.js_surveyresult'"); @@ -159,6 +160,35 @@ if(!$('.js_surveyresult').length) { return chartConfig; } + //initialize doughnut Chart + function init_doughnut_chart(graph_data, quizz_score){ + var data = graph_data.map(function (point) { + return point.count; + }); + var chartConfig = { + type: 'doughnut', + data: { + labels: graph_data.map(function (point) { + return point.text; + }), + datasets: [{ + label: '', + data: data, + backgroundColor: data.map(function (val, index) { + return D3_COLORS[index % 20]; + }), + }] + }, + options: { + title: { + display: true, + text: _.str.sprintf(_t("Overall Performance %.2f%s"), parseFloat(quizz_score), '%'), + }, + } + }; + return chartConfig; + } + //load chart to svg element chart:initialized chart, response:AJAX response, quistion_id:if of survey question, tick_limit:text length limit function load_chart(chartConfig, containerSelector){ var $container = $(containerSelector).css({position: 'relative'}); @@ -187,6 +217,11 @@ if(!$('.js_surveyresult').length) { chartConfig = init_pie_chart(graph_data); return load_chart(chartConfig, containerSelector); } + else if (graph_type === 'doughnut') { + var quizz_score = $(graph).attr("quizz-score") || 0.0; + chartConfig = init_doughnut_chart(graph_data, quizz_score); + return load_chart(chartConfig, containerSelector); + } }); var $scoringResultsChart = $('#scoring_results_chart'); diff --git a/addons/survey/views/survey_templates.xml b/addons/survey/views/survey_templates.xml index 569da41c1c5..4906e4e6c0e 100644 --- a/addons/survey/views/survey_templates.xml +++ b/addons/survey/views/survey_templates.xml @@ -57,6 +57,15 @@ +

+
+
+ + +
+
+
+