Instrumenting project sidebar menus

In this commit, we're adding some tracking attributes
to the project sidebar. This way we can track the state of
menus and menu items when they're clicked.

In order to trigger the tracking, the menus and menu items
must have the `data-track-label` attribute. Based on that
attr and also the context of the sidebar, we calculate
the other tracking attributes.

Changelog: added
parent 0d9ae330
import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation'; import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation';
import { initSidebarTracking } from '../shared/nav/sidebar_tracking';
import Project from './project'; import Project from './project';
new Project(); // eslint-disable-line no-new new Project(); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new new ShortcutsNavigation(); // eslint-disable-line no-new
initSidebarTracking();
function onSidebarLinkClick() {
const setDataTrackAction = (element, action) => {
element.setAttribute('data-track-action', action);
};
const setDataTrackExtra = (element, value) => {
const SIDEBAR_COLLAPSED = 'Collapsed';
const SIDEBAR_EXPANDED = 'Expanded';
const sidebarCollapsed = document
.querySelector('.nav-sidebar')
.classList.contains('js-sidebar-collapsed')
? SIDEBAR_COLLAPSED
: SIDEBAR_EXPANDED;
element.setAttribute(
'data-track-extra',
JSON.stringify({ sidebar_display: sidebarCollapsed, menu_display: value }),
);
};
const EXPANDED = 'Expanded';
const FLY_OUT = 'Fly out';
const CLICK_MENU_ACTION = 'click_menu';
const CLICK_MENU_ITEM_ACTION = 'click_menu_item';
const parentElement = this.parentNode;
const subMenuList = parentElement.closest('.sidebar-sub-level-items');
if (subMenuList) {
const isFlyOut = subMenuList.classList.contains('fly-out-list') ? FLY_OUT : EXPANDED;
setDataTrackExtra(parentElement, isFlyOut);
setDataTrackAction(parentElement, CLICK_MENU_ITEM_ACTION);
} else {
const isFlyOut = parentElement.classList.contains('is-showing-fly-out') ? FLY_OUT : EXPANDED;
setDataTrackExtra(parentElement, isFlyOut);
setDataTrackAction(parentElement, CLICK_MENU_ACTION);
}
}
export const initSidebarTracking = () => {
document.querySelectorAll('.nav-sidebar li[data-track-label] > a').forEach((link) => {
link.addEventListener('click', onSidebarLinkClick);
});
};
= nav_link(**sidebar_menu_item.active_routes) do = nav_link(**sidebar_menu_item.active_routes, html_options: sidebar_menu_item.nav_link_html_options) do
= link_to sidebar_menu_item.link, **sidebar_menu_item.container_html_options, data: { qa_selector: 'sidebar_menu_item_link', qa_menu_item: sidebar_menu_item.title } do = link_to sidebar_menu_item.link, **sidebar_menu_item.container_html_options, data: { qa_selector: 'sidebar_menu_item_link', qa_menu_item: sidebar_menu_item.title } do
%span %span
= sidebar_menu_item.title = sidebar_menu_item.title
......
...@@ -38,6 +38,16 @@ module Sidebars ...@@ -38,6 +38,16 @@ module Sidebars
# in the helper method that sets the active class # in the helper method that sets the active class
# on each element. # on each element.
def nav_link_html_options def nav_link_html_options
{
data: {
track_label: self.class.name.demodulize.underscore
}
}.deep_merge(extra_nav_link_html_options)
end
# Classes should mostly override this method
# and not `nav_link_html_options`.
def extra_nav_link_html_options
{} {}
end end
......
...@@ -22,5 +22,13 @@ module Sidebars ...@@ -22,5 +22,13 @@ module Sidebars
def render? def render?
true true
end end
def nav_link_html_options
{
data: {
track_label: item_id
}
}
end
end end
end end
...@@ -35,14 +35,13 @@ module Sidebars ...@@ -35,14 +35,13 @@ module Sidebars
end end
end end
override :extra_container_html_options override :extra_nav_link_html_options
def nav_link_html_options def extra_nav_link_html_options
{ {
class: 'home', class: 'home',
data: { data: {
track_action: 'click_menu', track_label: 'learn_gitlab',
track_property: context.learn_gitlab_experiment_tracking_category, track_property: context.learn_gitlab_experiment_tracking_category
track_label: 'learn_gitlab'
} }
} }
end end
......
...@@ -29,8 +29,8 @@ module Sidebars ...@@ -29,8 +29,8 @@ module Sidebars
end end
end end
override :nav_link_html_options override :extra_nav_link_html_options
def nav_link_html_options def extra_nav_link_html_options
{ class: 'home' } { class: 'home' }
end end
......
...@@ -28,8 +28,8 @@ module Sidebars ...@@ -28,8 +28,8 @@ module Sidebars
} }
end end
override :nav_link_html_options override :extra_nav_link_html_options
def nav_link_html_options def extra_nav_link_html_options
return {} if Feature.disabled?(:sidebar_refactor, context.current_user, default_enabled: :yaml) return {} if Feature.disabled?(:sidebar_refactor, context.current_user, default_enabled: :yaml)
{ class: 'context-header' } { class: 'context-header' }
......
import { setHTMLFixture } from 'helpers/fixtures';
import { initSidebarTracking } from '~/pages/shared/nav/sidebar_tracking';
describe('~/pages/shared/nav/sidebar_tracking.js', () => {
beforeEach(() => {
setHTMLFixture(`
<aside class="nav-sidebar">
<div class="nav-sidebar-inner-scroll">
<ul class="sidebar-top-level-items">
<li data-track-label="project_information_menu" class="home">
<a aria-label="Project information" class="shortcuts-project-information has-sub-items" href="">
<span class="nav-icon-container">
<svg class="s16" data-testid="project-icon">
<use xlink:href="/assets/icons-1b2dadc4c3d49797908ba67b8f10da5d63dd15d859bde28d66fb60bbb97a4dd5.svg#project"></use>
</svg>
</span>
<span class="nav-item-name">Project information</span>
</a>
<ul class="sidebar-sub-level-items">
<li class="fly-out-top-item">
<a aria-label="Project information" href="#">
<strong class="fly-out-top-item-name">Project information</strong>
</a>
</li>
<li class="divider fly-out-top-item"></li>
<li data-track-label="activity" class="">
<a aria-label="Activity" class="shortcuts-project-activity" href=#">
<span>Activity</span>
</a>
</li>
<li data-track-label="labels" class="">
<a aria-label="Labels" href="#">
<span>Labels</span>
</a>
</li>
<li data-track-label="members" class="">
<a aria-label="Members" href="#">
<span>Members</span>
</a>
</li>
</ul>
</li>
</ul>
</div>
</aside>
`);
initSidebarTracking();
});
describe('sidebar is not collapsed', () => {
describe('menu is not expanded', () => {
it('sets the proper data tracking attributes when clicking on menu', () => {
const menu = document.querySelector('li[data-track-label="project_information_menu"]');
const menuLink = menu.querySelector('a');
menu.classList.add('is-over', 'is-showing-fly-out');
menuLink.click();
expect(menu.dataset).toMatchObject({
trackAction: 'click_menu',
trackExtra: JSON.stringify({
sidebar_display: 'Expanded',
menu_display: 'Fly out',
}),
});
});
it('sets the proper data tracking attributes when clicking on submenu', () => {
const menu = document.querySelector('li[data-track-label="activity"]');
const menuLink = menu.querySelector('a');
const submenuList = document.querySelector('ul.sidebar-sub-level-items');
submenuList.classList.add('fly-out-list');
menuLink.click();
expect(menu.dataset).toMatchObject({
trackAction: 'click_menu_item',
trackExtra: JSON.stringify({
sidebar_display: 'Expanded',
menu_display: 'Fly out',
}),
});
});
});
describe('menu is expanded', () => {
it('sets the proper data tracking attributes when clicking on menu', () => {
const menu = document.querySelector('li[data-track-label="project_information_menu"]');
const menuLink = menu.querySelector('a');
menu.classList.add('active');
menuLink.click();
expect(menu.dataset).toMatchObject({
trackAction: 'click_menu',
trackExtra: JSON.stringify({
sidebar_display: 'Expanded',
menu_display: 'Expanded',
}),
});
});
it('sets the proper data tracking attributes when clicking on submenu', () => {
const menu = document.querySelector('li[data-track-label="activity"]');
const menuLink = menu.querySelector('a');
menu.classList.add('active');
menuLink.click();
expect(menu.dataset).toMatchObject({
trackAction: 'click_menu_item',
trackExtra: JSON.stringify({
sidebar_display: 'Expanded',
menu_display: 'Expanded',
}),
});
});
});
});
describe('sidebar is collapsed', () => {
beforeEach(() => {
document.querySelector('aside.nav-sidebar').classList.add('js-sidebar-collapsed');
});
it('sets the proper data tracking attributes when clicking on menu', () => {
const menu = document.querySelector('li[data-track-label="project_information_menu"]');
const menuLink = menu.querySelector('a');
menu.classList.add('is-over', 'is-showing-fly-out');
menuLink.click();
expect(menu.dataset).toMatchObject({
trackAction: 'click_menu',
trackExtra: JSON.stringify({
sidebar_display: 'Collapsed',
menu_display: 'Fly out',
}),
});
});
it('sets the proper data tracking attributes when clicking on submenu', () => {
const menu = document.querySelector('li[data-track-label="activity"]');
const menuLink = menu.querySelector('a');
const submenuList = document.querySelector('ul.sidebar-sub-level-items');
submenuList.classList.add('fly-out-list');
menuLink.click();
expect(menu.dataset).toMatchObject({
trackAction: 'click_menu_item',
trackExtra: JSON.stringify({
sidebar_display: 'Collapsed',
menu_display: 'Fly out',
}),
});
});
});
});
...@@ -27,7 +27,6 @@ RSpec.describe Sidebars::Projects::Menus::LearnGitlabMenu do ...@@ -27,7 +27,6 @@ RSpec.describe Sidebars::Projects::Menus::LearnGitlabMenu do
{ {
class: 'home', class: 'home',
data: { data: {
track_action: 'click_menu',
track_property: tracking_category, track_property: tracking_category,
track_label: 'learn_gitlab' track_label: 'learn_gitlab'
} }
......
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