Commit be3a67c0 authored by Savas Vedova's avatar Savas Vedova

Display call to action for jira issues

- Display a different messaged based on the user subscription.
- Add changelog
parent ff725188
<script> <script>
import { import { GlFormGroup, GlFormCheckbox, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui';
GlFormGroup,
GlFormCheckbox,
GlFormInput,
GlSprintf,
GlLink,
GlButton,
GlCard,
} from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import JiraUpgradeCta from './jira_upgrade_cta.vue';
export default { export default {
name: 'JiraIssuesFields', name: 'JiraIssuesFields',
...@@ -19,8 +12,7 @@ export default { ...@@ -19,8 +12,7 @@ export default {
GlFormInput, GlFormInput,
GlSprintf, GlSprintf,
GlLink, GlLink,
GlButton, JiraUpgradeCta,
GlCard,
JiraIssueCreationVulnerabilities: () => JiraIssueCreationVulnerabilities: () =>
import('ee_component/integrations/edit/components/jira_issue_creation_vulnerabilities.vue'), import('ee_component/integrations/edit/components/jira_issue_creation_vulnerabilities.vue'),
}, },
...@@ -84,11 +76,13 @@ export default { ...@@ -84,11 +76,13 @@ export default {
return !this.enableJiraIssues || Boolean(this.projectKey) || !this.validated; return !this.enableJiraIssues || Boolean(this.projectKey) || !this.validated;
}, },
showJiraVulnerabilitiesOptions() { showJiraVulnerabilitiesOptions() {
return ( return this.showJiraVulnerabilitiesIntegration && this.glFeatures.jiraForVulnerabilities;
this.enableJiraIssues && },
this.showJiraVulnerabilitiesIntegration && showUltimateUpgrade() {
this.glFeatures.jiraForVulnerabilities return this.showJiraIssuesIntegration && !this.showJiraVulnerabilitiesIntegration;
); },
showPremiumUpgrade() {
return !this.showJiraIssuesIntegration;
}, },
}, },
created() { created() {
...@@ -135,27 +129,23 @@ export default { ...@@ -135,27 +129,23 @@ export default {
</template> </template>
</gl-form-checkbox> </gl-form-checkbox>
<jira-issue-creation-vulnerabilities <jira-issue-creation-vulnerabilities
v-if="showJiraVulnerabilitiesOptions" v-if="enableJiraIssues"
:project-key="projectKey" :project-key="projectKey"
:initial-is-enabled="initialEnableJiraVulnerabilities" :initial-is-enabled="initialEnableJiraVulnerabilities"
:initial-issue-type-id="initialVulnerabilitiesIssuetype" :initial-issue-type-id="initialVulnerabilitiesIssuetype"
:show-full-feature="showJiraVulnerabilitiesOptions"
data-testid="jira-for-vulnerabilities" data-testid="jira-for-vulnerabilities"
@request-get-issue-types="getJiraIssueTypes" @request-get-issue-types="getJiraIssueTypes"
/> />
</template> </template>
<gl-card v-else class="gl-mt-7"> <jira-upgrade-cta
<strong>{{ __('This is a Premium feature') }}</strong> v-if="showUltimateUpgrade || showPremiumUpgrade"
<p>{{ __('Upgrade your plan to enable this feature of the Jira Integration.') }}</p> class="gl-mt-2"
<gl-button :class="{ 'gl-ml-6': showUltimateUpgrade }"
v-if="upgradePlanPath" :upgrade-plan-path="upgradePlanPath"
category="primary" :show-ultimate-message="showUltimateUpgrade"
variant="info" :show-premium-message="showPremiumUpgrade"
:href="upgradePlanPath" />
target="_blank"
>
{{ __('Upgrade your plan') }}
</gl-button>
</gl-card>
</div> </div>
</gl-form-group> </gl-form-group>
<template v-if="showJiraIssuesIntegration"> <template v-if="showJiraIssuesIntegration">
......
<script>
import { GlButton, GlCard } from '@gitlab/ui';
import { s__, __ } from '~/locale';
export default {
components: {
GlButton,
GlCard,
},
props: {
upgradePlanPath: {
type: String,
required: false,
default: '',
},
showPremiumMessage: {
type: Boolean,
required: false,
default: false,
},
showUltimateMessage: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
title() {
return this.showUltimateMessage
? this.$options.i18n.titleUltimate
: this.$options.i18n.titlePremium;
},
},
i18n: {
titleUltimate: s__('JiraService|This is an Ultimate feature'),
titlePremium: s__('JiraService|This is a Premium feature'),
content: s__('JiraService|Upgrade your plan to enable this feature of the Jira Integration.'),
upgrade: __('Upgrade your plan'),
},
};
</script>
<template>
<gl-card>
<strong>{{ title }}</strong>
<p>{{ $options.i18n.content }}</p>
<gl-button v-if="upgradePlanPath" category="primary" variant="info" :href="upgradePlanPath">
{{ $options.i18n.upgrade }}
</gl-button>
</gl-card>
</template>
...@@ -50,6 +50,11 @@ export default { ...@@ -50,6 +50,11 @@ export default {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
props: { props: {
showFullFeature: {
type: Boolean,
required: false,
default: true,
},
projectKey: { projectKey: {
type: String, type: String,
required: false, required: false,
...@@ -129,71 +134,74 @@ export default { ...@@ -129,71 +134,74 @@ export default {
<gl-form-checkbox <gl-form-checkbox
v-model="isJiraVulnerabilitiesEnabled" v-model="isJiraVulnerabilitiesEnabled"
data-testid="enable-jira-vulnerabilities" data-testid="enable-jira-vulnerabilities"
:disabled="!showFullFeature"
> >
{{ $options.i18n.checkbox.label }} {{ $options.i18n.checkbox.label }}
<template #help> <template #help>
{{ $options.i18n.checkbox.description }} {{ $options.i18n.checkbox.description }}
</template> </template>
</gl-form-checkbox> </gl-form-checkbox>
<input <template v-if="showFullFeature">
name="service[vulnerabilities_enabled]" <input
type="hidden" name="service[vulnerabilities_enabled]"
:value="isJiraVulnerabilitiesEnabled" type="hidden"
/> :value="isJiraVulnerabilitiesEnabled"
<gl-form-group />
v-if="isJiraVulnerabilitiesEnabled" <gl-form-group
:label="$options.i18n.issueTypeSelect.label" v-if="isJiraVulnerabilitiesEnabled"
class="gl-mt-4 gl-pl-1 gl-ml-5" :label="$options.i18n.issueTypeSelect.label"
data-testid="issue-type-section" class="gl-mt-4 gl-pl-1 gl-ml-5"
> data-testid="issue-type-section"
<p>{{ $options.i18n.issueTypeSelect.description }}</p>
<gl-alert
v-if="shouldShowLoadingErrorAlert"
class="gl-mb-5"
variant="danger"
:title="$options.i18n.fetchIssueTypesErrorMessage"
@dismiss="isLoadingErrorAlertDimissed = true"
> >
{{ loadingJiraIssueTypesErrorMessage }} <p>{{ $options.i18n.issueTypeSelect.description }}</p>
</gl-alert> <gl-alert
<div class="row gl-display-flex gl-align-items-center"> v-if="shouldShowLoadingErrorAlert"
<gl-button-group class="col-md-5 gl-mr-3"> class="gl-mb-5"
<input variant="danger"
name="service[vulnerabilities_issuetype]" :title="$options.i18n.fetchIssueTypesErrorMessage"
type="hidden" @dismiss="isLoadingErrorAlertDimissed = true"
:value="checkedIssueType.id || initialIssueTypeId" >
/> {{ loadingJiraIssueTypesErrorMessage }}
<gl-dropdown </gl-alert>
class="gl-w-full" <div class="row gl-display-flex gl-align-items-center">
:disabled="!jiraIssueTypes.length" <gl-button-group class="col-md-5 gl-mr-3">
:loading="isLoadingJiraIssueTypes || isTesting" <input
:text="checkedIssueType.name || $options.i18n.issueTypeSelect.defaultText" name="service[vulnerabilities_issuetype]"
> type="hidden"
<gl-dropdown-item :value="checkedIssueType.id || initialIssueTypeId"
v-for="jiraIssueType in jiraIssueTypes" />
:key="jiraIssueType.id" <gl-dropdown
:is-checked="checkedIssueType.id === jiraIssueType.id" class="gl-w-full"
is-check-item :disabled="!jiraIssueTypes.length"
@click="selectedJiraIssueType = jiraIssueType" :loading="isLoadingJiraIssueTypes || isTesting"
:text="checkedIssueType.name || $options.i18n.issueTypeSelect.defaultText"
> >
{{ jiraIssueType.name }} <gl-dropdown-item
</gl-dropdown-item> v-for="jiraIssueType in jiraIssueTypes"
</gl-dropdown> :key="jiraIssueType.id"
<gl-button :is-checked="checkedIssueType.id === jiraIssueType.id"
v-gl-tooltip.hover is-check-item
:title="$options.i18n.fetchIssueTypesButtonLabel" @click="selectedJiraIssueType = jiraIssueType"
:aria-label="$options.i18n.fetchIssueTypesButtonLabel" >
:disabled="!projectKey" {{ jiraIssueType.name }}
icon="retry" </gl-dropdown-item>
data-testid="fetch-issue-types" </gl-dropdown>
@click="handleLoadJiraIssueTypesClick" <gl-button
/> v-gl-tooltip.hover
</gl-button-group> :title="$options.i18n.fetchIssueTypesButtonLabel"
<p v-if="projectKeyWarning" class="gl-my-0"> :aria-label="$options.i18n.fetchIssueTypesButtonLabel"
<gl-icon name="warning" class="gl-text-orange-500" /> :disabled="!projectKey"
{{ projectKeyWarning }} icon="retry"
</p> data-testid="fetch-issue-types"
</div> @click="handleLoadJiraIssueTypesClick"
</gl-form-group> />
</gl-button-group>
<p v-if="projectKeyWarning" class="gl-my-0">
<gl-icon name="warning" class="gl-text-orange-500" />
{{ projectKeyWarning }}
</p>
</div>
</gl-form-group>
</template>
</div> </div>
</template> </template>
---
title: Display ultimate or premium upgrade plan banner for jira issues
merge_request: 58877
author:
type: added
...@@ -51,7 +51,6 @@ describe('JiraIssuesFields', () => { ...@@ -51,7 +51,6 @@ describe('JiraIssuesFields', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
describe('content', () => { describe('content', () => {
...@@ -101,6 +100,16 @@ describe('JiraIssuesFields', () => { ...@@ -101,6 +100,16 @@ describe('JiraIssuesFields', () => {
}); });
}); });
describe('when showFullFeature is off', () => {
beforeEach(() => {
wrapper = createShallowComponent({ props: { showFullFeature: false } });
});
it('does not show the issue type section', () => {
expect(findIssueTypeSection().exists()).toBe(false);
});
});
describe('Jira issue type dropdown', () => { describe('Jira issue type dropdown', () => {
describe('with no Jira issues fetched', () => { describe('with no Jira issues fetched', () => {
beforeEach(async () => { beforeEach(async () => {
......
...@@ -18031,6 +18031,12 @@ msgstr "" ...@@ -18031,6 +18031,12 @@ msgstr ""
msgid "JiraService|This feature requires a Premium plan." msgid "JiraService|This feature requires a Premium plan."
msgstr "" msgstr ""
msgid "JiraService|This is a Premium feature"
msgstr ""
msgid "JiraService|This is an Ultimate feature"
msgstr ""
msgid "JiraService|This issue is synchronized with Jira" msgid "JiraService|This issue is synchronized with Jira"
msgstr "" msgstr ""
...@@ -18040,6 +18046,9 @@ msgstr "" ...@@ -18040,6 +18046,9 @@ msgstr ""
msgid "JiraService|Transition Jira issues to their final state:" msgid "JiraService|Transition Jira issues to their final state:"
msgstr "" msgstr ""
msgid "JiraService|Upgrade your plan to enable this feature of the Jira Integration."
msgstr ""
msgid "JiraService|Use a password for server version and an API token for cloud version." msgid "JiraService|Use a password for server version and an API token for cloud version."
msgstr "" msgstr ""
...@@ -31944,9 +31953,6 @@ msgstr "" ...@@ -31944,9 +31953,6 @@ msgstr ""
msgid "This is a Jira user." msgid "This is a Jira user."
msgstr "" msgstr ""
msgid "This is a Premium feature"
msgstr ""
msgid "This is a confidential %{noteableTypeText}." msgid "This is a confidential %{noteableTypeText}."
msgstr "" msgstr ""
...@@ -33651,9 +33657,6 @@ msgstr "" ...@@ -33651,9 +33657,6 @@ msgstr ""
msgid "Upgrade your plan to activate Group Webhooks." msgid "Upgrade your plan to activate Group Webhooks."
msgstr "" msgstr ""
msgid "Upgrade your plan to enable this feature of the Jira Integration."
msgstr ""
msgid "Upgrade your plan to improve merge requests." msgid "Upgrade your plan to improve merge requests."
msgstr "" msgstr ""
......
import { GlFormCheckbox, GlFormInput } from '@gitlab/ui'; import { GlFormCheckbox, GlFormInput } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue'; import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import JiraUpgradeCta from '~/integrations/edit/components/jira_upgrade_cta.vue';
import eventHub from '~/integrations/edit/event_hub'; import eventHub from '~/integrations/edit/event_hub';
describe('JiraIssuesFields', () => { describe('JiraIssuesFields', () => {
...@@ -28,23 +28,46 @@ describe('JiraIssuesFields', () => { ...@@ -28,23 +28,46 @@ describe('JiraIssuesFields', () => {
} }
}); });
const findEnableCheckbox = () => wrapper.find(GlFormCheckbox); const findEnableCheckbox = () => wrapper.findComponent(GlFormCheckbox);
const findProjectKey = () => wrapper.find(GlFormInput); const findProjectKey = () => wrapper.findComponent(GlFormInput);
const expectedBannerText = 'This is a Premium feature'; const findJiraUpgradeCta = () => wrapper.findComponent(JiraUpgradeCta);
const findJiraForVulnerabilities = () => wrapper.find('[data-testid="jira-for-vulnerabilities"]'); const findJiraForVulnerabilities = () => wrapper.find('[data-testid="jira-for-vulnerabilities"]');
const setEnableCheckbox = async (isEnabled = true) => const setEnableCheckbox = async (isEnabled = true) =>
findEnableCheckbox().vm.$emit('input', isEnabled); findEnableCheckbox().vm.$emit('input', isEnabled);
describe('jira issues call to action', () => {
it('shows the premium message', () => {
createComponent({
props: { showJiraIssuesIntegration: false },
});
expect(findJiraUpgradeCta().props()).toMatchObject({
showPremiumMessage: true,
showUltimateMessage: false,
});
});
it('shows the ultimate message', () => {
createComponent({
props: {
showJiraIssuesIntegration: true,
showJiraVulnerabilitiesIntegration: false,
},
});
expect(findJiraUpgradeCta().props()).toMatchObject({
showPremiumMessage: false,
showUltimateMessage: true,
});
});
});
describe('template', () => { describe('template', () => {
describe('upgrade banner for non-Premium user', () => { describe('upgrade banner for non-Premium user', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ props: { initialProjectKey: '', showJiraIssuesIntegration: false } }); createComponent({ props: { initialProjectKey: '', showJiraIssuesIntegration: false } });
}); });
it('shows upgrade banner', () => {
expect(wrapper.text()).toContain(expectedBannerText);
});
it('does not show checkbox and input field', () => { it('does not show checkbox and input field', () => {
expect(findEnableCheckbox().exists()).toBe(false); expect(findEnableCheckbox().exists()).toBe(false);
expect(findProjectKey().exists()).toBe(false); expect(findProjectKey().exists()).toBe(false);
...@@ -57,7 +80,7 @@ describe('JiraIssuesFields', () => { ...@@ -57,7 +80,7 @@ describe('JiraIssuesFields', () => {
}); });
it('does not show upgrade banner', () => { it('does not show upgrade banner', () => {
expect(wrapper.text()).not.toContain(expectedBannerText); expect(findJiraUpgradeCta().exists()).toBe(false);
}); });
// As per https://vuejs.org/v2/guide/forms.html#Checkbox-1, // As per https://vuejs.org/v2/guide/forms.html#Checkbox-1,
...@@ -125,6 +148,14 @@ describe('JiraIssuesFields', () => { ...@@ -125,6 +148,14 @@ describe('JiraIssuesFields', () => {
}, },
); );
it('passes down the correct show-full-feature property', async () => {
await setEnableCheckbox(true);
expect(findJiraForVulnerabilities().attributes('show-full-feature')).toBe('true');
wrapper.setProps({ showJiraVulnerabilitiesIntegration: false });
await wrapper.vm.$nextTick();
expect(findJiraForVulnerabilities().attributes('show-full-feature')).toBeUndefined();
});
it('passes down the correct initial-issue-type-id value when value is empty', async () => { it('passes down the correct initial-issue-type-id value when value is empty', async () => {
await setEnableCheckbox(true); await setEnableCheckbox(true);
expect(findJiraForVulnerabilities().attributes('initial-issue-type-id')).toBeUndefined(); expect(findJiraForVulnerabilities().attributes('initial-issue-type-id')).toBeUndefined();
......
import { shallowMount } from '@vue/test-utils';
import JiraUpgradeCta from '~/integrations/edit/components/jira_upgrade_cta.vue';
describe('JiraUpgradeCta', () => {
let wrapper;
const contentMessage = 'Upgrade your plan to enable this feature of the Jira Integration.';
const createComponent = (propsData) => {
wrapper = shallowMount(JiraUpgradeCta, {
propsData,
});
};
afterEach(() => {
wrapper.destroy();
});
it('displays the correct message for premium and lower users', () => {
createComponent({ showPremiumMessage: true });
expect(wrapper.html()).toContain('This is a Premium feature');
expect(wrapper.html()).toContain(contentMessage);
});
it('displays the correct message for ultimate and lower users', () => {
createComponent({ showUltimateMessage: true });
expect(wrapper.html()).toContain('This is an Ultimate feature');
expect(wrapper.html()).toContain(contentMessage);
});
});
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