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" />