From 2b50869347f32f49e8a428330f0830508773af17 Mon Sep 17 00:00:00 2001 From: Doug Stull <dstull@gitlab.com> Date: Tue, 3 Sep 2019 08:08:58 +0000 Subject: [PATCH] Add feedback verbiage to guided tour exit routine - We want to be able to collect user feedback data from the tour upon exit. This will enable us to better analyze user experiences from the on-boarding process and adjust our process as necessary. - Fix typo on welcome page - we need to track feedback as a state in order to properly transition from exit tour to feedback to actually exiting --- .../javascripts/onboarding/constants.js | 27 ++++++++-- .../onboarding_helper/components/app.vue | 49 ++++++++++++++++--- .../components/help_content_popover.vue | 24 ++++++++- .../components/onboarding_helper.vue | 6 +-- .../onboarding/onboarding_helper/index.js | 3 +- .../onboarding_helper/store/actions.js | 4 ++ .../onboarding_helper/store/mutation_types.js | 1 + .../onboarding_helper/store/mutations.js | 3 ++ .../onboarding_helper/store/state.js | 1 + .../components/welcome_page.vue | 2 +- .../2-add-user-feedback-to-on-boarding.yml | 5 ++ .../onboarding_helper/components/app_spec.js | 32 +++++++++++- .../components/onboarding_helper_spec.js | 2 +- .../onboarding_helper/store/actions_spec.js | 16 ++++++ .../onboarding_helper/store/mutations_spec.js | 9 ++++ .../onboarding_welcome/welcome_page_spec.js | 2 +- locale/gitlab.pot | 16 +++++- 17 files changed, 181 insertions(+), 21 deletions(-) create mode 100644 ee/changelogs/unreleased/2-add-user-feedback-to-on-boarding.yml diff --git a/ee/app/assets/javascripts/onboarding/constants.js b/ee/app/assets/javascripts/onboarding/constants.js index 7928fd823d1..387e5f9aee3 100644 --- a/ee/app/assets/javascripts/onboarding/constants.js +++ b/ee/app/assets/javascripts/onboarding/constants.js @@ -1,4 +1,5 @@ import { s__, sprintf } from '~/locale'; +import { glEmojiTag } from '~/emoji'; export const ONBOARDING_DISMISSED_COOKIE_NAME = 'onboarding_dismissed'; @@ -31,16 +32,36 @@ export const LABEL_SEARCH_QUERY = `scope=all&state=opened&label_name[]=${encodeU ACCEPTING_MR_LABEL_TEXT, )}`; -export const EXIT_TOUR_CONTENT = { +export const FEEDBACK_CONTENT = { text: sprintf( s__( - 'UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right.', + "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}", ), { emphasisStart: '<strong>', emphasisEnd: '</strong>', + lineBreak: '<br/>', + clapHands: glEmojiTag('clap'), }, false, ), - buttons: [{ text: s__('UserOnboardingTour|Got it'), btnClass: 'btn-primary', exitTour: true }], + feedbackButtons: true, + feedbackSize: 5, +}; + +export const EXIT_TOUR_CONTENT = { + text: sprintf( + s__('UserOnboardingTour|Thanks for the feedback! %{thumbsUp}'), + { + thumbsUp: glEmojiTag('thumbsup'), + }, + false, + ), + buttons: [ + { + text: s__("UserOnboardingTour|Close 'Learn GitLab'"), + btnClass: 'btn-primary', + exitTour: true, + }, + ], }; diff --git a/ee/app/assets/javascripts/onboarding/onboarding_helper/components/app.vue b/ee/app/assets/javascripts/onboarding/onboarding_helper/components/app.vue index d2d409ab49c..0c151a38231 100644 --- a/ee/app/assets/javascripts/onboarding/onboarding_helper/components/app.vue +++ b/ee/app/assets/javascripts/onboarding/onboarding_helper/components/app.vue @@ -18,6 +18,10 @@ export default { type: Array, required: true, }, + feedbackContent: { + type: Object, + required: true, + }, exitTourContent: { type: Object, required: true, @@ -41,6 +45,7 @@ export default { 'tourData', 'lastStepIndex', 'helpContentIndex', + 'tourFeedback', 'exitTour', 'dismissed', ]), @@ -53,11 +58,11 @@ export default { 'actionPopover', ]), helpContentData() { - if (this.showStepContent) { - return this.exitTour ? this.exitTourContent : this.helpContent; - } + if (!this.showStepContent) return null; + if (this.exitTour) return this.exitTourContent; + if (this.tourFeedback) return this.feedbackContent; - return null; + return this.helpContent; }, completedSteps() { return Math.max(this.lastStepIndex, 0); @@ -73,6 +78,7 @@ export default { 'setHelpContentIndex', 'switchTourPart', 'setExitTour', + 'setTourFeedback', 'setDismissed', ]), init() { @@ -111,6 +117,7 @@ export default { }, handleRestartStep() { this.showExitTourContent(false); + this.handleFeedbackTourContent(false); Tracking.event(TRACKING_CATEGORY, 'click_link', { label: this.getTrackingLabel(), property: 'restart_this_step', @@ -131,19 +138,41 @@ export default { } }, handleClickPopoverButton(button) { - const { showExitTourContent, exitTour, redirectPath, nextPart, dismissPopover } = button; + const { + showExitTourContent, + exitTour, + redirectPath, + nextPart, + dismissPopover, + feedbackResult, + showFeedbackTourContent, + } = button; const helpContentItems = this.stepContent ? this.stepContent.getHelpContent({ projectName: this.projectName }) : null; - const showNextContentItem = helpContentItems && helpContentItems.length > 1 && this.helpContentIndex < helpContentItems.length - 1; + // track feedback + if (feedbackResult) { + Tracking.event(TRACKING_CATEGORY, 'click_link', { + label: 'feedback', + property: 'feedback_result', + value: feedbackResult, + }); + } + + // display feedback content after user hits the exit button + if (showFeedbackTourContent) { + this.handleFeedbackTourContent(true); + return; + } + // display exit tour content if (showExitTourContent) { - this.showExitTourContent(true); + this.handleShowExitTourContent(true); return; } @@ -191,6 +220,11 @@ export default { }); this.showExitTourContent(showExitTour); }, + handleFeedbackTourContent(showTourFeedback) { + this.dismissPopover = false; + this.showStepContent = true; + this.setTourFeedback(showTourFeedback); + }, showExitTourContent(showExitTour) { this.dismissPopover = false; this.showStepContent = true; @@ -230,6 +264,7 @@ export default { @clickPopoverButton="handleClickPopoverButton" @restartStep="handleRestartStep" @skipStep="handleSkipStep" + @showFeedbackContent="handleFeedbackTourContent" @showExitTourContent="handleShowExitTourContent" @exitTour="handleExitTour" /> diff --git a/ee/app/assets/javascripts/onboarding/onboarding_helper/components/help_content_popover.vue b/ee/app/assets/javascripts/onboarding/onboarding_helper/components/help_content_popover.vue index fd7062f77fb..d69e6f6bb74 100644 --- a/ee/app/assets/javascripts/onboarding/onboarding_helper/components/help_content_popover.vue +++ b/ee/app/assets/javascripts/onboarding/onboarding_helper/components/help_content_popover.vue @@ -1,11 +1,12 @@ <script> -import { GlPopover, GlButton } from '@gitlab/ui'; +import { GlPopover, GlButton, GlButtonGroup } from '@gitlab/ui'; export default { name: 'HelpContentPopover', components: { GlPopover, GlButton, + GlButtonGroup, }, props: { target: { @@ -68,6 +69,27 @@ export default { </span> </template> </template> + <template v-if="helpContent.feedbackButtons"> + <gl-button-group> + <gl-button + v-for="feedbackValue in helpContent.feedbackSize" + :key="feedbackValue" + @click=" + callButtonAction({ + feedbackResult: feedbackValue, + showExitTourContent: true, + exitTour: true, + }) + " + > + {{ feedbackValue }} + </gl-button> + </gl-button-group> + <div class="pt-1"> + <small>{{ __('Not helpful') }}</small> + <small class="ml-4">{{ __('Very helpful') }}</small> + </div> + </template> </div> </gl-popover> </template> diff --git a/ee/app/assets/javascripts/onboarding/onboarding_helper/components/onboarding_helper.vue b/ee/app/assets/javascripts/onboarding/onboarding_helper/components/onboarding_helper.vue index 1c28ed97e85..dc4e73604b4 100644 --- a/ee/app/assets/javascripts/onboarding/onboarding_helper/components/onboarding_helper.vue +++ b/ee/app/assets/javascripts/onboarding/onboarding_helper/components/onboarding_helper.vue @@ -132,8 +132,8 @@ export default { restartStep() { this.$emit('restartStep'); }, - showExitTourContent() { - this.$emit('showExitTourContent', true); + showFeedbackContent() { + this.$emit('showFeedbackContent', true); }, callButtonAction(button) { this.$emit('clickPopoverButton', button); @@ -208,7 +208,7 @@ export default { </gl-link> </li> <li> - <gl-link class="qa-exit-tour-link d-inline-flex" @click="showExitTourContent"> + <gl-link class="qa-exit-tour-link d-inline-flex" @click="showFeedbackContent"> <icon name="leave" class="mr-1" /> <span>{{ s__("UserOnboardingTour|Exit 'Learn GitLab'") }}</span> </gl-link> diff --git a/ee/app/assets/javascripts/onboarding/onboarding_helper/index.js b/ee/app/assets/javascripts/onboarding/onboarding_helper/index.js index 77753f21985..e93c678ce07 100644 --- a/ee/app/assets/javascripts/onboarding/onboarding_helper/index.js +++ b/ee/app/assets/javascripts/onboarding/onboarding_helper/index.js @@ -3,7 +3,7 @@ import { mapActions } from 'vuex'; import OnboardingApp from './components/app.vue'; import createStore from './store'; import onboardingUtils from './../utils'; -import { TOUR_TITLES, EXIT_TOUR_CONTENT } from './../constants'; +import { TOUR_TITLES, FEEDBACK_CONTENT, EXIT_TOUR_CONTENT } from './../constants'; import TOUR_PARTS from './../tour_parts'; export default function() { @@ -51,6 +51,7 @@ export default function() { props: { tourTitles: TOUR_TITLES, exitTourContent: EXIT_TOUR_CONTENT, + feedbackContent: FEEDBACK_CONTENT, goldenTanukiSvgPath, }, }); diff --git a/ee/app/assets/javascripts/onboarding/onboarding_helper/store/actions.js b/ee/app/assets/javascripts/onboarding/onboarding_helper/store/actions.js index b49843bbe6b..089f585565e 100644 --- a/ee/app/assets/javascripts/onboarding/onboarding_helper/store/actions.js +++ b/ee/app/assets/javascripts/onboarding/onboarding_helper/store/actions.js @@ -29,6 +29,10 @@ export const switchTourPart = ({ dispatch }, tourKey) => { dispatch('setHelpContentIndex', 0); }; +export const setTourFeedback = ({ commit }, tourFeedback) => { + commit(types.SET_FEEDBACK, tourFeedback); +}; + export const setExitTour = ({ commit }, exitTour) => { commit(types.SET_EXIT_TOUR, exitTour); }; diff --git a/ee/app/assets/javascripts/onboarding/onboarding_helper/store/mutation_types.js b/ee/app/assets/javascripts/onboarding/onboarding_helper/store/mutation_types.js index 8a6d0a713a3..e9d28c63627 100644 --- a/ee/app/assets/javascripts/onboarding/onboarding_helper/store/mutation_types.js +++ b/ee/app/assets/javascripts/onboarding/onboarding_helper/store/mutation_types.js @@ -2,5 +2,6 @@ export const SET_INITIAL_DATA = 'SET_INITIAL_DATA'; export const SET_TOUR_KEY = 'SET_TOUR_KEY'; export const SET_LAST_STEP_INDEX = 'SET_LAST_STEP_INDEX'; export const SET_HELP_CONTENT_INDEX = 'SET_HELP_CONTENT_INDEX'; +export const SET_FEEDBACK = 'SET_FEEDBACK'; export const SET_EXIT_TOUR = 'SET_EXIT_TOUR'; export const SET_DISMISSED = 'SET_DISMISSED'; diff --git a/ee/app/assets/javascripts/onboarding/onboarding_helper/store/mutations.js b/ee/app/assets/javascripts/onboarding/onboarding_helper/store/mutations.js index 4c8e87c650b..fda39961681 100644 --- a/ee/app/assets/javascripts/onboarding/onboarding_helper/store/mutations.js +++ b/ee/app/assets/javascripts/onboarding/onboarding_helper/store/mutations.js @@ -13,6 +13,9 @@ export default { [types.SET_HELP_CONTENT_INDEX](state, payload) { state.helpContentIndex = payload; }, + [types.SET_FEEDBACK](state, payload) { + state.tourFeedback = payload; + }, [types.SET_EXIT_TOUR](state, payload) { state.exitTour = payload; }, diff --git a/ee/app/assets/javascripts/onboarding/onboarding_helper/store/state.js b/ee/app/assets/javascripts/onboarding/onboarding_helper/store/state.js index f3d8f30450f..40de3f7493d 100644 --- a/ee/app/assets/javascripts/onboarding/onboarding_helper/store/state.js +++ b/ee/app/assets/javascripts/onboarding/onboarding_helper/store/state.js @@ -11,4 +11,5 @@ export default () => ({ dismissed: false, createdProjectPath: '', exitTour: false, + tourFeedback: false, }); diff --git a/ee/app/assets/javascripts/onboarding/onboarding_welcome/components/welcome_page.vue b/ee/app/assets/javascripts/onboarding/onboarding_welcome/components/welcome_page.vue index 29244fbf435..f9f23c8c2b4 100644 --- a/ee/app/assets/javascripts/onboarding/onboarding_welcome/components/welcome_page.vue +++ b/ee/app/assets/javascripts/onboarding/onboarding_welcome/components/welcome_page.vue @@ -90,7 +90,7 @@ export default { <p class="mt-4"> {{ __( - 'We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You willl be guided by two types of helpers, best recognized by their color.', + 'We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color.', ) }} </p> diff --git a/ee/changelogs/unreleased/2-add-user-feedback-to-on-boarding.yml b/ee/changelogs/unreleased/2-add-user-feedback-to-on-boarding.yml new file mode 100644 index 00000000000..3baa5ecb088 --- /dev/null +++ b/ee/changelogs/unreleased/2-add-user-feedback-to-on-boarding.yml @@ -0,0 +1,5 @@ +--- +title: Add user feedback to exit routine of onboarding tour +merge_request: +author: +type: changed diff --git a/ee/spec/javascripts/onboarding/onboarding_helper/components/app_spec.js b/ee/spec/javascripts/onboarding/onboarding_helper/components/app_spec.js index e63d024fca4..42d89d8e815 100644 --- a/ee/spec/javascripts/onboarding/onboarding_helper/components/app_spec.js +++ b/ee/spec/javascripts/onboarding/onboarding_helper/components/app_spec.js @@ -20,6 +20,11 @@ describe('User onboarding helper app', () => { }; const tourTitles = [{ id: 1, title: 'First tour' }, { id: 2, title: 'Second tour' }]; const exitTourContent = { + text: 'feedback content', + feedbackButtons: true, + feedbackSize: 5, + }; + const feedbackContent = { text: 'exit tour content', buttons: [{ text: 'OK', btnClass: 'btn-primary' }], }; @@ -27,6 +32,7 @@ describe('User onboarding helper app', () => { const defaultProps = { tourTitles, exitTourContent, + feedbackContent, goldenTanukiSvgPath: 'illustrations/golden_tanuki.svg', }; @@ -70,6 +76,12 @@ describe('User onboarding helper app', () => { expect(vm.helpContentData).toEqual(exitTourContent); }); + + it('returns an object containing tour feedback content if tourFeedback is true', () => { + store.dispatch('setTourFeedback', true); + + expect(vm.helpContentData).toEqual(feedbackContent); + }); }); describe('completedSteps', () => { @@ -164,12 +176,14 @@ describe('User onboarding helper app', () => { }); describe('handleRestartStep', () => { - it('calls the "showExitTourContent" method', () => { + it('calls the "showExitTourContent" and "handleFeedbackTourContent" methods', () => { spyOn(vm, 'showExitTourContent'); + spyOn(vm, 'handleFeedbackTourContent'); vm.handleRestartStep(); expect(vm.showExitTourContent).toHaveBeenCalledWith(false); + expect(vm.handleFeedbackTourContent).toHaveBeenCalledWith(false); }); it('emits the "onboardingHelper.hideActionPopover" event', () => { @@ -308,6 +322,22 @@ describe('User onboarding helper app', () => { }); }); + describe('handleFeedbackTourContent', () => { + it('sets the "dismissPopover" prop to false', () => { + vm.handleFeedbackTourContent(true); + + expect(vm.dismissPopover).toBeFalsy(); + }); + + it('calls the "setTourFeedback" method', () => { + spyOn(vm.$store, 'dispatch'); + + vm.handleFeedbackTourContent(true); + + expect(vm.$store.dispatch).toHaveBeenCalledWith('setTourFeedback', true); + }); + }); + describe('handleExitTour', () => { it('calls the "hideActionPopover" method', () => { spyOn(vm, 'hideActionPopover'); diff --git a/ee/spec/javascripts/onboarding/onboarding_helper/components/onboarding_helper_spec.js b/ee/spec/javascripts/onboarding/onboarding_helper/components/onboarding_helper_spec.js index 7d3dfeb2dd4..21ab6cbbf40 100644 --- a/ee/spec/javascripts/onboarding/onboarding_helper/components/onboarding_helper_spec.js +++ b/ee/spec/javascripts/onboarding/onboarding_helper/components/onboarding_helper_spec.js @@ -226,7 +226,7 @@ describe('User onboarding tour parts list', () => { it('emits the "showExitTourContent" event when the "Exit Learn GitLab" link is clicked', () => { wrapper.find('.qa-exit-tour-link').vm.$emit('click'); - expect(wrapper.emitted('showExitTourContent')).toBeTruthy(); + expect(wrapper.emitted('showFeedbackContent')).toBeTruthy(); }); }); diff --git a/ee/spec/javascripts/onboarding/onboarding_helper/store/actions_spec.js b/ee/spec/javascripts/onboarding/onboarding_helper/store/actions_spec.js index 1e62b231391..02846152826 100644 --- a/ee/spec/javascripts/onboarding/onboarding_helper/store/actions_spec.js +++ b/ee/spec/javascripts/onboarding/onboarding_helper/store/actions_spec.js @@ -8,6 +8,7 @@ import { setLastStepIndex, setHelpContentIndex, switchTourPart, + setTourFeedback, setExitTour, setDismissed, } from 'ee/onboarding/onboarding_helper/store/actions'; @@ -140,6 +141,21 @@ describe('User onboarding helper store actions', () => { }); }); + describe('setTourFeedback', () => { + it(`commits ${types.SET_FEEDBACK} mutation`, done => { + const tourFeedback = true; + + testAction( + setTourFeedback, + tourFeedback, + state, + [{ type: types.SET_FEEDBACK, payload: tourFeedback }], + [], + done, + ); + }); + }); + describe('setDismissed', () => { it(`commits ${types.SET_DISMISSED} mutation`, done => { const dismissed = true; diff --git a/ee/spec/javascripts/onboarding/onboarding_helper/store/mutations_spec.js b/ee/spec/javascripts/onboarding/onboarding_helper/store/mutations_spec.js index 3d8aca667e9..50a25418a1e 100644 --- a/ee/spec/javascripts/onboarding/onboarding_helper/store/mutations_spec.js +++ b/ee/spec/javascripts/onboarding/onboarding_helper/store/mutations_spec.js @@ -23,6 +23,7 @@ describe('User onboarding helper store mutations', () => { dismissed: false, createdProjectPath: '', exitTour: false, + tourFeedback: false, }; mutations[types.SET_INITIAL_DATA](state, initialData); @@ -66,6 +67,14 @@ describe('User onboarding helper store mutations', () => { }); }); + describe('SET_FEEDBACK', () => { + it('sets the tourFeedback property to true', () => { + mutations[types.SET_FEEDBACK](state, true); + + expect(state.tourFeedback).toBeTruthy(); + }); + }); + describe('SET_DISMISSED', () => { it('sets the dismissed property to true', () => { mutations[types.SET_DISMISSED](state, true); diff --git a/ee/spec/javascripts/onboarding/onboarding_welcome/welcome_page_spec.js b/ee/spec/javascripts/onboarding/onboarding_welcome/welcome_page_spec.js index 02d6c426d1f..1e2c2da0650 100644 --- a/ee/spec/javascripts/onboarding/onboarding_welcome/welcome_page_spec.js +++ b/ee/spec/javascripts/onboarding/onboarding_welcome/welcome_page_spec.js @@ -125,7 +125,7 @@ describe('User onboarding welcome page', () => { it('displays the welcome text', () => { expect(wrapper.text()).toContain( - 'We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You willl be guided by two types of helpers, best recognized by their color.', + 'We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color.', ); }); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 0988f8169cd..1aec861736a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -10217,6 +10217,9 @@ msgstr "" msgid "Not found." msgstr "" +msgid "Not helpful" +msgstr "" + msgid "Not now" msgstr "" @@ -16682,6 +16685,9 @@ msgstr "" msgid "UserOnboardingTour|Click to open the latest commit to see its details." msgstr "" +msgid "UserOnboardingTour|Close 'Learn GitLab'" +msgstr "" + msgid "UserOnboardingTour|Commits are shown in chronological order and can be filtered by the commit message or by the branch." msgstr "" @@ -16694,6 +16700,9 @@ msgstr "" msgid "UserOnboardingTour|Got it" msgstr "" +msgid "UserOnboardingTour|Great job! %{clapHands} We hope the tour was helpful and that you learned how to use GitLab.%{lineBreak}%{lineBreak}We'd love to get your feedback on this tour.%{lineBreak}%{lineBreak}%{emphasisStart}How helpful would you say this guided tour was?%{emphasisEnd}%{lineBreak}%{lineBreak}" +msgstr "" + msgid "UserOnboardingTour|Guided GitLab Tour" msgstr "" @@ -16757,7 +16766,7 @@ msgstr "" msgid "UserOnboardingTour|Take a look. Here's a nifty menu for quickly creating issues, merge requests, snippets, projects and groups. Click on it and select \"New project\" from the \"GitLab\" section to get started." msgstr "" -msgid "UserOnboardingTour|Thanks for taking the guided tour. Remember, if you want to go through it again, you can start %{emphasisStart}Learn GitLab%{emphasisEnd} in the help menu on the top right." +msgid "UserOnboardingTour|Thanks for the feedback! %{thumbsUp}" msgstr "" msgid "UserOnboardingTour|That's it for issues. Let'st take a look at %{emphasisStart}Merge Requests%{emphasisEnd}." @@ -16964,6 +16973,9 @@ msgstr "" msgid "Version" msgstr "" +msgid "Very helpful" +msgstr "" + msgid "View app" msgstr "" @@ -17171,7 +17183,7 @@ msgstr "" msgid "We couldn't find any results matching" msgstr "" -msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You willl be guided by two types of helpers, best recognized by their color." +msgid "We created a short guided tour that will help you learn the basics of GitLab and how it will help you be better at your job. It should only take a couple of minutes. You will be guided by two types of helpers, best recognized by their color." msgstr "" msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed." -- 2.30.9