Commit 196a9ccc authored by Etienne Baqué's avatar Etienne Baqué

Merge branch '332280-integration-settings-template-jira-trigger-and-issues' into 'master'

Add Jira trigger and issues sections to integration form

See merge request gitlab-org/gitlab!82391
parents 67d8c854 5662fb4b
...@@ -28,8 +28,12 @@ export const overridesTabTitle = s__('Integrations|Projects using custom setting ...@@ -28,8 +28,12 @@ export const overridesTabTitle = s__('Integrations|Projects using custom setting
export const integrationFormSections = { export const integrationFormSections = {
CONNECTION: 'connection', CONNECTION: 'connection',
JIRA_TRIGGER: 'jira_trigger',
JIRA_ISSUES: 'jira_issues',
}; };
export const integrationFormSectionComponents = { export const integrationFormSectionComponents = {
[integrationFormSections.CONNECTION]: 'IntegrationSectionConnection', [integrationFormSections.CONNECTION]: 'IntegrationSectionConnection',
[integrationFormSections.JIRA_TRIGGER]: 'IntegrationSectionJiraTrigger',
[integrationFormSections.JIRA_ISSUES]: 'IntegrationSectionJiraIssues',
}; };
...@@ -39,6 +39,14 @@ export default { ...@@ -39,6 +39,14 @@ export default {
import( import(
/* webpackChunkName: 'integrationSectionConnection' */ '~/integrations/edit/components/sections/connection.vue' /* webpackChunkName: 'integrationSectionConnection' */ '~/integrations/edit/components/sections/connection.vue'
), ),
IntegrationSectionJiraIssues: () =>
import(
/* webpackChunkName: 'integrationSectionJiraIssues' */ '~/integrations/edit/components/sections/jira_issues.vue'
),
IntegrationSectionJiraTrigger: () =>
import(
/* webpackChunkName: 'integrationSectionJiraTrigger' */ '~/integrations/edit/components/sections/jira_trigger.vue'
),
GlButton, GlButton,
GlForm, GlForm,
}, },
...@@ -47,6 +55,11 @@ export default { ...@@ -47,6 +55,11 @@ export default {
SafeHtml, SafeHtml,
}, },
mixins: [glFeatureFlagsMixin()], mixins: [glFeatureFlagsMixin()],
provide() {
return {
hasSections: this.hasSections,
};
},
inject: { inject: {
helpHtml: { helpHtml: {
default: '', default: '',
...@@ -208,9 +221,9 @@ export default { ...@@ -208,9 +221,9 @@ export default {
<template v-if="hasSections"> <template v-if="hasSections">
<div <div
v-for="section in customState.sections" v-for="(section, index) in customState.sections"
:key="section.type" :key="section.type"
class="gl-border-b gl-mb-5" :class="{ 'gl-border-b gl-pb-3 gl-mb-6': index !== customState.sections.length - 1 }"
data-testid="integration-section" data-testid="integration-section"
> >
<div class="row"> <div class="row">
...@@ -225,6 +238,7 @@ export default { ...@@ -225,6 +238,7 @@ export default {
:fields="fieldsForSection(section)" :fields="fieldsForSection(section)"
:is-validated="isValidated" :is-validated="isValidated"
@toggle-integration-active="onToggleIntegrationState" @toggle-integration-active="onToggleIntegrationState"
@request-jira-issue-types="onRequestJiraIssueTypes"
/> />
</div> </div>
</div> </div>
...@@ -244,13 +258,13 @@ export default { ...@@ -244,13 +258,13 @@ export default {
@toggle-integration-active="onToggleIntegrationState" @toggle-integration-active="onToggleIntegrationState"
/> />
<jira-trigger-fields <jira-trigger-fields
v-if="isJira" v-if="isJira && !hasSections"
:key="`${currentKey}-jira-trigger-fields`" :key="`${currentKey}-jira-trigger-fields`"
v-bind="propsSource.triggerFieldsProps" v-bind="propsSource.triggerFieldsProps"
:is-validated="isValidated" :is-validated="isValidated"
/> />
<trigger-fields <trigger-fields
v-else-if="propsSource.triggerEvents.length" v-else-if="propsSource.triggerEvents.length && !hasSections"
:key="`${currentKey}-trigger-fields`" :key="`${currentKey}-trigger-fields`"
:events="propsSource.triggerEvents" :events="propsSource.triggerEvents"
:type="propsSource.type" :type="propsSource.type"
...@@ -262,15 +276,18 @@ export default { ...@@ -262,15 +276,18 @@ export default {
:is-validated="isValidated" :is-validated="isValidated"
/> />
<jira-issues-fields <jira-issues-fields
v-if="isJira && !isInstanceOrGroupLevel" v-if="isJira && !isInstanceOrGroupLevel && !hasSections"
:key="`${currentKey}-jira-issues-fields`" :key="`${currentKey}-jira-issues-fields`"
v-bind="propsSource.jiraIssuesProps" v-bind="propsSource.jiraIssuesProps"
:is-validated="isValidated" :is-validated="isValidated"
@request-jira-issue-types="onRequestJiraIssueTypes" @request-jira-issue-types="onRequestJiraIssueTypes"
/> />
</div>
</div>
<div v-if="isEditable" class="row">
<div :class="hasSections ? 'col' : 'col-lg-8 offset-lg-4'">
<div <div
v-if="isEditable"
class="footer-block row-content-block gl-display-flex gl-justify-content-space-between" class="footer-block row-content-block gl-display-flex gl-justify-content-space-between"
> >
<div> <div>
......
...@@ -16,6 +16,11 @@ export default { ...@@ -16,6 +16,11 @@ export default {
JiraIssueCreationVulnerabilities: () => JiraIssueCreationVulnerabilities: () =>
import('ee_component/integrations/edit/components/jira_issue_creation_vulnerabilities.vue'), import('ee_component/integrations/edit/components/jira_issue_creation_vulnerabilities.vue'),
}, },
inject: {
hasSections: {
default: false,
},
},
props: { props: {
showJiraIssuesIntegration: { showJiraIssuesIntegration: {
type: Boolean, type: Boolean,
...@@ -101,9 +106,12 @@ export default { ...@@ -101,9 +106,12 @@ export default {
<template> <template>
<div> <div>
<gl-form-group :label="$options.i18n.sectionTitle" label-for="jira-issue-settings"> <gl-form-group
:label="hasSections ? null : $options.i18n.sectionTitle"
label-for="jira-issue-settings"
>
<div id="jira-issue-settings"> <div id="jira-issue-settings">
<p> <p v-if="!hasSections">
{{ $options.i18n.sectionDescription }} {{ $options.i18n.sectionDescription }}
</p> </p>
<template v-if="showJiraIssuesIntegration"> <template v-if="showJiraIssuesIntegration">
......
...@@ -62,6 +62,11 @@ export default { ...@@ -62,6 +62,11 @@ export default {
GlLink, GlLink,
GlSprintf, GlSprintf,
}, },
inject: {
hasSections: {
default: false,
},
},
props: { props: {
initialTriggerCommit: { initialTriggerCommit: {
type: Boolean, type: Boolean,
...@@ -134,10 +139,12 @@ export default { ...@@ -134,10 +139,12 @@ export default {
<template> <template>
<div> <div>
<gl-form-group <gl-form-group
:label="__('Trigger')" :label="hasSections ? null : __('Trigger')"
label-for="service[trigger]" label-for="service[trigger]"
:description=" :description="
s__( hasSections
? null
: s__(
'JiraService|When a Jira issue is mentioned in a commit or merge request, a remote link and comment (if enabled) will be created.', 'JiraService|When a Jira issue is mentioned in a commit or merge request, a remote link and comment (if enabled) will be created.',
) )
" "
......
<script>
import { mapGetters } from 'vuex';
import JiraIssuesFields from '../jira_issues_fields.vue';
export default {
name: 'IntegrationSectionJiraIssues',
components: {
JiraIssuesFields,
},
props: {
isValidated: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
...mapGetters(['currentKey', 'propsSource']),
},
};
</script>
<template>
<div>
<jira-issues-fields
:key="`${currentKey}-jira-issues-fields`"
v-bind="propsSource.jiraIssuesProps"
:is-validated="isValidated"
@request-jira-issue-types="$emit('request-jira-issue-types')"
/>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import JiraTriggerFields from '../jira_trigger_fields.vue';
export default {
name: 'IntegrationSectionJiraTrigger',
components: {
JiraTriggerFields,
},
props: {
isValidated: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
...mapGetters(['currentKey', 'propsSource']),
},
};
</script>
<template>
<div>
<jira-trigger-fields
:key="`${currentKey}-jira-trigger-fields`"
v-bind="propsSource.triggerFieldsProps"
:is-validated="isValidated"
/>
</div>
</template>
...@@ -15,6 +15,9 @@ module Integrations ...@@ -15,6 +15,9 @@ module Integrations
ATLASSIAN_REFERRER_GITLAB_COM = { atlOrigin: 'eyJpIjoiY2QyZTJiZDRkNGZhNGZlMWI3NzRkNTBmZmVlNzNiZTkiLCJwIjoianN3LWdpdGxhYi1pbnQifQ' }.freeze ATLASSIAN_REFERRER_GITLAB_COM = { atlOrigin: 'eyJpIjoiY2QyZTJiZDRkNGZhNGZlMWI3NzRkNTBmZmVlNzNiZTkiLCJwIjoianN3LWdpdGxhYi1pbnQifQ' }.freeze
ATLASSIAN_REFERRER_SELF_MANAGED = { atlOrigin: 'eyJpIjoiYjM0MTA4MzUyYTYxNDVkY2IwMzVjOGQ3ZWQ3NzMwM2QiLCJwIjoianN3LWdpdGxhYlNNLWludCJ9' }.freeze ATLASSIAN_REFERRER_SELF_MANAGED = { atlOrigin: 'eyJpIjoiYjM0MTA4MzUyYTYxNDVkY2IwMzVjOGQ3ZWQ3NzMwM2QiLCJwIjoianN3LWdpdGxhYlNNLWludCJ9' }.freeze
SECTION_TYPE_JIRA_TRIGGER = 'jira_trigger'
SECTION_TYPE_JIRA_ISSUES = 'jira_issues'
validates :url, public_url: true, presence: true, if: :activated? validates :url, public_url: true, presence: true, if: :activated?
validates :api_url, public_url: true, allow_blank: true validates :api_url, public_url: true, allow_blank: true
validates :username, presence: true, if: :activated? validates :username, presence: true, if: :activated?
...@@ -157,13 +160,31 @@ module Integrations ...@@ -157,13 +160,31 @@ module Integrations
end end
def sections def sections
[ jira_issues_link_start = '<a href="%{url}">'.html_safe % { url: help_page_url('integration/jira/issues.html') }
sections = [
{ {
type: SECTION_TYPE_CONNECTION, type: SECTION_TYPE_CONNECTION,
title: s_('Integrations|Connection details'), title: s_('Integrations|Connection details'),
description: help description: help
},
{
type: SECTION_TYPE_JIRA_TRIGGER,
title: _('Trigger'),
description: s_('JiraService|When a Jira issue is mentioned in a commit or merge request, a remote link and comment (if enabled) will be created.')
} }
].freeze ]
# Jira issues is currently only configurable on the project level.
if project_level?
sections.push({
type: SECTION_TYPE_JIRA_ISSUES,
title: _('Issues'),
description: s_('JiraService|Work on Jira issues without leaving GitLab. Add a Jira menu to access a read-only list of your Jira issues. %{jira_issues_link_start}Learn more.%{link_end}') % { jira_issues_link_start: jira_issues_link_start, link_end: '</a>'.html_safe }
})
end
sections
end end
def web_url(path = nil, **params) def web_url(path = nil, **params)
......
...@@ -21059,6 +21059,9 @@ msgstr "" ...@@ -21059,6 +21059,9 @@ msgstr ""
msgid "JiraService|Work on Jira issues without leaving GitLab. Add a Jira menu to access a read-only list of your Jira issues." msgid "JiraService|Work on Jira issues without leaving GitLab. Add a Jira menu to access a read-only list of your Jira issues."
msgstr "" msgstr ""
msgid "JiraService|Work on Jira issues without leaving GitLab. Add a Jira menu to access a read-only list of your Jira issues. %{jira_issues_link_start}Learn more.%{link_end}"
msgstr ""
msgid "JiraService|You must configure Jira before enabling this integration. %{jira_doc_link_start}Learn more.%{link_end}" msgid "JiraService|You must configure Jira before enabling this integration. %{jira_doc_link_start}Learn more.%{link_end}"
msgstr "" msgstr ""
......
...@@ -435,6 +435,29 @@ describe('IntegrationForm', () => { ...@@ -435,6 +435,29 @@ describe('IntegrationForm', () => {
}); });
}, },
); );
describe('when IntegrationSectionConnection emits `request-jira-issue-types` event', () => {
beforeEach(() => {
jest.spyOn(document, 'querySelector').mockReturnValue(document.createElement('form'));
createComponent({
provide: {
glFeatures: { integrationFormSections: true },
},
customStateProps: {
sections: [mockSectionConnection],
testPath: '/test',
},
mountFn: mountExtended,
});
findConnectionSectionComponent().vm.$emit('request-jira-issue-types');
});
it('dispatches `requestJiraIssueTypes` action', () => {
expect(dispatch).toHaveBeenCalledWith('requestJiraIssueTypes', expect.any(FormData));
});
});
}); });
describe('ActiveCheckbox', () => { describe('ActiveCheckbox', () => {
......
import { shallowMount } from '@vue/test-utils';
import IntegrationSectionJiraIssue from '~/integrations/edit/components/sections/jira_issues.vue';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import { createStore } from '~/integrations/edit/store';
import { mockIntegrationProps } from '../../mock_data';
describe('IntegrationSectionJiraIssue', () => {
let wrapper;
const createComponent = () => {
const store = createStore({
customState: { ...mockIntegrationProps },
});
wrapper = shallowMount(IntegrationSectionJiraIssue, {
store,
});
};
afterEach(() => {
wrapper.destroy();
});
const findJiraIssuesFields = () => wrapper.findComponent(JiraIssuesFields);
describe('template', () => {
it('renders JiraIssuesFields', () => {
createComponent();
expect(findJiraIssuesFields().exists()).toBe(true);
});
});
});
import { shallowMount } from '@vue/test-utils';
import IntegrationSectionJiraTrigger from '~/integrations/edit/components/sections/jira_trigger.vue';
import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
import { createStore } from '~/integrations/edit/store';
import { mockIntegrationProps } from '../../mock_data';
describe('IntegrationSectionJiraTrigger', () => {
let wrapper;
const createComponent = () => {
const store = createStore({
customState: { ...mockIntegrationProps },
});
wrapper = shallowMount(IntegrationSectionJiraTrigger, {
store,
});
};
afterEach(() => {
wrapper.destroy();
});
const findJiraTriggerFields = () => wrapper.findComponent(JiraTriggerFields);
describe('template', () => {
it('renders JiraTriggerFields', () => {
createComponent();
expect(findJiraTriggerFields().exists()).toBe(true);
});
});
});
...@@ -109,6 +109,32 @@ RSpec.describe Integrations::Jira do ...@@ -109,6 +109,32 @@ RSpec.describe Integrations::Jira do
end end
end end
describe '#sections' do
let(:integration) { create(:jira_integration) }
subject(:sections) { integration.sections.map { |s| s[:type] } }
context 'when project_level? is true' do
before do
allow(integration).to receive(:project_level?).and_return(true)
end
it 'includes SECTION_TYPE_JIRA_ISSUES' do
expect(sections).to include(described_class::SECTION_TYPE_JIRA_ISSUES)
end
end
context 'when project_level? is false' do
before do
allow(integration).to receive(:project_level?).and_return(false)
end
it 'does not include SECTION_TYPE_JIRA_ISSUES' do
expect(sections).not_to include(described_class::SECTION_TYPE_JIRA_ISSUES)
end
end
end
describe '.reference_pattern' do describe '.reference_pattern' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
......
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