Skip to content
代码片段 群组 项目
提交 d3721f5b 编辑于 作者: Paul Gascou-Vaillancourt's avatar Paul Gascou-Vaillancourt 提交者: Alexander Turinske
浏览文件

Show the Trial Status widget in the super sidebar

This adds the ability for the Trial Status widget to show up in the
super sidebar.
上级 4eff21e1
No related branches found
No related tags found
无相关合并请求
显示
225 个添加17 个删除
......@@ -27,11 +27,16 @@ export default {
HelpCenter,
SidebarMenu,
SidebarPortalTarget,
TrialStatusWidget: () =>
import('ee_component/contextual_sidebar/components/trial_status_widget.vue'),
TrialStatusPopover: () =>
import('ee_component/contextual_sidebar/components/trial_status_popover.vue'),
},
mixins: [glFeatureFlagsMixin()],
i18n: {
skipToMainContent: __('Skip to main content'),
},
inject: ['showTrialStatusWidget'],
props: {
sidebarData: {
type: Object,
......@@ -122,6 +127,12 @@ export default {
{{ $options.i18n.skipToMainContent }}
</gl-button>
<user-bar :has-collapse-button="!isPeek" :sidebar-data="sidebarData" />
<div v-if="showTrialStatusWidget" class="gl-px-2 gl-py-2">
<trial-status-widget
class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-mb-1 gl-px-3 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! nav-item-link gl-py-3"
/>
<trial-status-popover />
</div>
<div class="gl-display-flex gl-flex-direction-column gl-flex-grow-1 gl-overflow-hidden">
<context-switcher-toggle
:context="sidebarData.current_context_header"
......
......@@ -18,6 +18,48 @@ const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
const getTrialStatusWidgetData = (sidebarData) => {
if (sidebarData.trial_status_widget_data_attrs && sidebarData.trial_status_popover_data_attrs) {
const {
containerId,
trialDaysUsed,
trialDuration,
navIconImagePath,
percentageComplete,
planName,
plansHref,
} = convertObjectPropsToCamelCase(sidebarData.trial_status_widget_data_attrs);
const {
daysRemaining,
targetId,
trialEndDate,
namespaceId,
userName,
firstName,
lastName,
companyName,
glmContent,
} = convertObjectPropsToCamelCase(sidebarData.trial_status_popover_data_attrs);
return {
showTrialStatusWidget: true,
containerId,
trialDaysUsed: Number(trialDaysUsed),
trialDuration: Number(trialDuration),
navIconImagePath,
percentageComplete: Number(percentageComplete),
planName,
plansHref,
daysRemaining,
targetId,
trialEndDate: new Date(trialEndDate),
user: { namespaceId, userName, firstName, lastName, companyName, glmContent },
};
}
return { showTrialStatusWidget: false };
};
export const initSuperSidebar = () => {
const el = document.querySelector('.js-super-sidebar');
......@@ -41,6 +83,7 @@ export const initSuperSidebar = () => {
rootPath,
toggleNewNavEndpoint,
isImpersonating,
...getTrialStatusWidgetData(sidebarData),
},
store: createStore({
searchPath,
......
......@@ -166,6 +166,11 @@
opacity: 1;
}
}
#trial-status-sidebar-widget:hover {
text-decoration: none;
@include gl-text-contrast-light;
}
}
.super-sidebar-skip-to {
......
......@@ -51,10 +51,10 @@ export default {
@click="onWidgetClick"
>
<div class="gl-display-flex gl-w-full">
<span class="nav-icon-container svg-container">
<span class="nav-icon-container svg-container gl-mr-3">
<img :src="navIconImagePath" width="16" class="svg" />
</span>
<span class="nav-item-name">
<span class="nav-item-name gl-flex-grow-1">
{{ widgetTitle }}
</span>
<span class="collapse-text gl-font-sm gl-mr-auto">
......
......@@ -4,6 +4,8 @@ module EE
module SidebarsHelper
extend ::Gitlab::Utils::Override
include TrialStatusWidgetHelper
override :project_sidebar_context_data
def project_sidebar_context_data(project, user, current_ref, **args)
super.merge({
......@@ -30,14 +32,16 @@ def your_work_context_data(user)
override :super_sidebar_context
def super_sidebar_context(user, group:, project:, panel:, panel_type:)
show_buy_pipeline_minutes = show_buy_pipeline_minutes?(project, group)
context = super
root_namespace = (project || group)&.root_ancestor
return super.merge({ show_tanuki_bot: show_tanuki_bot_chat? }) unless show_buy_pipeline_minutes
context.merge!(trial_data(root_namespace), show_tanuki_bot: show_tanuki_bot_chat?)
root_namespace = root_ancestor_namespace(project, group)
show_buy_pipeline_minutes = show_buy_pipeline_minutes?(project, group)
super.merge({
show_tanuki_bot: show_tanuki_bot_chat?,
return context unless show_buy_pipeline_minutes && root_namespace.present?
context.merge({
pipeline_minutes: {
show_buy_pipeline_minutes: show_buy_pipeline_minutes,
show_notification_dot: show_pipeline_minutes_notification_dot?(project, group),
......@@ -60,5 +64,37 @@ def super_sidebar_context(user, group:, project:, panel:, panel_type:)
}
})
end
private
def trial_data(root_namespace)
if root_namespace.present? &&
::Gitlab::CurrentSettings.should_check_namespace_plan? &&
root_namespace.trial_active? &&
can?(current_user, :admin_namespace, root_namespace)
trial_status = trial_status(root_namespace)
return {
trial_status_widget_data_attrs: trial_status_widget_data_attrs(root_namespace, trial_status),
trial_status_popover_data_attrs: trial_status_popover_data_attrs(root_namespace, trial_status,
ultimate_plan_id)
}
end
{}
end
def trial_status(group)
GitlabSubscriptions::TrialStatus.new(group.trial_starts_on, group.trial_ends_on)
end
def ultimate_plan_id
# supplying plan here rejects any free plans so we won't get that data returned
plans = GitlabSubscriptions::FetchSubscriptionPlansService.new(plan: :free).execute
return unless plans
plans.find { |data| data['code'] == 'ultimate' }&.fetch('id', nil)
end
end
end
......@@ -14,7 +14,11 @@ def configure_menus
context.is_super_sidebar ? ::Sidebars::Groups::Menus::SettingsMenu : ::Sidebars::Groups::Menus::GroupInformationMenu,
::Sidebars::Groups::Menus::EpicsMenu.new(context)
)
insert_menu_before(::Sidebars::Groups::Menus::GroupInformationMenu, ::Sidebars::Groups::Menus::TrialWidgetMenu.new(context))
unless context.is_super_sidebar
insert_menu_before(::Sidebars::Groups::Menus::GroupInformationMenu, ::Sidebars::Groups::Menus::TrialWidgetMenu.new(context))
end
insert_menu_after(
context.is_super_sidebar ? ::Sidebars::Groups::Menus::CiCdMenu : ::Sidebars::Groups::Menus::MergeRequestsMenu,
::Sidebars::Groups::Menus::SecurityComplianceMenu.new(context)
......
......@@ -10,10 +10,13 @@ module Panel
def configure_menus
super
insert_menu_before(
::Sidebars::Projects::Menus::ProjectInformationMenu,
::Sidebars::Projects::Menus::TrialWidgetMenu.new(context)
)
unless context.is_super_sidebar
insert_menu_before(
::Sidebars::Projects::Menus::ProjectInformationMenu,
::Sidebars::Projects::Menus::TrialWidgetMenu.new(context)
)
end
insert_menu_after(
::Sidebars::Projects::Menus::ProjectInformationMenu,
::Sidebars::Projects::Menus::LearnGitlabMenu.new(context)
......
......@@ -13,7 +13,7 @@ exports[`TrialStatusWidget component without the optional containerId prop match
class="gl-display-flex gl-w-full"
>
<span
class="nav-icon-container svg-container"
class="nav-icon-container svg-container gl-mr-3"
>
<img
class="svg"
......@@ -23,7 +23,7 @@ exports[`TrialStatusWidget component without the optional containerId prop match
</span>
<span
class="nav-item-name"
class="nav-item-name gl-flex-grow-1"
>
Ultimate Trial
......
......@@ -3,6 +3,7 @@
require 'spec_helper'
RSpec.describe ::SidebarsHelper, feature_category: :navigation do
using RSpec::Parameterized::TableSyntax
include Devise::Test::ControllerHelpers
describe '#super_sidebar_context' do
......@@ -50,12 +51,79 @@
end
end
shared_examples 'trial status widget data' do
describe 'trial status on .com', :saas do
let_it_be(:root_group) { namespace.root_ancestor }
let_it_be(:gitlab_subscription) { build(:gitlab_subscription, :active_trial, :free, namespace: root_group) }
before do
allow_next_instance_of(GitlabSubscriptions::FetchSubscriptionPlansService) do |instance|
allow(instance).to receive(:execute).and_return([{ 'code' => 'ultimate', 'id' => 'ultimate-plan-id' }])
end
end
describe 'does not return trial status widget data' do
where(:description, :should_check_namespace_plan, :trial_active, :can_admin) do
'when instance does not check namespace plan' | false | true | true
'when no trial is active' | true | false | true
'when user cannot admin namespace' | true | true | false
end
with_them do
before do
allow(helper).to receive(:can?).with(user, :admin_namespace, root_group).and_return(can_admin)
stub_ee_application_setting(should_check_namespace_plan: should_check_namespace_plan)
allow(root_group).to receive(:trial_active?).and_return(trial_active)
end
it { is_expected.not_to include(:trial_status_widget_data_attrs) }
it { is_expected.not_to include(:trial_status_popover_data_attrs) }
end
end
context 'when a trial is in progress' do
before do
allow(helper).to receive(:can?).with(user, :admin_namespace, root_group).and_return(true)
stub_ee_application_setting(should_check_namespace_plan: true)
allow(root_group).to receive(:trial_active?).and_return(true)
end
it 'returns trial status widget data' do
expect(subject[:trial_status_widget_data_attrs]).to match({
container_id: "trial-status-sidebar-widget",
nav_icon_image_path: match_asset_path("/assets/illustrations/golden_tanuki.svg"),
percentage_complete: 50.0,
plan_name: nil,
plans_href: group_billings_path(root_group),
trial_days_used: 15,
trial_duration: 30
})
expect(subject[:trial_status_popover_data_attrs]).to eq({
company_name: "",
container_id: "trial-status-sidebar-widget",
days_remaining: 15,
first_name: user.first_name,
glm_content: "trial-status-show-group",
last_name: user.last_name,
namespace_id: nil,
plan_name: nil,
plans_href: group_billings_path(root_group),
target_id: "trial-status-sidebar-widget",
trial_end_date: root_group.trial_ends_on,
user_name: user.username
})
end
end
end
end
context 'when in project scope' do
before do
allow(helper).to receive(:show_buy_pipeline_minutes?).and_return(true)
end
let_it_be(:project) { build(:project) }
let_it_be(:namespace) { project }
let_it_be(:group) { nil }
let(:subject) do
......@@ -63,6 +131,7 @@
end
include_examples 'pipeline minutes attributes'
include_examples 'trial status widget data'
it 'returns correct usage quotes path', :use_clean_rails_memory_store_caching do
expect(subject[:pipeline_minutes]).to include({
......@@ -77,6 +146,7 @@
end
let_it_be(:group) { build(:group) }
let_it_be(:namespace) { group }
let_it_be(:project) { nil }
let(:subject) do
......@@ -84,6 +154,7 @@
end
include_examples 'pipeline minutes attributes'
include_examples 'trial status widget data'
it 'returns correct usage quotes path', :use_clean_rails_memory_store_caching do
expect(subject[:pipeline_minutes]).to include({
......
......@@ -3,6 +3,10 @@
require 'spec_helper'
RSpec.describe Sidebars::Projects::Menus::TrialWidgetMenu, :saas, feature_category: :experimentation_conversion do
before do
stub_feature_flags(super_sidebar_nav: true)
end
it_behaves_like 'trial widget menu items' do
let(:context) do
container = instance_double(Project, namespace: group)
......
......@@ -21,6 +21,13 @@ import { sidebarData } from '../mock_data';
jest.mock('~/super_sidebar/super_sidebar_collapsed_state_manager');
const focusInputMock = jest.fn();
const trialStatusWidgetStubTestId = 'trial-status-widget';
const TrialStatusWidgetStub = { template: `<div data-testid="${trialStatusWidgetStubTestId}" />` };
const trialStatusPopoverStubTestId = 'trial-status-popover';
const TrialStatusPopoverStub = {
template: `<div data-testid="${trialStatusPopoverStubTestId}" />`,
};
describe('SuperSidebar component', () => {
let wrapper;
......@@ -29,24 +36,30 @@ describe('SuperSidebar component', () => {
const findUserBar = () => wrapper.findComponent(UserBar);
const findHelpCenter = () => wrapper.findComponent(HelpCenter);
const findSidebarPortalTarget = () => wrapper.findComponent(SidebarPortalTarget);
const findTrialStatusWidget = () => wrapper.findByTestId(trialStatusWidgetStubTestId);
const findTrialStatusPopover = () => wrapper.findByTestId(trialStatusPopoverStubTestId);
const createWrapper = ({ props = {}, provide = {}, sidebarState = {} } = {}) => {
const createWrapper = ({ provide = {}, sidebarState = {} } = {}) => {
wrapper = shallowMountExtended(SuperSidebar, {
data() {
return {
...sidebarState,
};
},
provide: {
showTrialStatusWidget: false,
...provide,
},
propsData: {
sidebarData,
...props,
},
stubs: {
ContextSwitcher: stubComponent(ContextSwitcher, {
methods: { focusInput: focusInputMock },
}),
TrialStatusWidget: TrialStatusWidgetStub,
TrialStatusPopover: TrialStatusPopoverStub,
},
provide,
});
};
......@@ -113,6 +126,13 @@ describe('SuperSidebar component', () => {
expect(Mousetrap.unbind).toHaveBeenCalledWith(['mod+\\']);
});
it('does not render trial status widget', () => {
createWrapper();
expect(findTrialStatusWidget().exists()).toBe(false);
expect(findTrialStatusPopover().exists()).toBe(false);
});
});
describe('when peeking on hover', () => {
......@@ -213,4 +233,15 @@ describe('SuperSidebar component', () => {
expect(focusInputMock).toHaveBeenCalledTimes(1);
});
});
describe('when a trial is active', () => {
beforeEach(() => {
createWrapper({ provide: { showTrialStatusWidget: true } });
});
it('renders trial status widget', () => {
expect(findTrialStatusWidget().exists()).toBe(true);
expect(findTrialStatusPopover().exists()).toBe(true);
});
});
});
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册