Skip to content
代码片段 群组 项目
未验证 提交 df3106f8 编辑于 作者: Brandon Labuschagne's avatar Brandon Labuschagne 提交者: GitLab
浏览文件

Merge branch '454592-jmontal-update-usage-quotas-duo-pro-card-for-trials' into 'master'

No related branches found
No related tags found
无相关合并请求
......@@ -18,7 +18,8 @@ import {
DUO_ENTERPRISE_TITLE,
} from 'ee/usage_quotas/code_suggestions/constants';
import { addSeatsText } from 'ee/usage_quotas/seats/constants';
import Tracking from '~/tracking';
import HandRaiseLeadButton from 'ee/hand_raise_leads/hand_raise_lead/components/hand_raise_lead_button.vue';
import { InternalEvents } from '~/tracking';
import { getSubscriptionPermissionsData } from 'ee/fulfillment/shared_queries/subscription_actions_reason.customer.query.graphql';
import LimitedAccessModal from 'ee/usage_quotas/components/limited_access_modal.vue';
import { visitUrl } from '~/lib/utils/url_utility';
......@@ -26,6 +27,7 @@ import { LIMITED_ACCESS_KEYS } from 'ee/usage_quotas/components/constants';
import { ADD_ON_PURCHASE_FETCH_ERROR_CODE } from 'ee/usage_quotas/error_constants';
import getGitlabSubscriptionQuery from 'ee/fulfillment/shared_queries/gitlab_subscription.query.graphql';
import { localeDateFormat } from '~/lib/utils/datetime_utility';
import { PQL_MODAL_ID } from 'ee/hand_raise_leads/hand_raise_lead/constants';
export default {
name: 'CodeSuggestionsUsageInfoCard',
......@@ -40,7 +42,11 @@ export default {
addSeatsText,
subscriptionStartDate: __('Subscription start date'),
subscriptionEndDate: __('Subscription end date'),
trialStartDate: __('Trial start date'),
trialEndDate: __('Trial end date'),
notAvailable: __('Not available'),
purchaseSeats: __('Purchase seats'),
trial: s__('CodeSuggestions|trial'),
},
components: {
GlButton,
......@@ -50,17 +56,20 @@ export default {
UsageStatistics,
GlSkeletonLoader,
LimitedAccessModal,
HandRaiseLeadButton,
},
directives: {
GlModalDirective,
},
mixins: [Tracking.mixin()],
mixins: [InternalEvents.mixin()],
inject: [
'addDuoProHref',
'isSaaS',
'subscriptionName',
'subscriptionStartDate',
'subscriptionEndDate',
'duoProActiveTrialStartDate',
'duoProActiveTrialEndDate',
],
props: {
groupId: {
......@@ -109,16 +118,42 @@ export default {
return this.subscriptionPermissions?.reason;
},
duoTitle() {
return this.duoTier === DUO_ENTERPRISE ? DUO_ENTERPRISE_TITLE : CODE_SUGGESTIONS_TITLE;
const title = this.duoTier === DUO_ENTERPRISE ? DUO_ENTERPRISE_TITLE : CODE_SUGGESTIONS_TITLE;
return `${title} ${this.duoProActiveTrial ? this.$options.i18n.trial : ''}`;
},
subscriptionStartDateText() {
return this.duoProActiveTrial
? this.$options.i18n.trialStartDate
: this.$options.i18n.subscriptionStartDate;
},
subscriptionEndDateText() {
return this.duoProActiveTrial
? this.$options.i18n.trialEndDate
: this.$options.i18n.subscriptionEndDate;
},
startDate() {
if (this.duoProActiveTrial) {
return this.formattedDate(this.duoProActiveTrialStartDate);
}
const date = this.subscription?.startDate || this.subscriptionStartDate;
return date ? this.formattedDate(date) : this.$options.i18n.notAvailable;
},
endDate() {
if (this.duoProActiveTrial) {
return this.formattedDate(this.duoProActiveTrialEndDate);
}
const date = this.subscription?.endDate || this.subscriptionEndDate;
return date ? this.formattedDate(date) : this.$options.i18n.notAvailable;
},
duoProActiveTrial() {
return Boolean(this.duoProActiveTrialStartDate);
},
pageViewLabel() {
return this.duoProActiveTrial ? `duo_pro_add_on_tab_active_trial` : `duo_pro_add_on_tab`;
},
},
apollo: {
subscription: {
......@@ -161,9 +196,18 @@ export default {
},
},
},
mounted() {
this.trackEvent(
'pageview',
{
label: this.pageViewLabel,
},
'groups:usage_quotas:index',
);
},
methods: {
handleAddDuoProClick() {
this.track('click_button', {
this.trackEvent('click_button', {
label: `add_duo_pro_${this.trackingPreffix}`,
property: 'usage_quotas_page',
});
......@@ -177,11 +221,44 @@ export default {
this.handleAddDuoProClick();
visitUrl(this.addDuoProHref);
},
handlePurchaseSeats() {
this.trackEvent(
'click_button',
{
label: `duo_pro_purchase_seats`,
},
'groups:usage_quotas:index',
);
visitUrl(this.addDuoProHref);
},
handleCodeSuggestionsLink() {
this.trackEvent(
'click_link',
{
label: `duo_pro_marketing_page`,
},
'groups:usage_quotas:index',
);
visitUrl(this.$options.helpLinks.codeSuggestionsLearnMoreLink);
},
formattedDate(date) {
const [year, month, day] = date.split('-');
return localeDateFormat.asDate.format(new Date(year, month - 1, day));
},
},
handRaiseLeadAttributes: {
size: 'small',
variant: 'confirm',
category: 'secondary',
},
handRaiseLeadBtnTracking: {
category: 'groups:usage_quotas:index',
action: 'click_button',
label: 'duo_pro_contact_sales',
},
modalId: PQL_MODAL_ID,
};
</script>
<template>
......@@ -201,46 +278,66 @@ export default {
<p class="gl-mt-5" data-testid="description">
<gl-sprintf :message="$options.i18n.description">
<template #link="{ content }">
<gl-link :href="$options.helpLinks.codeSuggestionsLearnMoreLink" target="_blank">{{
content
}}</gl-link>
<gl-link
target="_blank"
data-testid="usage-quotas-gitlab-duo-tab-code-suggestions-link"
@click="handleCodeSuggestionsLink"
>{{ content }}</gl-link
>
</template>
</gl-sprintf>
</p>
<div data-testid="subscription-info">
<div class="gl-flex gl-gap-3">
<span class="gl-basis-1/3 gl-font-bold gl-min-w-20">{{
$options.i18n.subscriptionStartDate
subscriptionStartDateText
}}</span>
<span>{{ startDate }}</span>
</div>
<div class="gl-flex gl-mt-2 gl-gap-3">
<span class="gl-basis-1/3 gl-font-bold gl-min-w-20">{{
$options.i18n.subscriptionEndDate
}}</span>
<span class="gl-basis-1/3 gl-font-bold gl-min-w-20">{{ subscriptionEndDateText }}</span>
<span>{{ endDate }}</span>
</div>
</div>
</template>
<template #actions>
<gl-button
v-if="shouldShowAddSeatsButton"
v-gl-modal-directive="'limited-access-modal-id'"
category="primary"
target="_blank"
variant="confirm"
size="small"
class="gl-ml-3 gl-align-self-start"
data-testid="purchase-button"
@click="handleAddSeats"
>
{{ $options.i18n.addSeatsText }}
</gl-button>
<limited-access-modal
v-if="shouldShowModal"
v-model="showLimitedAccessModal"
:limited-access-reason="permissionReason"
/>
<div v-if="duoProActiveTrial">
<gl-button
variant="confirm"
size="small"
data-testid="usage-quotas-gitlab-duo-tab-active-trial-purchase-seats-button"
@click="handlePurchaseSeats"
>
{{ $options.i18n.purchaseSeats }}
</gl-button>
<hand-raise-lead-button
:modal-id="$options.modalId"
:button-attributes="$options.handRaiseLeadAttributes"
:cta-tracking="$options.handRaiseLeadBtnTracking"
glm-content="usage-quotas-gitlab-duo-tab"
/>
</div>
<div v-else>
<gl-button
v-if="shouldShowAddSeatsButton"
v-gl-modal-directive="'limited-access-modal-id'"
category="primary"
target="_blank"
variant="confirm"
size="small"
class="gl-ml-3 gl-self-start"
data-testid="purchase-button"
@click="handleAddSeats"
>
{{ $options.i18n.addSeatsText }}
</gl-button>
<limited-access-modal
v-if="shouldShowModal"
v-model="showLimitedAccessModal"
:limited-access-reason="permissionReason"
/>
</div>
</template>
</usage-statistics>
</gl-card>
......
......@@ -12,6 +12,8 @@ export const parseProvideData = (el) => {
addDuoProHref,
duoProBulkUserAssignmentAvailable,
isStandalonePage,
duoProActiveTrialStartDate,
duoProActiveTrialEndDate,
subscriptionName,
subscriptionStartDate,
subscriptionEndDate,
......@@ -40,6 +42,8 @@ export const parseProvideData = (el) => {
fullPath,
groupId,
duoProTrialHref,
duoProActiveTrialStartDate,
duoProActiveTrialEndDate,
addDuoProHref,
isSaaS: true,
isStandalonePage: parseBoolean(isStandalonePage),
......
......@@ -116,7 +116,18 @@ def code_suggestions_usage_app_data(group)
add_duo_pro_href: duo_pro_url(group),
duo_pro_bulk_user_assignment_available: duo_pro_bulk_user_assignment_available?(group).to_s,
hand_raise_lead: code_suggestions_usage_app_hand_raise_lead_data
}.merge(duo_pro_trial_link(group))
}.merge(duo_pro_trial_link(group), active_duo_pro_trial_data(group))
end
def active_duo_pro_trial_data(group)
active_duo_pro_trial_add_on = group.subscription_add_on_purchases.for_gitlab_duo_pro.active.trial.first
return {} unless active_duo_pro_trial_add_on
{
duo_pro_active_trial_start_date: active_duo_pro_trial_add_on.started_at,
duo_pro_active_trial_end_date: active_duo_pro_trial_add_on.expires_on
}
end
def duo_pro_trial_link(group)
......
import { GlLink, GlSprintf, GlButton, GlSkeletonLoader } from '@gitlab/ui';
import { GlSprintf, GlButton, GlSkeletonLoader } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import Vue, { nextTick } from 'vue';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import Tracking from '~/tracking';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { PROMO_URL, visitUrl } from 'jh_else_ce/lib/utils/url_utility';
import { visitUrl, PROMO_URL } from 'jh_else_ce/lib/utils/url_utility';
import CodeSuggestionsInfoCard from 'ee/usage_quotas/code_suggestions/components/code_suggestions_info_card.vue';
import { getSubscriptionPermissionsData } from 'ee/fulfillment/shared_queries/subscription_actions_reason.customer.query.graphql';
import { createMockClient } from 'helpers/mock_apollo_helper';
......@@ -13,6 +13,7 @@ import LimitedAccessModal from 'ee/usage_quotas/components/limited_access_modal.
import { ADD_ON_PURCHASE_FETCH_ERROR_CODE } from 'ee/usage_quotas/error_constants';
import getGitlabSubscriptionQuery from 'ee/fulfillment/shared_queries/gitlab_subscription.query.graphql';
import { getMockSubscriptionData } from 'ee_jest/usage_quotas/seats/mock_data';
import HandRaiseLeadButton from 'ee/hand_raise_leads/hand_raise_lead/components/hand_raise_lead_button.vue';
Vue.use(VueApollo);
......@@ -28,6 +29,8 @@ const defaultProvide = {
subscriptionName: null,
subscriptionStartDate: '2023-03-16',
subscriptionEndDate: '2024-03-16',
duoProActiveTrialStartDate: null,
duoProActiveTrialEndDate: null,
};
describe('CodeSuggestionsInfoCard', () => {
......@@ -50,10 +53,14 @@ describe('CodeSuggestionsInfoCard', () => {
const findCodeSuggestionsDescription = () => wrapper.findByTestId('description');
const findCodeSuggestionsSubscriptionInfo = () => wrapper.findByTestId('subscription-info');
const findCodeSuggestionsLearnMoreLink = () => wrapper.findComponent(GlLink);
const findCodeSuggestionsLearnMoreLink = () =>
wrapper.findByTestId('usage-quotas-gitlab-duo-tab-code-suggestions-link');
const findCodeSuggestionsInfoTitle = () => wrapper.findByTestId('title');
const findAddSeatsButton = () => wrapper.findComponent(GlButton);
const findLimitedAccessModal = () => wrapper.findComponent(LimitedAccessModal);
const findPurchaseSeatsButton = () =>
wrapper.findByTestId('usage-quotas-gitlab-duo-tab-active-trial-purchase-seats-button');
const findContactSalesButton = () => wrapper.findComponent(HandRaiseLeadButton);
const createMockApolloProvider = ({ subscriptionData }) => {
const mockCustomersDotClient = createMockClient([
......@@ -126,6 +133,8 @@ describe('CodeSuggestionsInfoCard', () => {
describe('with Duo Pro add-on enabled', () => {
beforeEach(async () => {
jest.spyOn(Tracking, 'event');
createComponent({ props: { duoTier: 'pro' } });
// wait for apollo to load
......@@ -135,6 +144,16 @@ describe('CodeSuggestionsInfoCard', () => {
it('renders the title text', () => {
expect(findCodeSuggestionsInfoTitle().text()).toBe('GitLab Duo Pro');
});
it('tracks the pageview correctly', () => {
expect(Tracking.event).toHaveBeenCalledWith(
'groups:usage_quotas:index',
'pageview',
expect.objectContaining({
label: 'duo_pro_add_on_tab',
}),
);
});
});
describe('with Duo Enterprise add-on enabled', () => {
......@@ -156,12 +175,6 @@ describe('CodeSuggestionsInfoCard', () => {
);
});
it('renders the learn more link', () => {
expect(findCodeSuggestionsLearnMoreLink().attributes('href')).toBe(
`${PROMO_URL}/gitlab-duo/`,
);
});
describe('with subscription date info', () => {
const outputStartDate = 'Mar 16, 2023';
const outputEndDate = 'Mar 16, 2024';
......@@ -217,6 +230,87 @@ describe('CodeSuggestionsInfoCard', () => {
});
});
});
describe('with a Duo Pro add-on trial', () => {
const outputStartDate = 'Jan 1, 2024';
const outputEndDate = 'Feb 1, 2024';
beforeEach(async () => {
jest.spyOn(Tracking, 'event');
createComponent({
provide: {
duoProActiveTrialStartDate: '2024-01-01',
duoProActiveTrialEndDate: '2024-02-01',
},
});
await waitForPromises();
});
it('renders the title text', () => {
expect(findCodeSuggestionsInfoTitle().text()).toBe('GitLab Duo Pro trial');
});
it('renders the trial start date', () => {
expect(findCodeSuggestionsSubscriptionInfo().text()).toContain(outputStartDate);
});
it('renders the trial end date', () => {
expect(findCodeSuggestionsSubscriptionInfo().text()).toContain(outputEndDate);
});
it('tracks the pageview correctly', () => {
expect(Tracking.event).toHaveBeenCalledWith(
'groups:usage_quotas:index',
'pageview',
expect.objectContaining({
label: 'duo_pro_add_on_tab_active_trial',
}),
);
});
describe('buttons', () => {
it('sets to the correct props to the hand raise lead (contact sales) button', () => {
expect(findContactSalesButton().props()).toMatchObject({
glmContent: 'usage-quotas-gitlab-duo-tab',
ctaTracking: {
category: 'groups:usage_quotas:index',
action: 'click_button',
label: 'duo_pro_contact_sales',
},
});
});
it('visits the correct url and tracks the purchase seats button when clicked', () => {
findPurchaseSeatsButton().vm.$emit('click');
expect(Tracking.event).toHaveBeenCalledWith(
'groups:usage_quotas:index',
'click_button',
expect.objectContaining({
label: 'duo_pro_purchase_seats',
}),
);
expect(visitUrl).toHaveBeenCalledWith(defaultProvide.addDuoProHref);
});
it('visits the correct url and tracks the learn more link when clicked', () => {
findCodeSuggestionsLearnMoreLink().vm.$emit('click');
expect(Tracking.event).toHaveBeenCalledWith(
'groups:usage_quotas:index',
'click_link',
expect.objectContaining({
label: 'duo_pro_marketing_page',
}),
);
expect(visitUrl).toHaveBeenCalledWith(`${PROMO_URL}/gitlab-duo/`);
});
});
});
});
describe('add seats button', () => {
......
......@@ -478,6 +478,27 @@
end
end
describe '#active_duo_pro_trial_data' do
context 'when an active duo pro trial exists' do
let(:trial_add_on) { create(:gitlab_subscription_add_on_purchase, :gitlab_duo_pro, :trial, namespace: group) }
it 'returns the trial start date and end date' do
trial_add_on
expect(helper.active_duo_pro_trial_data(group)).to eq({
duo_pro_active_trial_start_date: trial_add_on.started_at,
duo_pro_active_trial_end_date: trial_add_on.expires_on
})
end
end
context 'when an active duo pro trial does not exist' do
it 'returns empty' do
expect(helper.active_duo_pro_trial_data(group)).to eq({})
end
end
end
describe '#duo_pro_trial_link' do
it 'returns the trial link when group has no previous or active duo pro trial' do
expect(helper.duo_pro_trial_link(group)).to eq({
......
......@@ -12849,6 +12849,9 @@ msgstr ""
msgid "CodeSuggestions|Manage seat assignments for %{addOnName} across your instance."
msgstr ""
 
msgid "CodeSuggestions|trial"
msgstr ""
msgid "CodeownersValidation|An error occurred while loading the validation errors. Please try again later."
msgstr ""
 
......@@ -56039,6 +56042,12 @@ msgstr ""
msgid "Trending"
msgstr ""
 
msgid "Trial end date"
msgstr ""
msgid "Trial start date"
msgstr ""
msgid "TrialDiscoverPage|A single application eliminates complex integrations, data checkpoints, and toolchain maintenance, resulting in greater productivity and lower cost."
msgstr ""
 
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册