From a888b8ee4a78c1902ae75ccde0913a02608ccf8d Mon Sep 17 00:00:00 2001 From: David Beguin Date: Fri, 9 Oct 2020 14:02:09 +0000 Subject: [PATCH] [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) --- .../controllers/survey_session_manage.py | 8 +-- addons/survey/models/survey_survey.py | 15 +++++- .../static/src/js/survey_session_manage.js | 54 ++++++++++++++----- .../tests/tours/survey_tour_session_manage.js | 30 +++++++++++ .../survey_templates_user_input_session.xml | 3 +- 5 files changed, 90 insertions(+), 20 deletions(-) diff --git a/addons/survey/controllers/survey_session_manage.py b/addons/survey/controllers/survey_session_manage.py index c0935e7a7d7..b058662dd5f 100644 --- a/addons/survey/controllers/survey_session_manage.py +++ b/addons/survey/controllers/survey_session_manage.py @@ -72,7 +72,7 @@ class UserInputSession(http.Controller): return request.render('survey.user_input_session_manage', template_values) @http.route('/survey/session/next_question/', 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'])) diff --git a/addons/survey/models/survey_survey.py b/addons/survey/models/survey_survey.py index 5da87e86392..58c5a93accc 100644 --- a/addons/survey/models/survey_survey.py +++ b/addons/survey/models/survey_survey.py @@ -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 diff --git a/addons/survey/static/src/js/survey_session_manage.js b/addons/survey/static/src/js/survey_session_manage.js index 9af7e15d84d..584172f636d 100644 --- a/addons/survey/static/src/js/survey_session_manage.js +++ b/addons/survey/static/src/js/survey_session_manage.js @@ -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'); }, diff --git a/addons/survey/static/tests/tours/survey_tour_session_manage.js b/addons/survey/static/tests/tours/survey_tour_session_manage.js index f92e6803d3b..ba63c9f01e4 100644 --- a/addons/survey/static/tests/tours/survey_tour_session_manage.js +++ b/addons/survey/static/tests/tours/survey_tour_session_manage.js @@ -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 diff --git a/addons/survey/views/survey_templates_user_input_session.xml b/addons/survey/views/survey_templates_user_input_session.xml index 68156c0c68e..0262493acf3 100644 --- a/addons/survey/views/survey_templates_user_input_session.xml +++ b/addons/survey/views/survey_templates_user_input_session.xml @@ -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 @@ + class="font-weight-bold fa fa-chevron-left o_survey_session_navigation o_survey_session_navigation_previous p-3" />