Commit f6392723 authored by Sheldon Led's avatar Sheldon Led Committed by Mayra Cabrera

Initial PipelineUsageApp with Buy More Minutes button

This creates the Vue app and add the button to Buy Additional Minutes
parent 056adee2
...@@ -232,35 +232,40 @@ export const trackTransaction = (transactionDetails) => { ...@@ -232,35 +232,40 @@ export const trackTransaction = (transactionDetails) => {
pushEnhancedEcommerceEvent('EECtransactionSuccess', eventData); pushEnhancedEcommerceEvent('EECtransactionSuccess', eventData);
}; };
export const trackAddToCartUsageTab = () => { export const pushEECproductAddToCartEvent = () => {
if (!isSupported()) { if (!isSupported()) {
return; return;
} }
const getStartedButton = document.querySelector('.js-buy-additional-minutes'); window.dataLayer.push({
getStartedButton.addEventListener('click', () => { event: 'EECproductAddToCart',
window.dataLayer.push({ ecommerce: {
event: 'EECproductAddToCart', currencyCode: 'USD',
ecommerce: { add: {
currencyCode: 'USD', products: [
add: { {
products: [ name: 'CI/CD Minutes',
{ id: '0003',
name: 'CI/CD Minutes', price: '10',
id: '0003', brand: 'GitLab',
price: '10', category: 'DevOps',
brand: 'GitLab', variant: 'add-on',
category: 'DevOps', quantity: 1,
variant: 'add-on', },
quantity: 1, ],
},
],
},
}, },
}); },
}); });
}; };
export const trackAddToCartUsageTab = () => {
const getStartedButton = document.querySelector('.js-buy-additional-minutes');
if (!getStartedButton) {
return;
}
getStartedButton.addEventListener('click', pushEECproductAddToCartEvent);
};
export const trackCombinedGroupProjectForm = () => { export const trackCombinedGroupProjectForm = () => {
if (!isSupported()) { if (!isSupported()) {
return; return;
......
...@@ -88,6 +88,15 @@ module NamespacesHelper ...@@ -88,6 +88,15 @@ module NamespacesHelper
}.to_json }.to_json
end end
def pipeline_usage_quota_app_data(namespace)
{
namespace_actual_plan_name: namespace.actual_plan_name,
namespace_path: namespace.full_path,
namespace_id: namespace.id,
page_size: page_size
}
end
private private
# Many importers create a temporary Group, so use the real # Many importers create a temporary Group, so use the real
......
<script>
import { GlButton } from '@gitlab/ui';
import { pushEECproductAddToCartEvent } from '~/google_tag_manager';
import { LABEL_BUY_ADDITIONAL_MINUTES } from '../constants';
export default {
name: 'PipelineUsageApp',
components: { GlButton },
inject: ['namespaceActualPlanName', 'buyAdditionalMinutesPath', 'buyAdditionalMinutesTarget'],
methods: {
trackBuyAdditionalMinutesClick() {
pushEECproductAddToCartEvent();
},
},
LABEL_BUY_ADDITIONAL_MINUTES,
};
</script>
<template>
<div>
<div
v-if="buyAdditionalMinutesPath && buyAdditionalMinutesTarget"
class="gl-display-flex gl-justify-content-end"
>
<gl-button
:href="buyAdditionalMinutesPath"
:target="buyAdditionalMinutesTarget"
:data-track-label="namespaceActualPlanName"
data-track-action="click_buy_ci_minutes"
data-track-property="pipeline_quota_page"
data-testid="buy-additional-minutes-button"
category="primary"
variant="confirm"
@click="trackBuyAdditionalMinutesClick"
>
{{ $options.LABEL_BUY_ADDITIONAL_MINUTES }}
</gl-button>
</div>
</div>
</template>
import { s__ } from '~/locale';
export const LABEL_BUY_ADDITIONAL_MINUTES = s__('UsageQuota|Buy additional minutes');
import Vue from 'vue';
import PipelineUsageApp from './components/app.vue';
export default () => {
const el = document.getElementById('js-pipeline-usage-app');
if (!el) {
return false;
}
const {
namespaceActualPlanName,
buyAdditionalMinutesPath,
buyAdditionalMinutesTarget,
} = el.dataset;
return new Vue({
el,
name: 'PipelinesUsageView',
provide: {
namespaceActualPlanName,
buyAdditionalMinutesPath,
buyAdditionalMinutesTarget,
},
render(createElement) {
return createElement(PipelineUsageApp);
},
});
};
...@@ -67,6 +67,16 @@ module EE ...@@ -67,6 +67,16 @@ module EE
namespace.root_ancestor.free_plan? && !minute_limit_banner_dismissed? namespace.root_ancestor.free_plan? && !minute_limit_banner_dismissed?
end end
override :pipeline_usage_quota_app_data
def pipeline_usage_quota_app_data(namespace)
return super unless ::Gitlab::CurrentSettings.should_check_namespace_plan?
super.merge(
buy_additional_minutes_path: buy_additional_minutes_path(namespace),
buy_additional_minutes_target: buy_addon_target_attr(namespace)
)
end
private private
def use_customers_dot_for_addon_path?(namespace) def use_customers_dot_for_addon_path?(namespace)
......
import { GlButton } from '@gitlab/ui';
import { pushEECproductAddToCartEvent } from '~/google_tag_manager';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import PipelineUsageApp from 'ee/usage_quotas/pipelines/components/app.vue';
import { LABEL_BUY_ADDITIONAL_MINUTES } from 'ee/usage_quotas/pipelines/constants';
import { defaultProvide } from '../mock_data';
jest.mock('~/google_tag_manager');
describe('PipelineUsageApp', () => {
let wrapper;
const findBuyAdditionalMinutesButton = () =>
wrapper.findByTestId('buy-additional-minutes-button');
const createComponent = ({ provide = {} } = {}) => {
wrapper = shallowMountExtended(PipelineUsageApp, {
provide: {
...defaultProvide,
...provide,
},
stubs: {
GlButton,
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('Buy additional minutes Button', () => {
it('calls pushEECproductAddToCartEvent on click', async () => {
findBuyAdditionalMinutesButton().trigger('click');
expect(pushEECproductAddToCartEvent).toHaveBeenCalledTimes(1);
});
describe('Gitlab SaaS: valid data for buyAdditionalMinutesPath and buyAdditionalMinutesTarget', () => {
it('renders the button to buy additional minutes', () => {
expect(findBuyAdditionalMinutesButton().exists()).toBe(true);
expect(findBuyAdditionalMinutesButton().text()).toBe(LABEL_BUY_ADDITIONAL_MINUTES);
});
});
describe('Gitlab Self-Managed: buyAdditionalMinutesPath and buyAdditionalMinutesTarget not provided', () => {
beforeEach(() => {
createComponent({
provide: {
buyAdditionalMinutesPath: undefined,
buyAdditionalMinutesTarget: undefined,
},
});
});
it('does not render the button to buy additional minutes', () => {
expect(findBuyAdditionalMinutesButton().exists()).toBe(false);
});
});
});
});
import { TEST_HOST } from 'helpers/test_constants';
export const defaultProvide = {
namespaceActualPlanName: 'MyGroup',
buyAdditionalMinutesPath: `${TEST_HOST}/-/subscriptions/buy_minutes?selected_group=12345`,
buyAdditionalMinutesTarget: '_self',
};
...@@ -264,4 +264,34 @@ RSpec.describe EE::NamespacesHelper do ...@@ -264,4 +264,34 @@ RSpec.describe EE::NamespacesHelper do
end end
end end
end end
describe '#pipeline_usage_quota_app_data' do
context 'Gitlab SaaS', :saas do
before do
stub_ee_application_setting(should_check_namespace_plan: true)
end
it 'returns a hash with buy_additional_minutes data' do
expect(helper.pipeline_usage_quota_app_data(user_group)).to eql({
namespace_actual_plan_name: user_group.actual_plan_name,
namespace_path: user_group.full_path,
namespace_id: user_group.id,
page_size: Kaminari.config.default_per_page,
buy_additional_minutes_path: EE::SUBSCRIPTIONS_MORE_MINUTES_URL,
buy_additional_minutes_target: '_blank'
})
end
end
context 'Gitlab Self-Managed' do
it 'returns a hash without buy_additional_minutes data' do
expect(helper.pipeline_usage_quota_app_data(user_group)).to eql({
namespace_actual_plan_name: user_group.actual_plan_name,
namespace_path: user_group.full_path,
namespace_id: user_group.id,
page_size: Kaminari.config.default_per_page
})
end
end
end
end end
...@@ -268,4 +268,15 @@ RSpec.describe NamespacesHelper do ...@@ -268,4 +268,15 @@ RSpec.describe NamespacesHelper do
end end
end end
end end
describe '#pipeline_usage_quota_app_data' do
it 'returns a hash with necessary data for the frontend' do
expect(helper.pipeline_usage_quota_app_data(user_group)).to eql({
namespace_actual_plan_name: user_group.actual_plan_name,
namespace_path: user_group.full_path,
namespace_id: user_group.id,
page_size: Kaminari.config.default_per_page
})
end
end
end end
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