Commit 59986b12 authored by Dallas Reedy's avatar Dallas Reedy Committed by Andrew Fontaine

Create a constants.js file for the paid feature callout components

- Move the shared timeout duration there
- Move all translations there
- Move the shared screen size used for toggling between using a tooltip
  & showing the popover
parent 628a2020
...@@ -2,11 +2,17 @@ ...@@ -2,11 +2,17 @@
import { GlBadge, GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { GlBadge, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { __, sprintf } from '~/locale'; import { sprintf } from '~/locale';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import {
BADGE,
EXPERIMENT_KEY,
POPOVER_OR_TOOLTIP_BREAKPOINT,
RESIZE_EVENT_DEBOUNCE_MS,
} from '../constants';
const RESIZE_EVENT_DEBOUNCE_MS = 150; const { i18n, trackingEvents } = BADGE;
const trackingMixin = Tracking.mixin({ experiment: 'highlight_paid_features_during_active_trial' }); const trackingMixin = Tracking.mixin({ experiment: EXPERIMENT_KEY });
export default { export default {
components: { components: {
...@@ -24,20 +30,13 @@ export default { ...@@ -24,20 +30,13 @@ export default {
default: '', default: '',
}, },
}, },
i18n: {
title: {
generic: __('This feature is part of your GitLab Ultimate trial.'),
specific: __('The %{featureName} feature is part of your GitLab Ultimate trial.'),
},
},
trackingEvents: {
displayBadge: { action: 'display_badge', label: 'feature_highlight_badge' },
},
data() { data() {
return { return {
tooltipDisabled: false, tooltipDisabled: false,
}; };
}, },
i18n,
trackingEvents,
computed: { computed: {
title() { title() {
if (this.featureName === '') return this.$options.i18n.title.generic; if (this.featureName === '') return this.$options.i18n.title.generic;
...@@ -65,7 +64,7 @@ export default { ...@@ -65,7 +64,7 @@ export default {
this.track(action, options); this.track(action, options);
}, },
updateTooltipDisabledState() { updateTooltipDisabledState() {
this.tooltipDisabled = bp.getBreakpointSize() !== 'xs'; this.tooltipDisabled = bp.getBreakpointSize() !== POPOVER_OR_TOOLTIP_BREAKPOINT;
}, },
}, },
}; };
......
...@@ -2,12 +2,17 @@ ...@@ -2,12 +2,17 @@
import { GlButton, GlPopover } from '@gitlab/ui'; import { GlButton, GlPopover } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { __, n__, s__, sprintf } from '~/locale'; import { sprintf } from '~/locale';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import {
POPOVER,
EXPERIMENT_KEY,
POPOVER_OR_TOOLTIP_BREAKPOINT,
RESIZE_EVENT_DEBOUNCE_MS,
} from '../constants';
const RESIZE_EVENT_DEBOUNCE_MS = 150; const { i18n, trackingEvents } = POPOVER;
const CLICK_BUTTON = 'click_button'; const trackingMixin = Tracking.mixin({ experiment: EXPERIMENT_KEY });
const trackingMixin = Tracking.mixin({ experiment: 'highlight_paid_features_during_active_trial' });
export default { export default {
components: { components: {
...@@ -48,7 +53,7 @@ export default { ...@@ -48,7 +53,7 @@ export default {
promoImageAltText: { promoImageAltText: {
type: String, type: String,
required: false, required: false,
default: __('SVG illustration'), default: i18n.defaultImgAltText,
}, },
promoImagePath: { promoImagePath: {
type: String, type: String,
...@@ -65,44 +70,30 @@ export default { ...@@ -65,44 +70,30 @@ export default {
disabled: false, disabled: false,
}; };
}, },
i18n: { i18n,
compareAllButtonTitle: s__('BillingPlans|Compare all plans'), trackingEvents,
},
trackingEvents: {
popoverShown: { action: 'popover_shown', label: 'feature_highlight_popover' },
upgradeBtnClick: { action: CLICK_BUTTON, label: 'upgrade_to_premium' },
compareBtnClick: { action: CLICK_BUTTON, label: 'compare_all_plans' },
},
computed: { computed: {
popoverTitle() { title() {
const i18nPopoverTitle = n__( return sprintf(this.$options.i18n.title.countableTranslator(this.daysRemaining), {
'FeatureHighlight|%{daysRemaining} day remaining to enjoy %{featureName}',
'FeatureHighlight|%{daysRemaining} days remaining to enjoy %{featureName}',
this.daysRemaining,
);
return sprintf(i18nPopoverTitle, {
daysRemaining: this.daysRemaining, daysRemaining: this.daysRemaining,
featureName: this.featureName, featureName: this.featureName,
}); });
}, },
popoverContent() { content() {
const i18nPopoverContent = s__(`FeatureHighlight|Enjoying your GitLab %{planNameForTrial} trial? To continue return sprintf(this.$options.i18n.content, {
using %{featureName} after your trial ends, upgrade to GitLab %{planNameForUpgrade}.`);
return sprintf(i18nPopoverContent, {
featureName: this.featureName, featureName: this.featureName,
planNameForTrial: this.planNameForTrial, planNameForTrial: this.planNameForTrial,
planNameForUpgrade: this.planNameForUpgrade, planNameForUpgrade: this.planNameForUpgrade,
}); });
}, },
upgradeButtonTitle() { upgradeButtonLabel() {
const i18nUpgradeButtonTitle = s__('BillingPlans|Upgrade to GitLab %{planNameForUpgrade}'); return sprintf(this.$options.i18n.buttons.upgrade, {
return sprintf(i18nUpgradeButtonTitle, {
planNameForUpgrade: this.planNameForUpgrade, planNameForUpgrade: this.planNameForUpgrade,
}); });
}, },
comparePlansButtonLabel() {
return this.$options.i18n.buttons.comparePlans;
},
}, },
created() { created() {
this.debouncedResize = debounce(() => this.onResize(), RESIZE_EVENT_DEBOUNCE_MS); this.debouncedResize = debounce(() => this.onResize(), RESIZE_EVENT_DEBOUNCE_MS);
...@@ -119,11 +110,11 @@ export default { ...@@ -119,11 +110,11 @@ export default {
this.updateDisabledState(); this.updateDisabledState();
}, },
updateDisabledState() { updateDisabledState() {
this.disabled = bp.getBreakpointSize() === 'xs'; this.disabled = bp.getBreakpointSize() === POPOVER_OR_TOOLTIP_BREAKPOINT;
}, },
onShown() { onShown() {
const { action, ...options } = this.$options.trackingEvents.popoverShown; const { action, ...options } = this.$options.trackingEvents.popoverShown;
this.track(action, options); this.track(action, { ...options, label: `${options.label}:${this.featureName}` });
}, },
onUpgradeBtnClick() { onUpgradeBtnClick() {
const { action, ...options } = this.$options.trackingEvents.upgradeBtnClick; const { action, ...options } = this.$options.trackingEvents.upgradeBtnClick;
...@@ -147,7 +138,7 @@ export default { ...@@ -147,7 +138,7 @@ export default {
:delay="{ hide: 400 }" :delay="{ hide: 400 }"
@shown="onShown" @shown="onShown"
> >
<template #title>{{ popoverTitle }}</template> <template #title>{{ title }}</template>
<div v-if="promoImagePath" class="gl-display-flex gl-justify-content-center gl-mt-n3 gl-mb-4"> <div v-if="promoImagePath" class="gl-display-flex gl-justify-content-center gl-mt-n3 gl-mb-4">
<img <img
...@@ -159,7 +150,7 @@ export default { ...@@ -159,7 +150,7 @@ export default {
/> />
</div> </div>
{{ popoverContent }} {{ content }}
<div class="gl-mt-5"> <div class="gl-mt-5">
<gl-button <gl-button
...@@ -173,7 +164,7 @@ export default { ...@@ -173,7 +164,7 @@ export default {
data-testid="upgradeBtn" data-testid="upgradeBtn"
@click="onUpgradeBtnClick" @click="onUpgradeBtnClick"
> >
<span class="gl-font-sm">{{ upgradeButtonTitle }}</span> <span class="gl-font-sm">{{ upgradeButtonLabel }}</span>
</gl-button> </gl-button>
<gl-button <gl-button
:href="hrefComparePlans" :href="hrefComparePlans"
...@@ -186,7 +177,7 @@ export default { ...@@ -186,7 +177,7 @@ export default {
data-testid="compareBtn" data-testid="compareBtn"
@click="onCompareBtnClick" @click="onCompareBtnClick"
> >
<span class="gl-font-sm">{{ $options.i18n.compareAllButtonTitle }}</span> <span class="gl-font-sm">{{ comparePlansButtonLabel }}</span>
</gl-button> </gl-button>
</div> </div>
</gl-popover> </gl-popover>
......
import { __, n__, s__ } from '~/locale';
const CLICK_BUTTON = 'click_button';
export const EXPERIMENT_KEY = 'highlight_paid_features_during_active_trial';
export const RESIZE_EVENT_DEBOUNCE_MS = 150;
export const POPOVER_OR_TOOLTIP_BREAKPOINT = 'xs';
export const BADGE = {
i18n: {
title: {
generic: __('This feature is part of your GitLab Ultimate trial.'),
specific: __('The %{featureName} feature is part of your GitLab Ultimate trial.'),
},
},
trackingEvents: {
displayBadge: { action: 'display_badge', label: 'feature_highlight_badge' },
},
};
export const POPOVER = {
i18n: {
buttons: {
comparePlans: s__('BillingPlans|Compare all plans'),
upgrade: s__('BillingPlans|Upgrade to GitLab %{planNameForUpgrade}'),
},
content: s__(`FeatureHighlight|Enjoying your GitLab %{planNameForTrial} trial? To continue
using %{featureName} after your trial ends, upgrade to GitLab %{planNameForUpgrade}.`),
defaultImgAltText: __('SVG illustration'),
title: {
countableTranslator(count) {
return n__(
'FeatureHighlight|%{daysRemaining} day remaining to enjoy %{featureName}',
'FeatureHighlight|%{daysRemaining} days remaining to enjoy %{featureName}',
count,
);
},
},
},
trackingEvents: {
popoverShown: { action: 'popover_shown', label: 'feature_highlight_popover' },
upgradeBtnClick: { action: CLICK_BUTTON, label: 'upgrade_to_ultimate' },
compareBtnClick: { action: CLICK_BUTTON, label: 'compare_all_plans' },
},
};
...@@ -3,11 +3,14 @@ import { GlBreakpointInstance } from '@gitlab/ui/dist/utils'; ...@@ -3,11 +3,14 @@ import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import PaidFeatureCalloutBadge from 'ee/paid_feature_callouts/components/paid_feature_callout_badge.vue'; import PaidFeatureCalloutBadge from 'ee/paid_feature_callouts/components/paid_feature_callout_badge.vue';
import { BADGE } from 'ee/paid_feature_callouts/constants';
import { mockTracking } from 'helpers/tracking_helper'; import { mockTracking } from 'helpers/tracking_helper';
import { sprintf } from '~/locale';
describe('PaidFeatureCalloutBadge component', () => { describe('PaidFeatureCalloutBadge component', () => {
let trackingSpy; let trackingSpy;
let wrapper; let wrapper;
const { i18n, trackingEvents } = BADGE;
const findGlBadge = () => wrapper.findComponent(GlBadge); const findGlBadge = () => wrapper.findComponent(GlBadge);
const findGlIcon = () => wrapper.findComponent(GlIcon); const findGlIcon = () => wrapper.findComponent(GlIcon);
...@@ -27,7 +30,7 @@ describe('PaidFeatureCalloutBadge component', () => { ...@@ -27,7 +30,7 @@ describe('PaidFeatureCalloutBadge component', () => {
it('sets attributes on the GlBadge component', () => { it('sets attributes on the GlBadge component', () => {
expect(findGlBadge().attributes()).toMatchObject({ expect(findGlBadge().attributes()).toMatchObject({
title: 'This feature is part of your GitLab Ultimate trial.', title: i18n.title.generic,
tabindex: '0', tabindex: '0',
size: 'sm', size: 'sm',
class: 'feature-highlight-badge', class: 'feature-highlight-badge',
...@@ -46,18 +49,15 @@ describe('PaidFeatureCalloutBadge component', () => { ...@@ -46,18 +49,15 @@ describe('PaidFeatureCalloutBadge component', () => {
describe('when no featureName is provided', () => { describe('when no featureName is provided', () => {
it('sets the title to a sensible default', () => { it('sets the title to a sensible default', () => {
wrapper = createComponent(); wrapper = createComponent();
expect(findGlBadge().attributes('title')).toBe( expect(findGlBadge().attributes('title')).toBe(i18n.title.generic);
'This feature is part of your GitLab Ultimate trial.',
);
}); });
}); });
describe('when an optional featureName is provided', () => { describe('when an optional featureName is provided', () => {
it('sets the title using the given feature name', () => { it('sets the title using the given feature name', () => {
wrapper = createComponent({ featureName: 'fantastical thing' }); const props = { featureName: 'fantastical thing' };
expect(findGlBadge().attributes('title')).toBe( wrapper = createComponent(props);
'The fantastical thing feature is part of your GitLab Ultimate trial.', expect(findGlBadge().attributes('title')).toBe(sprintf(i18n.title.specific, props));
);
}); });
}); });
}); });
...@@ -69,10 +69,12 @@ describe('PaidFeatureCalloutBadge component', () => { ...@@ -69,10 +69,12 @@ describe('PaidFeatureCalloutBadge component', () => {
}); });
it('tracks that the badge has been displayed when mounted', () => { it('tracks that the badge has been displayed when mounted', () => {
const { action, ...trackingOpts } = trackingEvents.displayBadge;
expect(trackingSpy).toHaveBeenCalledWith( expect(trackingSpy).toHaveBeenCalledWith(
undefined, undefined,
'display_badge', action,
expect.objectContaining({ label: 'feature_highlight_badge' }), expect.objectContaining(trackingOpts),
); );
}); });
}); });
......
import { GlPopover } from '@gitlab/ui'; import { GlPopover } from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils'; import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import { shallowMount } from '@vue/test-utils'; import { mount, shallowMount } from '@vue/test-utils';
import PaidFeatureCalloutPopover from 'ee/paid_feature_callouts/components/paid_feature_callout_popover.vue'; import PaidFeatureCalloutPopover from 'ee/paid_feature_callouts/components/paid_feature_callout_popover.vue';
import { POPOVER } from 'ee/paid_feature_callouts/constants';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { sprintf } from '~/locale';
const { i18n, trackingEvents } = POPOVER;
describe('PaidFeatureCalloutPopover', () => { describe('PaidFeatureCalloutPopover', () => {
let trackingSpy; let trackingSpy;
let wrapper; let wrapper;
const findGlPopover = () => wrapper.findComponent(GlPopover); const findGlPopover = () => wrapper.findComponent(GlPopover);
const findUpgradeBtn = () => wrapper.findByTestId('upgradeBtn');
const findCompareBtn = () => wrapper.findByTestId('compareBtn');
const defaultProps = { const defaultProps = {
daysRemaining: 12, daysRemaining: 12,
...@@ -22,9 +28,9 @@ describe('PaidFeatureCalloutPopover', () => { ...@@ -22,9 +28,9 @@ describe('PaidFeatureCalloutPopover', () => {
targetId: 'some-feature-callout-target', targetId: 'some-feature-callout-target',
}; };
const createComponent = (extraProps = {}) => { const createComponent = (extraProps = {}, mountFn = shallowMount) => {
return extendedWrapper( return extendedWrapper(
shallowMount(PaidFeatureCalloutPopover, { mountFn(PaidFeatureCalloutPopover, {
propsData: { propsData: {
...defaultProps, ...defaultProps,
...extraProps, ...extraProps,
...@@ -43,11 +49,19 @@ describe('PaidFeatureCalloutPopover', () => { ...@@ -43,11 +49,19 @@ describe('PaidFeatureCalloutPopover', () => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('interpolated strings', () => {
it('correctly interpolates them all', () => {
wrapper = createComponent({}, mount);
expect(wrapper.text()).not.toMatch(/%{\w+}/);
});
});
describe('GlPopover attributes', () => { describe('GlPopover attributes', () => {
const sharedAttrs = { const sharedAttrs = {
boundary: 'viewport', boundary: 'viewport',
placement: 'top', placement: 'top',
target: 'some-feature-callout-target', target: defaultProps.targetId,
}; };
describe('with some default props', () => { describe('with some default props', () => {
...@@ -73,21 +87,6 @@ describe('PaidFeatureCalloutPopover', () => { ...@@ -73,21 +87,6 @@ describe('PaidFeatureCalloutPopover', () => {
}); });
}); });
describe('popoverTitle', () => {
it('renders the title text', () => {
expect(wrapper.vm.popoverTitle).toEqual('12 days remaining to enjoy some feature');
});
});
describe('popoverContent', () => {
it('renders the content text', () => {
expect(wrapper.vm.popoverContent).toEqual(
'Enjoying your GitLab Awesomesauce trial? To continue using some feature after your trial ends, upgrade to ' +
'GitLab Amazing.',
);
});
});
describe('promo image', () => { describe('promo image', () => {
const promoImagePathForTest = 'path/to/some/image.svg'; const promoImagePathForTest = 'path/to/some/image.svg';
...@@ -103,21 +102,23 @@ describe('PaidFeatureCalloutPopover', () => { ...@@ -103,21 +102,23 @@ describe('PaidFeatureCalloutPopover', () => {
}); });
describe('with the optional promoImageAltText prop', () => { describe('with the optional promoImageAltText prop', () => {
const promoImageAltText = 'My fancy alt text';
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
promoImagePath: promoImagePathForTest, promoImagePath: promoImagePathForTest,
promoImageAltText: 'My fancy alt text', promoImageAltText,
}); });
}); });
it('renders the promo image with the given alt text', () => { it('renders the promo image with the given alt text', () => {
expect(findPromoImage().attributes('alt')).toBe('My fancy alt text'); expect(findPromoImage().attributes('alt')).toBe(promoImageAltText);
}); });
}); });
describe('without the optional promoImageAltText prop', () => { describe('without the optional promoImageAltText prop', () => {
it('renders the promo image with default alt text', () => { it('renders the promo image with default alt text', () => {
expect(findPromoImage().attributes('alt')).toBe('SVG illustration'); expect(findPromoImage().attributes('alt')).toBe(i18n.defaultImgAltText);
}); });
}); });
}); });
...@@ -129,6 +130,51 @@ describe('PaidFeatureCalloutPopover', () => { ...@@ -129,6 +130,51 @@ describe('PaidFeatureCalloutPopover', () => {
}); });
}); });
describe('title', () => {
const expectTitleToMatch = (daysRemaining) => {
expect(wrapper.text()).toContain(
sprintf(i18n.title.countableTranslator(daysRemaining), {
daysRemaining,
featureName: defaultProps.featureName,
}),
);
};
describe('singularized form', () => {
it('renders the title text with "1 day"', () => {
wrapper = createComponent({ daysRemaining: 1 }, mount);
expectTitleToMatch(1);
});
});
describe('pluralized form', () => {
it('renders the title text with "5 days"', () => {
wrapper = createComponent({ daysRemaining: 5 }, mount);
expectTitleToMatch(5);
});
it('renders the title text with "0 days"', () => {
wrapper = createComponent({ daysRemaining: 0 }, mount);
expectTitleToMatch(0);
});
});
});
describe('content', () => {
it('renders the content text', () => {
expect(findGlPopover().text()).toMatch(
sprintf(i18n.content, {
featureName: defaultProps.featureName,
planNameForTrial: defaultProps.planNameForTrial,
planNameForUpgrade: defaultProps.planNameForUpgrade,
}),
);
});
});
describe('call-to-action buttons', () => { describe('call-to-action buttons', () => {
const sharedAttrs = { const sharedAttrs = {
target: '_blank', target: '_blank',
...@@ -138,51 +184,51 @@ describe('PaidFeatureCalloutPopover', () => { ...@@ -138,51 +184,51 @@ describe('PaidFeatureCalloutPopover', () => {
}; };
describe('upgrade plan button', () => { describe('upgrade plan button', () => {
const findUpgradeBtn = () => wrapper.findByTestId('upgradeBtn');
it('correctly renders an Upgrade button', () => { it('correctly renders an Upgrade button', () => {
const upgradeBtn = findUpgradeBtn(); const upgradeBtn = findUpgradeBtn();
expect(upgradeBtn.text()).toEqual('Upgrade to GitLab Amazing'); expect(upgradeBtn.text()).toEqual(
sprintf(i18n.buttons.upgrade, { planNameForUpgrade: defaultProps.planNameForUpgrade }),
);
expect(upgradeBtn.attributes()).toMatchObject({ expect(upgradeBtn.attributes()).toMatchObject({
...sharedAttrs, ...sharedAttrs,
href: '/-/subscriptions/new?namespace_id=123&plan_id=abc456', href: defaultProps.hrefUpgradeToPaid,
category: 'primary', category: 'primary',
}); });
}); });
it('tracks on click', () => { it('tracks on click', () => {
const { action, ...trackingOpts } = trackingEvents.upgradeBtnClick;
findUpgradeBtn().vm.$emit('click'); findUpgradeBtn().vm.$emit('click');
expect(trackingSpy).toHaveBeenCalledWith( expect(trackingSpy).toHaveBeenCalledWith(
undefined, undefined,
'click_button', action,
expect.objectContaining({ label: 'upgrade_to_premium' }), expect.objectContaining(trackingOpts),
); );
}); });
}); });
describe('compare plans button', () => { describe('compare plans button', () => {
const findCompareBtn = () => wrapper.findByTestId('compareBtn');
it('correctly renders a Compare button', () => { it('correctly renders a Compare button', () => {
const compareBtn = findCompareBtn(); const compareBtn = findCompareBtn();
expect(compareBtn.text()).toEqual('Compare all plans'); expect(compareBtn.text()).toEqual(i18n.buttons.comparePlans);
expect(compareBtn.attributes()).toMatchObject({ expect(compareBtn.attributes()).toMatchObject({
...sharedAttrs, ...sharedAttrs,
href: '/group/test-group/-/billings', href: defaultProps.hrefComparePlans,
category: 'secondary', category: 'secondary',
}); });
}); });
it('tracks on click', () => { it('tracks on click', () => {
const { action, ...trackingOpts } = trackingEvents.compareBtnClick;
findCompareBtn().vm.$emit('click'); findCompareBtn().vm.$emit('click');
expect(trackingSpy).toHaveBeenCalledWith( expect(trackingSpy).toHaveBeenCalledWith(
undefined, undefined,
'click_button', action,
expect.objectContaining({ label: 'compare_all_plans' }), expect.objectContaining(trackingOpts),
); );
}); });
}); });
...@@ -190,12 +236,15 @@ describe('PaidFeatureCalloutPopover', () => { ...@@ -190,12 +236,15 @@ describe('PaidFeatureCalloutPopover', () => {
describe('onShown', () => { describe('onShown', () => {
it('tracks that the popover has been shown', () => { it('tracks that the popover has been shown', () => {
const { action, label } = trackingEvents.popoverShown;
findGlPopover().vm.$emit('shown'); findGlPopover().vm.$emit('shown');
expect(trackingSpy).toHaveBeenCalledWith( expect(trackingSpy).toHaveBeenCalledWith(
undefined, undefined,
'popover_shown', action,
expect.objectContaining({ label: 'feature_highlight_popover' }), expect.objectContaining({
label: `${label}:${defaultProps.featureName}`,
}),
); );
}); });
}); });
......
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