Commit 0b144001 authored by Alex Kalderimis's avatar Alex Kalderimis

Merge branch '328511-fj-add-analytics-menu' into 'master'

Add Analytics menu

See merge request gitlab-org/gitlab!66904
parents 57917640 0f31a593
# frozen_string_literal: true
module Analytics
module NavbarHelper
class NavbarSubItem
attr_reader :title, :path, :link, :link_to_options
def initialize(title:, path:, link:, link_to_options: {})
@title = title
@path = path
@link = link
@link_to_options = link_to_options.merge(title: title)
end
end
def group_analytics_navbar_links(group, current_user)
[]
end
private
def navbar_sub_item(args)
NavbarSubItem.new(**args)
end
end
end
Analytics::NavbarHelper.prepend_mod_with('Analytics::NavbarHelper')
- navbar_links = links.sort_by(&:title)
- all_paths = navbar_links.map(&:path)
- analytics_link = navbar_links.find { |link| link.title == _('Value stream') } || navbar_links.first
- if navbar_links.any?
= nav_link(path: all_paths) do
= link_to analytics_link.link, {class: 'shortcuts-analytics has-sub-items', data: { qa_selector: 'analytics_anchor' } } do
.nav-icon-container
= sprite_icon('chart')
%span.nav-item-name{ data: { qa_selector: 'analytics_link' } }
= _('Analytics')
%ul.sidebar-sub-level-items{ data: { qa_selector: 'analytics_sidebar_submenu' } }
= nav_link(path: analytics_link.path, html_options: { class: "fly-out-top-item" } ) do
= link_to analytics_link.link do
%strong.fly-out-top-item-name
= _('Analytics')
%li.divider.fly-out-top-item
- navbar_links.each do |menu_item|
= nav_link(path: menu_item.path) do
= link_to(menu_item.link, menu_item.link_to_options) do
%span= menu_item.title
= render 'layouts/nav/sidebar/analytics_links', links: group_analytics_navbar_links(@group, current_user)
- if group_sidebar_link?(:wiki) - if group_sidebar_link?(:wiki)
= render 'layouts/nav/sidebar/wiki_link', wiki_url: @group.wiki.web_url = render 'layouts/nav/sidebar/wiki_link', wiki_url: @group.wiki.web_url
......
# frozen_string_literal: true
module EE
module Analytics
module NavbarHelper
extend ::Gitlab::Utils::Override
override :group_analytics_navbar_links
def group_analytics_navbar_links(group, current_user)
super + [
group_ci_cd_analytics_navbar_link(group, current_user),
group_devops_adoption_navbar_link(group, current_user),
group_repository_analytics_navbar_link(group, current_user),
contribution_analytics_navbar_link(group, current_user),
group_insights_navbar_link(group, current_user),
issues_analytics_navbar_link(group, current_user),
productivity_analytics_navbar_link(group, current_user),
group_cycle_analytics_navbar_link(group, current_user),
group_merge_request_analytics_navbar_link(group, current_user)
].compact
end
private
# Currently an empty page, so don't show it on the navbar for now
def group_merge_request_analytics_navbar_link(group, current_user)
return
return unless group_sidebar_link?(:merge_request_analytics) # rubocop: disable Lint/UnreachableCode
navbar_sub_item(
title: _('Merge request'),
path: 'groups/analytics/merge_request_analytics#show',
link: group_analytics_merge_request_analytics_path(group)
)
end
def group_cycle_analytics_navbar_link(group, current_user)
return unless group_sidebar_link?(:cycle_analytics)
navbar_sub_item(
title: _('Value stream'),
path: 'groups/analytics/cycle_analytics#show',
link: group_analytics_cycle_analytics_path(group)
)
end
def group_devops_adoption_navbar_link(group, current_user)
return unless group_sidebar_link?(:group_devops_adoption)
navbar_sub_item(
title: _('DevOps adoption'),
path: 'groups/analytics/devops_adoption#show',
link: group_analytics_devops_adoption_path(group)
)
end
def productivity_analytics_navbar_link(group, current_user)
return unless group_sidebar_link?(:productivity_analytics)
navbar_sub_item(
title: _('Productivity'),
path: 'groups/analytics/productivity_analytics#show',
link: group_analytics_productivity_analytics_path(group)
)
end
def contribution_analytics_navbar_link(group, current_user)
return unless group_sidebar_link?(:contribution_analytics)
navbar_sub_item(
title: _('Contribution'),
path: 'groups/contribution_analytics#show',
link: group_contribution_analytics_path(group),
link_to_options: { data: { placement: 'right', qa_selector: 'contribution_analytics_link' } }
)
end
def group_insights_navbar_link(group, current_user)
return unless group_sidebar_link?(:group_insights)
navbar_sub_item(
title: _('Insights'),
path: 'groups/insights#show',
link: group_insights_path(group),
link_to_options: { class: 'shortcuts-group-insights', data: { qa_selector: 'group_insights_link' } }
)
end
def issues_analytics_navbar_link(group, current_user)
return unless group_sidebar_link?(:analytics)
navbar_sub_item(
title: _('Issue'),
path: 'issues_analytics#show',
link: group_issues_analytics_path(group)
)
end
def group_ci_cd_analytics_navbar_link(group, current_user)
return unless group.licensed_feature_available?(:group_ci_cd_analytics)
return unless group_sidebar_link?(:group_ci_cd_analytics)
navbar_sub_item(
title: _('CI/CD'),
path: 'groups/analytics/ci_cd_analytics#show',
link: group_analytics_ci_cd_analytics_path(group)
)
end
def group_repository_analytics_navbar_link(group, current_user)
return unless group.licensed_feature_available?(:group_coverage_reports)
return unless group_sidebar_link?(:repository_analytics)
navbar_sub_item(
title: _('Repositories'),
path: 'groups/analytics/repository_analytics#show',
link: group_analytics_repository_analytics_path(group)
)
end
end
end
end
- return unless group_sidebar_link?(:group_insights)
= nav_link(path: 'groups/insights#show') do
= link_to group_insights_path(@group), title: _('Insights'), class: 'shortcuts-group-insights', data: { qa_selector: 'group_insights_link' } do
%span= _('Insights')
...@@ -14,6 +14,7 @@ module EE ...@@ -14,6 +14,7 @@ module EE
insert_menu_after(::Sidebars::Groups::Menus::GroupInformationMenu, ::Sidebars::Groups::Menus::EpicsMenu.new(context)) insert_menu_after(::Sidebars::Groups::Menus::GroupInformationMenu, ::Sidebars::Groups::Menus::EpicsMenu.new(context))
insert_menu_after(::Sidebars::Groups::Menus::MergeRequestsMenu, ::Sidebars::Groups::Menus::SecurityComplianceMenu.new(context)) insert_menu_after(::Sidebars::Groups::Menus::MergeRequestsMenu, ::Sidebars::Groups::Menus::SecurityComplianceMenu.new(context))
insert_menu_after(::Sidebars::Groups::Menus::SecurityComplianceMenu, ::Sidebars::Groups::Menus::PushRulesMenu.new(context)) insert_menu_after(::Sidebars::Groups::Menus::SecurityComplianceMenu, ::Sidebars::Groups::Menus::PushRulesMenu.new(context))
insert_menu_after(::Sidebars::Groups::Menus::PackagesRegistriesMenu, ::Sidebars::Groups::Menus::AnalyticsMenu.new(context))
end end
end end
end end
......
# frozen_string_literal: true
module Sidebars
module Groups
module Menus
class AnalyticsMenu < ::Sidebars::Menu
include Gitlab::Utils::StrongMemoize
override :configure_menu_items
def configure_menu_items
add_item(ci_cd_analytics_menu_item)
add_item(contribution_analytics_menu_item)
add_item(devops_adoption_menu_item)
add_item(insights_analytics_menu_item)
add_item(issues_analytics_menu_item)
add_item(merge_request_analytics_menu_item)
add_item(productivity_analytics_menu_item)
add_item(repository_analytics_menu_item)
add_item(cycle_analytics_menu_item)
true
end
override :link
def link
return cycle_analytics_menu_item.link if cycle_analytics_menu_item.render?
renderable_items.first.link
end
override :extra_container_html_options
def extra_container_html_options
{
class: 'shortcuts-analytics'
}
end
override :title
def title
_('Analytics')
end
override :sprite_icon
def sprite_icon
'chart'
end
private
def ci_cd_analytics_menu_item
unless show_ci_cd_analytics?
return ::Sidebars::NilMenuItem.new(item_id: :ci_cd_analytics)
end
::Sidebars::MenuItem.new(
title: _('CI/CD'),
link: group_analytics_ci_cd_analytics_path(context.group),
active_routes: { path: 'groups/analytics/ci_cd_analytics#show' },
item_id: :ci_cd_analytics
)
end
def show_ci_cd_analytics?
context.group.licensed_feature_available?(:group_ci_cd_analytics) &&
::Feature.enabled?(:group_ci_cd_analytics_page, context.group, default_enabled: true) &&
can?(context.current_user, :view_group_ci_cd_analytics, context.group)
end
def contribution_analytics_menu_item
unless show_contribution_analytics?
return ::Sidebars::NilMenuItem.new(item_id: :contribution_analytics)
end
::Sidebars::MenuItem.new(
title: _('Contribution'),
link: group_contribution_analytics_path(context.group),
active_routes: { path: 'groups/contribution_analytics#show' },
container_html_options: { data: { placement: 'right' } },
item_id: :contribution_analytics
)
end
def show_contribution_analytics?
can?(context.current_user, :read_group_contribution_analytics, context.group) ||
LicenseHelper.show_promotions?(context.current_user)
end
def devops_adoption_menu_item
unless can?(context.current_user, :view_group_devops_adoption, context.group)
return ::Sidebars::NilMenuItem.new(item_id: :devops_adoption)
end
::Sidebars::MenuItem.new(
title: _('DevOps adoption'),
link: group_analytics_devops_adoption_path(context.group),
active_routes: { path: 'groups/analytics/devops_adoption#show' },
item_id: :devops_adoption
)
end
def insights_analytics_menu_item
unless context.group.insights_available?
return ::Sidebars::NilMenuItem.new(item_id: :insights)
end
::Sidebars::MenuItem.new(
title: _('Insights'),
link: group_insights_path(context.group),
active_routes: { path: 'groups/insights#show' },
container_html_options: { class: 'shortcuts-group-insights' },
item_id: :insights
)
end
def issues_analytics_menu_item
unless context.group.licensed_feature_available?(:issues_analytics)
return ::Sidebars::NilMenuItem.new(item_id: :issues_analytics)
end
::Sidebars::MenuItem.new(
title: _('Issue'),
link: group_issues_analytics_path(context.group),
active_routes: { path: 'issues_analytics#show' },
item_id: :issues_analytics
)
end
def merge_request_analytics_menu_item
unless show_merge_requests_analytics?
return ::Sidebars::NilMenuItem.new(item_id: :merge_requests_analytics)
end
::Sidebars::MenuItem.new(
title: _('Merge request'),
link: group_analytics_merge_request_analytics_path(context.group),
active_routes: { path: 'groups/analytics/merge_request_analytics#show' },
item_id: :merge_requests_analytics
)
end
# Currently an empty page, so don't show it on the navbar for now
def show_merge_requests_analytics?
return false
can?(context.current_user, :read_group_merge_request_analytics, context.group) # rubocop:disable Lint/UnreachableCode
end
def productivity_analytics_menu_item
unless show_productivity_analytics?
return ::Sidebars::NilMenuItem.new(item_id: :productivity_analytics)
end
::Sidebars::MenuItem.new(
title: _('Productivity'),
link: group_analytics_productivity_analytics_path(context.group),
active_routes: { path: 'groups/analytics/productivity_analytics#show' },
item_id: :productivity_analytics
)
end
def show_productivity_analytics?
context.group.licensed_feature_available?(:productivity_analytics) &&
can?(context.current_user, :view_productivity_analytics, context.group)
end
def repository_analytics_menu_item
unless show_repository_analytics?
return ::Sidebars::NilMenuItem.new(item_id: :repository_analytics)
end
::Sidebars::MenuItem.new(
title: _('Repository'),
link: group_analytics_repository_analytics_path(context.group),
active_routes: { path: 'groups/analytics/repository_analytics#show' },
item_id: :repository_analytics
)
end
def show_repository_analytics?
context.group.licensed_feature_available?(:group_coverage_reports) &&
can?(context.current_user, :read_group_repository_analytics, context.group)
end
def cycle_analytics_menu_item
strong_memoize(:cycle_analytics_menu_item) do
unless can?(context.current_user, :read_group_cycle_analytics, context.group)
next ::Sidebars::NilMenuItem.new(item_id: :cycle_analytics)
end
::Sidebars::MenuItem.new(
title: _('Value stream'),
link: group_analytics_cycle_analytics_path(context.group),
active_routes: { path: 'groups/analytics/cycle_analytics#show' },
item_id: :cycle_analytics
)
end
end
end
end
end
end
...@@ -70,7 +70,7 @@ RSpec.describe 'Group navbar' do ...@@ -70,7 +70,7 @@ RSpec.describe 'Group navbar' do
it 'redirects to value stream when Analytics item is clicked' do it 'redirects to value stream when Analytics item is clicked' do
page.within('.sidebar-top-level-items') do page.within('.sidebar-top-level-items') do
find('[data-qa-selector=analytics_anchor]').click # rubocop:disable QA/SelectorUsage find('.shortcuts-analytics').click
end end
wait_for_requests wait_for_requests
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sidebars::Groups::Menus::AnalyticsMenu do
let_it_be(:owner) { create(:user) }
let_it_be_with_refind(:group) do
create(:group, :private).tap do |g|
g.add_owner(owner)
end
end
let(:user) { owner }
let(:context) { Sidebars::Groups::Context.new(current_user: user, container: group) }
let(:menu) { described_class.new(context) }
describe '#link' do
before do
stub_licensed_features(cycle_analytics_for_groups: true, group_ci_cd_analytics: true)
end
it 'returns link to the value stream page' do
expect(menu.link).to include("/groups/#{group.full_path}/-/analytics/value_stream_analytics")
end
context 'when Value Stream is not visible' do
it 'returns link to the the first visible menu item' do
allow(menu).to receive(:cycle_analytics_menu_item).and_return(double(render?: false))
expect(menu.link).not_to include("/groups/#{group.full_path}/-/analytics/value_stream_analytics")
expect(menu.link).to eq menu.renderable_items.first.link
end
end
end
describe 'Menu items' do
subject { described_class.new(context).renderable_items.index { |e| e.item_id == item_id } }
describe 'CI/CD' do
let(:item_id) { :ci_cd_analytics }
before do
stub_licensed_features(group_ci_cd_analytics: true)
end
specify { is_expected.not_to be_nil }
describe 'when licensed feature :group_ci_cd_analytics is disabled' do
specify do
stub_licensed_features(group_ci_cd_analytics: false)
is_expected.to be_nil
end
end
describe 'when feature flag :group_ci_cd_analytics_page is disabled' do
specify do
stub_feature_flags(group_ci_cd_analytics_page: false)
is_expected.to be_nil
end
end
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Devops adoptions' do
let(:item_id) { :devops_adoption }
before do
stub_licensed_features(group_level_devops_adoption: true)
end
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Repository' do
let(:item_id) { :repository_analytics }
before do
stub_licensed_features(group_coverage_reports: true, group_repository_analytics: true)
end
specify { is_expected.not_to be_nil }
describe 'when licensed feature :group_coverage_reports is disabled' do
specify do
stub_licensed_features(group_coverage_reports: false)
is_expected.to be_nil
end
end
describe 'when licensed feature :group_repository_analytics is disabled' do
specify do
stub_licensed_features(group_repository_analytics: false)
is_expected.to be_nil
end
end
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Contribution analytics' do
let(:item_id) { :contribution_analytics }
before do
stub_licensed_features(contribution_analytics: true)
end
specify { is_expected.not_to be_nil }
describe 'when licensed feature :group_coverage_reports is disabled' do
specify do
stub_licensed_features(contribution_analytics: false)
is_expected.to be_nil
end
end
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
describe 'when show_promotions? is true' do
specify do
allow(LicenseHelper).to receive(:show_promotions?).and_return(true)
is_expected.not_to be_nil
end
end
end
end
describe 'Insights' do
let(:item_id) { :insights }
let(:insights_available) { true }
before do
allow(group).to receive(:insights_available?).and_return(insights_available)
end
specify { is_expected.not_to be_nil }
describe 'when insights are not available' do
let(:insights_available) { false }
specify { is_expected.to be_nil }
end
end
describe 'Issue analytics' do
let(:item_id) { :issues_analytics }
let(:issues_analytics_enabled) { true }
before do
stub_licensed_features(issues_analytics: issues_analytics_enabled)
end
specify { is_expected.not_to be_nil }
describe 'when licensed feature :issues_analytics is disabled' do
let(:issues_analytics_enabled) { false }
specify { is_expected.to be_nil }
end
end
describe 'Productivity analytics' do
let(:item_id) { :productivity_analytics }
let(:productivity_analytics_enabled) { true }
before do
stub_licensed_features(productivity_analytics: productivity_analytics_enabled)
end
specify { is_expected.not_to be_nil }
describe 'when licensed feature :productivity_analytics is disabled' do
let(:productivity_analytics_enabled) { false }
specify { is_expected.to be_nil }
end
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Value Stream' do
let(:item_id) { :cycle_analytics }
let(:cycle_analytics_enabled) { true }
before do
stub_licensed_features(cycle_analytics_for_groups: cycle_analytics_enabled)
end
specify { is_expected.not_to be_nil }
describe 'when licensed feature :cycle_analytics_for_groups is disabled' do
let(:cycle_analytics_enabled) { false }
specify { is_expected.to be_nil }
end
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
end
end
...@@ -323,15 +323,46 @@ RSpec.describe 'layouts/nav/sidebar/_group' do ...@@ -323,15 +323,46 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
end end
end end
describe 'DevOps adoption link' do describe 'Analytics menu' do
let!(:current_user) { create(:user) } let_it_be(:owner) { create(:user) }
let_it_be(:guest) { create(:user) }
let_it_be_with_refind(:group) do
create(:group).tap do |g|
g.add_maintainer(owner)
g.add_guest(guest)
end
end
before do before do
group.add_maintainer(current_user) allow(view).to receive(:current_user).and_return(owner)
end
allow(view).to receive(:current_user).and_return(current_user) describe 'CI/CD analytics' do
let(:ci_cd_analytics_enabled) { true }
before do
stub_licensed_features(group_ci_cd_analytics: ci_cd_analytics_enabled)
end
it 'has a link to the CI/CD analytics page' do
render
expect(rendered).to have_link('CI/CD', href: group_analytics_ci_cd_analytics_path(group))
end end
describe 'feature is disabled' do
let(:ci_cd_analytics_enabled) { false }
specify do
render
expect(rendered).not_to have_link('CI/CD')
end
end
end
describe 'DevOps' do
context 'DevOps adoption feature is available' do context 'DevOps adoption feature is available' do
before do before do
stub_licensed_features(group_level_devops_adoption: true) stub_licensed_features(group_level_devops_adoption: true)
...@@ -357,13 +388,31 @@ RSpec.describe 'layouts/nav/sidebar/_group' do ...@@ -357,13 +388,31 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
end end
end end
describe 'contribution analytics tab' do describe 'Repository analytics' do
let!(:current_user) { create(:user) }
before do before do
group.add_guest(current_user) stub_licensed_features(group_coverage_reports: true, group_repository_analytics: true)
end
allow(view).to receive(:current_user).and_return(current_user) it 'has a link to the Repository analytics page' do
render
expect(rendered).to have_link('Repository', href: group_analytics_repository_analytics_path(group))
end
describe 'feature is not available' do
specify do
stub_licensed_features(group_coverage_reports: false)
render
expect(rendered).not_to have_link('Repository')
end
end
end
describe 'contribution analytics tab' do
before do
allow(view).to receive(:current_user).and_return(guest)
end end
context 'contribution analytics feature is available' do context 'contribution analytics feature is available' do
...@@ -444,6 +493,97 @@ RSpec.describe 'layouts/nav/sidebar/_group' do ...@@ -444,6 +493,97 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
end end
end end
describe 'Insights analytics' do
it 'has a link to the insights analytics page' do
allow(group).to receive(:insights_available?).and_return(true)
render
expect(rendered).to have_link('Insights', href: group_insights_path(group))
end
describe 'feature is disabled' do
specify do
render
expect(rendered).not_to have_link('Insights')
end
end
end
describe 'Issue analytics' do
let(:issues_analytics_enabled) { true }
before do
stub_licensed_features(issues_analytics: issues_analytics_enabled)
end
it 'has a link to the Issue analytics page' do
render
expect(rendered).to have_link('Issue', href: group_issues_analytics_path(group))
end
describe 'feature is disabled' do
let(:issues_analytics_enabled) { false }
specify do
render
expect(rendered).not_to have_link(exact_text: 'Issue')
end
end
end
describe 'Productivity analytics' do
let(:productivity_analytics_enabled) { true }
before do
stub_licensed_features(productivity_analytics: productivity_analytics_enabled)
end
it 'has a link to the Productivity analytics page' do
render
expect(rendered).to have_link('Productivity', href: group_analytics_productivity_analytics_path(group))
end
describe 'feature is disabled' do
let(:productivity_analytics_enabled) { false }
specify do
render
expect(rendered).not_to have_link('Productivity', href: group_analytics_productivity_analytics_path(group))
end
end
end
describe 'Cycle analytics' do
let(:cycle_analytics_enabled) { true }
before do
stub_licensed_features(cycle_analytics_for_groups: cycle_analytics_enabled)
end
it 'has a link to the Cycle analytics page' do
render
expect(rendered).to have_link('Value stream', href: group_analytics_cycle_analytics_path(group))
end
describe 'feature is disabled' do
let(:cycle_analytics_enabled) { false }
specify do
render
expect(rendered).not_to have_link('Value stream')
end
end
end
end
describe 'wiki tab' do describe 'wiki tab' do
let(:can_read_wiki) { true } let(:can_read_wiki) { true }
......
...@@ -32,10 +32,6 @@ module QA ...@@ -32,10 +32,6 @@ module QA
element :ldap_synchronization_link element :ldap_synchronization_link
element :billing_link element :billing_link
end end
view 'ee/app/views/layouts/nav/_group_insights_link.html.haml' do
element :group_insights_link
end
end end
end end
...@@ -71,10 +67,18 @@ module QA ...@@ -71,10 +67,18 @@ module QA
end end
end end
def click_contribution_analytics_item
hover_group_analytics do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Contribution')
end
end
end
def click_group_insights_link def click_group_insights_link
hover_element(:analytics_link) do hover_group_analytics do
within_submenu(:analytics_sidebar_submenu) do within_submenu do
click_element(:group_insights_link) click_element(:sidebar_menu_item_link, menu_item: 'Insights')
end end
end end
end end
...@@ -141,6 +145,15 @@ module QA ...@@ -141,6 +145,15 @@ module QA
yield yield
end end
end end
def hover_group_analytics
within_sidebar do
scroll_to_element(:sidebar_menu_link, menu_item: 'Analytics')
find_element(:sidebar_menu_link, menu_item: 'Analytics').hover
yield
end
end
end end
end end
end end
......
...@@ -15,11 +15,6 @@ module QA ...@@ -15,11 +15,6 @@ module QA
element :group_package_settings_link element :group_package_settings_link
end end
view 'app/views/layouts/nav/sidebar/_analytics_links.html.haml' do
element :analytics_link
element :analytics_sidebar_submenu
end
def click_group_members_item def click_group_members_item
hover_group_information do hover_group_information do
within_submenu do within_submenu do
...@@ -42,14 +37,6 @@ module QA ...@@ -42,14 +37,6 @@ module QA
end end
end end
def click_contribution_analytics_item
hover_element(:analytics_link) do
within_submenu(:analytics_sidebar_submenu) do
click_element(:contribution_analytics_link)
end
end
end
def click_group_general_settings_item def click_group_general_settings_item
hover_element(:group_settings) do hover_element(:group_settings) do
within_submenu(:group_sidebar_submenu) do within_submenu(:group_sidebar_submenu) do
......
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