Commit 859283c7 authored by Doug Stull's avatar Doug Stull Committed by Mayra Cabrera

Add banner for hints on homepage customization

- help users...
parent ac5e147d
<script>
import { GlBanner } from '@gitlab/ui';
import { s__ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
export default {
components: {
GlBanner,
},
inject: {
svgPath: {
default: '',
},
preferencesBehaviorPath: {
default: '',
},
calloutsPath: {
default: '',
},
calloutsFeatureId: {
default: '',
},
},
i18n: {
title: s__('CustomizeHomepageBanner|Do you want to customize this page?'),
body: s__(
'CustomizeHomepageBanner|This page shows a list of your projects by default but it can be changed to show projects\' activity, groups, your to-do list, assigned issues, assigned merge requests, and more. You can change this under "Homepage content" in your preferences',
),
button_text: s__('CustomizeHomepageBanner|Go to preferences'),
},
data() {
return {
visible: true,
};
},
methods: {
handleClose() {
axios
.post(this.calloutsPath, {
feature_name: this.calloutsFeatureId,
})
.catch(e => {
// eslint-disable-next-line @gitlab/require-i18n-strings, no-console
console.error('Failed to dismiss banner.', e);
});
this.visible = false;
},
},
};
</script>
<template>
<gl-banner
v-if="visible"
:title="$options.i18n.title"
:button-text="$options.i18n.button_text"
:button-link="preferencesBehaviorPath"
:svg-path="svgPath"
@close="handleClose"
>
<p>
{{ $options.i18n.body }}
</p>
</gl-banner>
</template>
import ProjectsList from '~/projects_list'; import ProjectsList from '~/projects_list';
import initCustomizeHomepageBanner from './init_customize_homepage_banner';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
new ProjectsList(); // eslint-disable-line no-new new ProjectsList(); // eslint-disable-line no-new
initCustomizeHomepageBanner();
}); });
import Vue from 'vue';
import CustomizeHomepageBanner from './components/customize_homepage_banner.vue';
export default () => {
const el = document.querySelector('.js-customize-homepage-banner');
if (!el) {
return false;
}
return new Vue({
el,
provide: { ...el.dataset },
render: createElement => createElement(CustomizeHomepageBanner),
});
};
...@@ -13,6 +13,7 @@ class RootController < Dashboard::ProjectsController ...@@ -13,6 +13,7 @@ class RootController < Dashboard::ProjectsController
before_action :redirect_unlogged_user, if: -> { current_user.nil? } before_action :redirect_unlogged_user, if: -> { current_user.nil? }
before_action :redirect_logged_user, if: -> { current_user.present? } before_action :redirect_logged_user, if: -> { current_user.present? }
before_action :customize_homepage, only: :index, if: -> { current_user.present? }
# We only need to load the projects when the user is logged in but did not # We only need to load the projects when the user is logged in but did not
# configure a dashboard. In which case we render projects. We can do that straight # configure a dashboard. In which case we render projects. We can do that straight
# from the #index action. # from the #index action.
...@@ -66,6 +67,10 @@ class RootController < Dashboard::ProjectsController ...@@ -66,6 +67,10 @@ class RootController < Dashboard::ProjectsController
root_urls.exclude?(home_page_url) root_urls.exclude?(home_page_url)
end end
def customize_homepage
@customize_homepage = experiment_enabled?(:customize_homepage)
end
end end
RootController.prepend_if_ee('EE::RootController') RootController.prepend_if_ee('EE::RootController')
...@@ -7,6 +7,7 @@ module UserCalloutsHelper ...@@ -7,6 +7,7 @@ module UserCalloutsHelper
SUGGEST_POPOVER_DISMISSED = 'suggest_popover_dismissed' SUGGEST_POPOVER_DISMISSED = 'suggest_popover_dismissed'
TABS_POSITION_HIGHLIGHT = 'tabs_position_highlight' TABS_POSITION_HIGHLIGHT = 'tabs_position_highlight'
WEBHOOKS_MOVED = 'webhooks_moved' WEBHOOKS_MOVED = 'webhooks_moved'
CUSTOMIZE_HOMEPAGE = 'customize_homepage'
def show_admin_integrations_moved? def show_admin_integrations_moved?
!user_dismissed?(ADMIN_INTEGRATIONS_MOVED) !user_dismissed?(ADMIN_INTEGRATIONS_MOVED)
...@@ -44,6 +45,10 @@ module UserCalloutsHelper ...@@ -44,6 +45,10 @@ module UserCalloutsHelper
!user_dismissed?(WEBHOOKS_MOVED) !user_dismissed?(WEBHOOKS_MOVED)
end end
def show_customize_homepage_banner?(customize_homepage)
customize_homepage && !user_dismissed?(CUSTOMIZE_HOMEPAGE)
end
private private
def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil) def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil)
......
...@@ -19,7 +19,8 @@ module UserCalloutEnums ...@@ -19,7 +19,8 @@ module UserCalloutEnums
webhooks_moved: 13, webhooks_moved: 13,
admin_integrations_moved: 15, admin_integrations_moved: 15,
personal_access_token_expiry: 21, # EE-only personal_access_token_expiry: 21, # EE-only
suggest_pipeline: 22 suggest_pipeline: 22,
customize_homepage: 23
} }
end end
end end
......
...@@ -3,6 +3,14 @@ ...@@ -3,6 +3,14 @@
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity") = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
- if show_customize_homepage_banner?(@customize_homepage)
= content_for :customize_homepage_banner do
.d-none.d-md-block{ class: "gl-pt-6! gl-pb-2! #{(container_class unless @no_container)} #{@content_class}" }
.js-customize-homepage-banner{ data: { svg_path: image_path('illustrations/monitoring/getting_started.svg'),
preferences_behavior_path: profile_preferences_path(anchor: 'behavior'),
callouts_path: user_callouts_path,
callouts_feature_id: UserCalloutsHelper::CUSTOMIZE_HOMEPAGE } }
= render_dashboard_gold_trial(current_user) = render_dashboard_gold_trial(current_user)
- page_title _("Projects") - page_title _("Projects")
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
= render_account_recovery_regular_check = render_account_recovery_regular_check
= render_if_exists "layouts/header/ee_subscribable_banner" = render_if_exists "layouts/header/ee_subscribable_banner"
= render_if_exists "shared/namespace_storage_limit_alert" = render_if_exists "shared/namespace_storage_limit_alert"
= yield :customize_homepage_banner
- unless @hide_breadcrumbs - unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs" = render "layouts/nav/breadcrumbs"
.d-flex .d-flex
......
...@@ -59,6 +59,9 @@ module Gitlab ...@@ -59,6 +59,9 @@ module Gitlab
}, },
contact_sales_btn_in_app: { contact_sales_btn_in_app: {
tracking_category: 'Growth::Conversion::Experiment::ContactSalesInApp' tracking_category: 'Growth::Conversion::Experiment::ContactSalesInApp'
},
customize_homepage: {
tracking_category: 'Growth::Expansion::Experiment::CustomizeHomepage'
} }
}.freeze }.freeze
......
...@@ -7429,6 +7429,15 @@ msgstr "" ...@@ -7429,6 +7429,15 @@ msgstr ""
msgid "Customize your pipeline configuration." msgid "Customize your pipeline configuration."
msgstr "" msgstr ""
msgid "CustomizeHomepageBanner|Do you want to customize this page?"
msgstr ""
msgid "CustomizeHomepageBanner|Go to preferences"
msgstr ""
msgid "CustomizeHomepageBanner|This page shows a list of your projects by default but it can be changed to show projects' activity, groups, your to-do list, assigned issues, assigned merge requests, and more. You can change this under \"Homepage content\" in your preferences"
msgstr ""
msgid "Cycle Time" msgid "Cycle Time"
msgstr "" msgstr ""
......
...@@ -122,6 +122,30 @@ RSpec.describe RootController do ...@@ -122,6 +122,30 @@ RSpec.describe RootController do
expect(response).to render_template 'dashboard/projects/index' expect(response).to render_template 'dashboard/projects/index'
end end
context 'when experiment is enabled' do
before do
stub_experiment_for_user(customize_homepage: true)
end
it 'renders the default dashboard' do
get :index
expect(assigns[:customize_homepage]).to be true
end
end
context 'when experiment not enabled' do
before do
stub_experiment(customize_homepage: false)
end
it 'renders the default dashboard' do
get :index
expect(assigns[:customize_homepage]).to be false
end
end
end end
end end
end end
......
import { shallowMount } from '@vue/test-utils';
import CustomizeHomepageBanner from '~/pages/dashboard/projects/index/components/customize_homepage_banner.vue';
import { GlBanner } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
const svgPath = '/illustrations/background';
const provide = {
svgPath,
preferencesBehaviorPath: 'some/behavior/path',
calloutsPath: 'call/out/path',
calloutsFeatureId: 'some-feature-id',
};
const createComponent = () => {
return shallowMount(CustomizeHomepageBanner, { provide });
};
describe('CustomizeHomepageBanner', () => {
let mockAxios;
let wrapper;
beforeEach(() => {
mockAxios = new MockAdapter(axios);
wrapper = createComponent();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
mockAxios.restore();
});
it('should render the banner when not dismissed', () => {
expect(wrapper.contains(GlBanner)).toBe(true);
});
it('should close the banner when dismiss is clicked', async () => {
mockAxios.onPost(provide.calloutsPath).replyOnce(200);
expect(wrapper.contains(GlBanner)).toBe(true);
wrapper.find(GlBanner).vm.$emit('close');
await wrapper.vm.$nextTick();
expect(wrapper.contains(GlBanner)).toBe(false);
});
it('includes the body text from options', () => {
expect(wrapper.html()).toContain(wrapper.vm.$options.i18n.body);
});
});
...@@ -81,6 +81,36 @@ RSpec.describe UserCalloutsHelper do ...@@ -81,6 +81,36 @@ RSpec.describe UserCalloutsHelper do
end end
end end
describe '.show_customize_homepage_banner?' do
let(:customize_homepage) { true }
subject { helper.show_customize_homepage_banner?(customize_homepage) }
context 'when user has not dismissed' do
before do
allow(helper).to receive(:user_dismissed?).with(described_class::CUSTOMIZE_HOMEPAGE) { false }
end
context 'when customize_homepage is set' do
it { is_expected.to be true }
end
context 'when customize_homepage is false' do
let(:customize_homepage) { false }
it { is_expected.to be false }
end
end
context 'when user dismissed' do
before do
allow(helper).to receive(:user_dismissed?).with(described_class::CUSTOMIZE_HOMEPAGE) { true }
end
it { is_expected.to be false }
end
end
describe '.render_flash_user_callout' do describe '.render_flash_user_callout' do
it 'renders the flash_user_callout partial' do it 'renders the flash_user_callout partial' do
expect(helper).to receive(:render) expect(helper).to receive(:render)
......
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