summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorDavid Beguin <dbe@odoo.com>2020-02-28 13:27:17 +0000
committerThibault Delavallée <tde@odoo.com>2020-03-06 10:29:50 +0000
commit2d499c0236927f010f47a4cb5f9908c770b1d0db (patch)
tree33a57709311f51ca2495defa43de15ffbeb020a9 /addons
parent3968bed23947722f92d1c8679b9a5955319a0cbc (diff)
[IMP] survey: integrate conditional questions in session mode
Purpose ======= This commit makes the live session mode work with conditional question. The point is to determine which question to display next. As multiple users are 'voting' for answers of the same questions, the way to determine the next question to display is to keep only the most voted answer. The most voted answer will be the only one to trigger the linked conditional question. E.g.: considering - following survey configuration: - Q1: A1, A2 - Q2: triggered by Q1-A1 - Q3: triggered by Q1-A2 - following user inputs: - U1: A1 selected - U2: A2 selected - U3: A2 selected The next question displayed to ALL users will be Q3. Note: If U4 had selected A1, the next question would have been Q2 as when there is an equality, we select the first one. Link ==== Task ID: 23509 PR #44950
Diffstat (limited to 'addons')
-rw-r--r--addons/survey/controllers/main.py4
-rw-r--r--addons/survey/controllers/survey_session_manage.py27
-rw-r--r--addons/survey/models/survey_survey.py67
-rw-r--r--addons/survey/static/src/js/survey_session_leaderboard.js17
-rw-r--r--addons/survey/static/src/js/survey_session_manage.js25
-rw-r--r--addons/survey/views/survey_templates_user_input_session.xml2
6 files changed, 99 insertions, 43 deletions
diff --git a/addons/survey/controllers/main.py b/addons/survey/controllers/main.py
index dfa0a49687f..3b9b29a9036 100644
--- a/addons/survey/controllers/main.py
+++ b/addons/survey/controllers/main.py
@@ -460,7 +460,7 @@ class Survey(http.Controller):
errors = {}
# Prepare answers / comment by question, validate and save answers
- inactive_questions = answer_sudo._get_inactive_conditional_questions()
+ inactive_questions = request.env['survey.question'] if answer_sudo.is_session_answer else answer_sudo._get_inactive_conditional_questions()
for question in questions:
if question in inactive_questions: # if question is inactive, skip validation and save
continue
@@ -472,7 +472,7 @@ class Survey(http.Controller):
if errors and not (answer_sudo.survey_time_limit_reached or answer_sudo.question_time_limit_reached):
return {'error': 'validation', 'fields': errors}
- if not answer_sudo.is_session_answer: # Ignore conditional configurations during survey session.
+ if not answer_sudo.is_session_answer:
answer_sudo._clear_inactive_conditional_answers()
if answer_sudo.survey_time_limit_reached or survey_sudo.questions_layout == 'one_page':
diff --git a/addons/survey/controllers/survey_session_manage.py b/addons/survey/controllers/survey_session_manage.py
index 929ac7e4819..ad7154b8753 100644
--- a/addons/survey/controllers/survey_session_manage.py
+++ b/addons/survey/controllers/survey_session_manage.py
@@ -1,12 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import datetime
import json
+from dateutil.relativedelta import relativedelta
from werkzeug.exceptions import NotFound
from werkzeug.urls import url_join
-from odoo import http
+from odoo import fields, http
from odoo.http import request
@@ -57,10 +59,25 @@ class UserInputSession(http.Controller):
if survey.session_state == 'ready':
survey._session_open()
- survey._session_trigger_next_question()
- template_values = self._prepare_manage_session_values(survey)
- template_values['is_rpc_call'] = True
- return request.env.ref('survey.user_input_session_manage_content').render(template_values)
+ next_question = survey._get_session_next_question()
+
+ # using datetime.datetime because we want the millis portion
+ if next_question:
+ now = datetime.datetime.now()
+ survey.sudo().write({
+ 'session_question_id': next_question.id,
+ 'session_question_start_time': fields.Datetime.now() + relativedelta(seconds=2)
+ })
+ request.env['bus.bus'].sendone(survey.access_token, {
+ 'question_start': now.timestamp(),
+ 'type': 'next_question'
+ })
+
+ template_values = self._prepare_manage_session_values(survey)
+ template_values['is_rpc_call'] = True
+ return request.env.ref('survey.user_input_session_manage_content').render(template_values)
+ else:
+ return False
@http.route('/survey/session/results/<string:survey_token>', type='json', auth='user', website=True)
def survey_session_results(self, survey_token, **kwargs):
diff --git a/addons/survey/models/survey_survey.py b/addons/survey/models/survey_survey.py
index 781e66b6e98..9af1f059d08 100644
--- a/addons/survey/models/survey_survey.py
+++ b/addons/survey/models/survey_survey.py
@@ -1,14 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-import datetime
import json
import random
import uuid
import werkzeug
-from dateutil.relativedelta import relativedelta
-
from odoo import api, exceptions, fields, models, _
from odoo.exceptions import AccessError
from odoo.osv import expression
@@ -566,7 +563,7 @@ class Survey(models.Model):
self.sudo().write({'session_state': 'in_progress'})
self.sudo().flush(['session_state'])
- def _session_trigger_next_question(self):
+ def _get_session_next_question(self):
""" Triggers the next question of the session.
We artificially add 2 seconds to the 'current_question_start_time' to account for server delay.
@@ -585,28 +582,52 @@ class Survey(models.Model):
return
if not self.session_question_id:
- question = self.question_ids[0]
+ next_question = self.question_ids[0]
else:
- questions = list(enumerate(self.question_ids))
- current_question_index = questions.index(
- next(question for question in questions if question[1] == self.session_question_id)
- )
- if current_question_index < len(self.question_ids) - 1:
- question = self.question_ids[current_question_index + 1]
- else:
- question = self.session_question_id
-
- # using datetime.datetime because we want the millis portion
- now = datetime.datetime.now()
- self.sudo().write({
- 'session_question_id': question.id,
- 'session_question_start_time': fields.Datetime.now() + relativedelta(seconds=2)
- })
- self.env['bus.bus'].sendone(self.access_token, {
- 'question_start': now.timestamp(),
- 'type': 'next_question'
+ most_voted_answers = self._get_session_most_voted_answers()
+ next_question = self._get_next_page_or_question(most_voted_answers, self.session_question_id.id)
+
+ return next_question
+
+ def _get_session_most_voted_answers(self):
+ """ In sessions of survey that has conditional questions, as the survey is passed at the same time by
+ many users, we need to extract the most chosen answers, to determine the next questions to display. """
+
+ # get user_inputs from current session
+ current_user_inputs = self.user_input_ids.filtered(lambda input: input.create_date > self.session_start_time)
+ current_user_input_lines = current_user_inputs.mapped('user_input_line_ids').filtered(lambda answer: answer.suggested_answer_id)
+
+ # count the number of vote per answer
+ votes_by_answer = dict.fromkeys(current_user_input_lines.mapped('suggested_answer_id'), 0)
+ for answer in current_user_input_lines:
+ votes_by_answer[answer.suggested_answer_id] += 1
+
+ # extract most voted answer for each question
+ most_voted_answer_by_questions = dict.fromkeys(current_user_input_lines.mapped('question_id'))
+ for question in most_voted_answer_by_questions.keys():
+ for answer in votes_by_answer.keys():
+ if answer.question_id != question:
+ continue
+ most_voted_answer = most_voted_answer_by_questions[question]
+ if not most_voted_answer or votes_by_answer[most_voted_answer] < votes_by_answer[answer]:
+ most_voted_answer_by_questions[question] = answer
+
+ # return a fake 'audiance' user_input
+ fake_user_input = self.env['survey.user_input'].new({
+ 'survey_id': self.id,
})
+ fake_user_input_lines = self.env['survey.user_input.line']
+ for question, answer in most_voted_answer_by_questions.items():
+ fake_user_input_lines |= self.env['survey.user_input.line'].new({
+ 'question_id': question.id,
+ 'suggested_answer_id': answer.id,
+ 'survey_id': self.id,
+ 'user_input_id': fake_user_input.id
+ })
+
+ return fake_user_input
+
def _prepare_leaderboard_values(self):
"""" The leaderboard is descending and takes the total of the attendee points up to the current question. """
self.ensure_one()
diff --git a/addons/survey/static/src/js/survey_session_leaderboard.js b/addons/survey/static/src/js/survey_session_leaderboard.js
index 0126a00f1c7..ae050ddedcf 100644
--- a/addons/survey/static/src/js/survey_session_leaderboard.js
+++ b/addons/survey/static/src/js/survey_session_leaderboard.js
@@ -24,14 +24,21 @@ publicWidget.registry.SurveySessionLeaderboard = publicWidget.Widget.extend({
*
* The width of the progress bars is set after the rendering to enable a width css animation.
*/
- showLeaderboard: function () {
+ showLeaderboard: function (fadeOut) {
var self = this;
var resolveFadeOut;
- var fadeOutPromise = new Promise(function (resolve, reject) { resolveFadeOut = resolve; });
- self.$sessionResults.fadeOut(400, function () {
- resolveFadeOut();
- });
+ var fadeOutPromise;
+ if (fadeOut) {
+ fadeOutPromise = new Promise(function (resolve, reject) { resolveFadeOut = resolve; });
+ self.$sessionResults.fadeOut(400, function () {
+ resolveFadeOut();
+ });
+ } else {
+ fadeOutPromise = Promise.resolve();
+ self.$sessionResults.hide();
+ self.$('.o_survey_session_leaderboard_container').empty();
+ }
var leaderboardPromise = this._rpc({
route: _.str.sprintf('/survey/session/leaderboard/%s', this.surveyAccessToken)
diff --git a/addons/survey/static/src/js/survey_session_manage.js b/addons/survey/static/src/js/survey_session_manage.js
index 0d473cdc9b9..aec3c1f76bf 100644
--- a/addons/survey/static/src/js/survey_session_manage.js
+++ b/addons/survey/static/src/js/survey_session_manage.js
@@ -161,7 +161,7 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend({
this.$('.o_survey_session_leaderboard_title').text(_('Final Leaderboard'));
this.$('.o_survey_session_navigation_next').addClass('d-none');
}
- this.leaderBoard.showLeaderboard();
+ this.leaderBoard.showLeaderboard(true);
} else {
if (!this.isLastQuestion) {
this._nextQuestion();
@@ -317,12 +317,23 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend({
}
Promise.all([fadeOutPromise, nextQuestionPromise]).then(function (results) {
- var $renderedTemplate = $(results[1]);
- self.$el.replaceWith($renderedTemplate);
- self.attachTo($renderedTemplate);
- self.$el.fadeIn(1000, function () {
- self._startTimer();
- });
+ if (results[1]) {
+ var $renderedTemplate = $(results[1]);
+ self.$el.replaceWith($renderedTemplate);
+ self.attachTo($renderedTemplate);
+ self.$el.fadeIn(1000, function () {
+ self._startTimer();
+ });
+ } else {
+ // Display last screen
+ self.isLastQuestion = true;
+ self._setupLeaderboard().then(function () {
+ self.$('.o_survey_session_leaderboard_title').text(_('Final Leaderboard'));
+ self.$('.o_survey_session_navigation_next').addClass('d-none');
+ self.$('.o_survey_leaderboard_buttons').removeClass('d-none');
+ self.leaderBoard.showLeaderboard(false);
+ });
+ }
});
},
diff --git a/addons/survey/views/survey_templates_user_input_session.xml b/addons/survey/views/survey_templates_user_input_session.xml
index 995673a0966..6f3ff38d282 100644
--- a/addons/survey/views/survey_templates_user_input_session.xml
+++ b/addons/survey/views/survey_templates_user_input_session.xml
@@ -130,7 +130,7 @@
<div class="o_survey_session_leaderboard w-100 flex-column flex-grow-1" style="display: none;">
<div class="d-flex">
<h1 class="o_survey_session_leaderboard_title flex-grow-1">Leaderboard</h1>
- <div t-if="is_last_question" class="o_survey_leaderboard_buttons font-weight-bold">
+ <div t-att-class="'o_survey_leaderboard_buttons font-weight-bold %s' % 'd-none' if not is_last_question else ''">
<a href="#" role="button" class="o_survey_session_close btn btn-primary mr-4"><i class="fa fa-close"/> Close</a>
<a href="#" role="button" class="o_survey_session_close btn btn-primary" t-att-data-show-results="True"><i class="fa fa-bar-chart"/> Results</a>
</div>