[IMP] survey: allow host to go to previous question in session

PURPOSE
=======
For now, the host can't go back to the previous screen on a live session.
Why would he want to go back? Misclick, want to display again the result, other

SPECIFICATION
=============
On a live session, let the host go back to the previous screen.
Do not "restart" the questions but display the graph with the results.
This back capability is independant of the "back button" survey option.
Even if the back button is disabled on the survey configuration, the host must
still be able to go back during a session.

Task ID: 2351759

closes odoo/odoo#59659

Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
David Beguin
2021-04-09 11:47:20 +00:00
parent 832125bd8c
commit a888b8ee4a
5 changed files with 90 additions and 20 deletions
@@ -72,7 +72,7 @@ class UserInputSession(http.Controller):
return request.render('survey.user_input_session_manage', template_values)
@http.route('/survey/session/next_question/<string:survey_token>', type='json', auth='user', website=True)
def survey_session_next_question(self, survey_token, **kwargs):
def survey_session_next_question(self, survey_token, go_back=False, **kwargs):
""" This route is called when the host goes to the next question of the session.
It's not a regular 'request.render' route because we handle the transition between
@@ -99,7 +99,7 @@ class UserInputSession(http.Controller):
if survey.session_state == 'ready':
survey._session_open()
next_question = survey._get_session_next_question()
next_question = survey._get_session_next_question(go_back)
# using datetime.datetime because we want the millis portion
if next_question:
@@ -196,14 +196,16 @@ class UserInputSession(http.Controller):
return {"error": "survey_wrong"}
def _prepare_manage_session_values(self, survey):
is_last_question = False
is_first_question, is_last_question = False, False
if survey.question_ids:
most_voted_answers = survey._get_session_most_voted_answers()
is_first_question = survey._is_first_page_or_question(survey.session_question_id)
is_last_question = survey._is_last_page_or_question(most_voted_answers, survey.session_question_id)
values = {
'survey': survey,
'is_last_question': is_last_question,
'is_first_question': is_first_question,
}
values.update(self._prepare_question_results_values(survey, request.env['survey.user_input.line']))
+13 -2
View File
@@ -573,6 +573,17 @@ class Survey(models.Model):
return section
return Question
def _is_first_page_or_question(self, page_or_question):
""" This method checks if the given question or page is the first one to display.
If the first section of the survey as a description, this will be the first screen to display.
else, the first question will be the first screen to be displayed.
This methods is used for survey session management where the host should not be able to go back on the
first page or question."""
first_section_has_description = self.page_ids and not is_html_empty(self.page_ids[0].description)
is_first_page_or_question = (first_section_has_description and page_or_question == self.page_ids[0]) or \
(not first_section_has_description and page_or_question == self.question_ids[0])
return is_first_page_or_question
def _is_last_page_or_question(self, user_input, page_or_question):
""" This method checks if the given question or page is the last one.
This includes conditional questions configuration. If the given question is normally not the last one but
@@ -681,7 +692,7 @@ class Survey(models.Model):
self.sudo().write({'session_state': 'in_progress'})
self.sudo().flush(['session_state'])
def _get_session_next_question(self):
def _get_session_next_question(self, go_back):
self.ensure_one()
if not self.question_ids or not self.env.user.has_group('survey.group_survey_user'):
@@ -690,7 +701,7 @@ class Survey(models.Model):
most_voted_answers = self._get_session_most_voted_answers()
return self._get_next_page_or_question(
most_voted_answers,
self.session_question_id.id if self.session_question_id else 0)
self.session_question_id.id if self.session_question_id else 0, go_back=go_back)
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
@@ -32,6 +32,7 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend({
self.surveyId = self.$el.data('surveyId');
self.surveyAccessToken = self.$el.data('surveyAccessToken');
self.isStartScreen = self.$el.data('isStartScreen');
self.isFirstQuestion = self.$el.data('isFirstQuestion');
self.isLastQuestion = self.$el.data('isLastQuestion');
// scoring props
self.isScoredQuestion = self.$el.data('isScoredQuestion');
@@ -148,13 +149,11 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend({
if (screenToDisplay === 'userInputs') {
this._setShowInputs(true);
this.$('.o_survey_session_navigation_previous').removeClass('d-none');
} else if (screenToDisplay === 'results') {
this._setShowAnswers(true);
// when showing results, stop refreshing answers
clearInterval(this.resultsRefreshInterval);
delete this.resultsRefreshInterval;
this.$('.o_survey_session_navigation_previous').removeClass('d-none');
} else if (['leaderboard', 'leaderboardFinal'].includes(screenToDisplay)
&& !['leaderboard', 'leaderboardFinal'].includes(this.currentScreen)) {
if (this.isLastQuestion) {
@@ -185,7 +184,6 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend({
if (screenToDisplay === 'question') {
this._setShowInputs(false);
this.$('.o_survey_session_navigation_previous').addClass('d-none');
} else if (screenToDisplay === 'userInputs') {
this._setShowAnswers(false);
// resume refreshing answers if necessary
@@ -193,10 +191,17 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend({
this.resultsRefreshInterval = setInterval(this._refreshResults.bind(this), 2000);
}
} else if (screenToDisplay === 'results') {
this.leaderBoard.hideLeaderboard();
if (this.leaderBoard) {
this.leaderBoard.hideLeaderboard();
}
// when showing results, stop refreshing answers
clearInterval(this.resultsRefreshInterval);
delete this.resultsRefreshInterval;
} else if (screenToDisplay === 'previousQuestion') {
if (this.isFirstQuestion) {
return; // nothing to go back to, we're on the first question
}
this._nextQuestion(true);
}
this.currentScreen = screenToDisplay;
@@ -272,14 +277,16 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend({
_getPreviousScreen: function () {
if (this.currentScreen === 'userInputs' && this.isScoredQuestion) {
return 'question';
} else if (this.currentScreen === 'results' ||
(this.currentScreen === 'leaderboard' && !this.isScoredQuestion)) {
} else if ((this.currentScreen === 'results' && this.isScoredQuestion) ||
(this.currentScreen === 'leaderboard' && !this.isScoredQuestion) ||
(this.currentScreen === 'leaderboardFinal' && this.isScoredQuestion)) {
return 'userInputs';
} else if (this.currentScreen === 'leaderboard' && this.isScoredQuestion) {
} else if ((this.currentScreen === 'leaderboard' && this.isScoredQuestion) ||
(this.currentScreen === 'leaderboardFinal' && !this.isScoredQuestion)){
return 'results';
}
return this.currentScreen;
return 'previousQuestion';
},
/**
@@ -294,7 +301,7 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend({
* @param {MouseEvent} ev
* @private
*/
_nextQuestion: function () {
_nextQuestion: function (goBack) {
var self = this;
this.isStartScreen = false;
@@ -309,7 +316,10 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend({
});
var nextQuestionPromise = this._rpc({
route: _.str.sprintf('/survey/session/next_question/%s', self.surveyAccessToken)
route: _.str.sprintf('/survey/session/next_question/%s', self.surveyAccessToken),
params: {
'go_back': goBack,
}
});
// avoid refreshing results while transitioning
@@ -318,14 +328,28 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend({
delete this.resultsRefreshInterval;
}
Promise.all([fadeOutPromise, nextQuestionPromise]).then(function (results) {
Promise.all([fadeOutPromise, nextQuestionPromise]).then(async function (results) {
if (results[1]) {
var $renderedTemplate = $(results[1]);
self.$el.replaceWith($renderedTemplate);
self.attachTo($renderedTemplate);
self.$el.fadeIn(self.fadeInOutTime, function () {
// Ensure new question is fully loaded before force loading previous question screen.
await self.attachTo($renderedTemplate);
if (goBack) {
// As we arrive on "question" screen, simulate going to the results screen or leaderboard.
self._setShowInputs(true);
self._setShowAnswers(true);
if (self.sessionShowLeaderboard && self.isScoredQuestion) {
self.currentScreen = 'leaderboard';
self.leaderBoard.showLeaderboard(false, self.isScoredQuestion);
} else {
self.currentScreen = 'results';
self._refreshResults();
}
} else {
self._startTimer();
});
}
self.$el.fadeIn(self.fadeInOutTime);
} else if (self.sessionShowLeaderboard) {
// Display last screen if leaderboard activated
self.isLastQuestion = true;
@@ -546,6 +570,8 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend({
this.currentScreen = 'question';
}
this.$('.o_survey_session_navigation_previous').toggleClass('d-none', !!this.isFirstQuestion);
this._setShowInputs(this.currentScreen === 'userInputs');
},
@@ -129,6 +129,36 @@ tour.register('test_survey_session_manage_tour', {
}, {
trigger: '.o_survey_session_text_answer_container:contains("11/11/2011")',
run: function () {} // check attendee 2 answer is displayed
}, {
trigger: 'h1',
run: previousScreen
}, {
trigger: 'h1:contains("Text Question")',
run: function () {} // check text question is displayed
}, {
trigger: '.o_survey_session_progress_small:contains("3 / 3")',
run: function () {} // check we have 3 answers
}, {
trigger: '.o_survey_session_text_answer_container:contains("Attendee 1 is the best")',
run: function () {} // check attendee 1 answer is displayed
}, {
trigger: '.o_survey_session_text_answer_container:contains("Attendee 2 rulez")',
run: function () {} // check attendee 2 answer is displayed
}, {
trigger: '.o_survey_session_text_answer_container:contains("Attendee 3 will crush you")',
run: function () {} // check attendee 3 answer is displayed
}, {
trigger: 'h1',
run: nextScreen
}, {
trigger: '.o_survey_session_progress_small:contains("2 / 3")',
run: function () {} // check we have 2 answers
}, {
trigger: '.o_survey_session_text_answer_container:contains("10/10/2010")',
run: function () {} // check attendee 1 answer is displayed
}, {
trigger: '.o_survey_session_text_answer_container:contains("11/11/2011")',
run: function () {} // check attendee 2 answer is displayed
}, {
trigger: 'h1',
run: nextScreen
@@ -72,6 +72,7 @@
t-att-data-question-type="question.question_type"
t-att-data-has-correct-answers="any(answer.is_correct for answer in question.suggested_answer_ids)"
t-att-data-answers-validity="answers_validity"
t-att-data-is-first-question="is_first_question"
t-att-data-is-last-question="is_last_question"
t-att-data-current-screen="'question' if is_scored_question else 'userInputs'"
t-att-data-show-bar-chart="show_bar_chart"
@@ -100,7 +101,7 @@
<a role="button"
class="font-weight-bold fa fa-chevron-right o_survey_session_navigation o_survey_session_navigation_next p-3" />
<a role="button"
class="font-weight-bold fa fa-chevron-left o_survey_session_navigation o_survey_session_navigation_previous p-3 d-none" />
class="font-weight-bold fa fa-chevron-left o_survey_session_navigation o_survey_session_navigation_previous p-3" />
<div class="o_survey_session_results flex-column flex-grow-1">
<div class="row">
<div class="col-lg-12"><h1 t-esc="question.title"></h1></div>