Commit 2b508693 authored by Doug Stull's avatar Doug Stull Committed by Kushal Pandya

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
parent b0b482fb
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,
},
],
};
......@@ -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"
/>
......
<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>
......@@ -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>
......
......@@ -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,
},
});
......
......@@ -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);
};
......
......@@ -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';
......@@ -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;
},
......
......@@ -11,4 +11,5 @@ export default () => ({
dismissed: false,
createdProjectPath: '',
exitTour: false,
tourFeedback: false,
});
......@@ -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>
......
---
title: Add user feedback to exit routine of onboarding tour
merge_request:
author:
type: changed
......@@ -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');
......
......@@ -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();
});
});
......
......@@ -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;
......
......@@ -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);
......
......@@ -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.',
);
});
......
......@@ -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."
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment