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
export const integrationFormSections = {
CONNECTION: 'connection',
JIRA_TRIGGER: 'jira_trigger',
JIRA_ISSUES: 'jira_issues',
};
export const integrationFormSectionComponents = {
[integrationFormSections.CONNECTION]: 'IntegrationSectionConnection',
[integrationFormSections.JIRA_TRIGGER]: 'IntegrationSectionJiraTrigger',
[integrationFormSections.JIRA_ISSUES]: 'IntegrationSectionJiraIssues',
};
......@@ -39,6 +39,14 @@ export default {
import(
/* 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,
GlForm,
},
......@@ -47,6 +55,11 @@ export default {
SafeHtml,
},
mixins: [glFeatureFlagsMixin()],
provide() {
return {
hasSections: this.hasSections,
};
},
inject: {
helpHtml: {
default: '',
......@@ -208,9 +221,9 @@ export default {
<template v-if="hasSections">
<div
v-for="section in customState.sections"
v-for="(section, index) in customState.sections"
: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"
>
<div class="row">
......@@ -225,6 +238,7 @@ export default {
:fields="fieldsForSection(section)"
:is-validated="isValidated"
@toggle-integration-active="onToggleIntegrationState"
@request-jira-issue-types="onRequestJiraIssueTypes"
/>
</div>
</div>
......@@ -244,13 +258,13 @@ export default {
@toggle-integration-active="onToggleIntegrationState"
/>
<jira-trigger-fields
v-if="isJira"
v-if="isJira && !hasSections"
:key="`${currentKey}-jira-trigger-fields`"
v-bind="propsSource.triggerFieldsProps"
:is-validated="isValidated"
/>
<trigger-fields
v-else-if="propsSource.triggerEvents.length"
v-else-if="propsSource.triggerEvents.length && !hasSections"
:key="`${currentKey}-trigger-fields`"
:events="propsSource.triggerEvents"
:type="propsSource.type"
......@@ -262,15 +276,18 @@ export default {
:is-validated="isValidated"
/>
<jira-issues-fields
v-if="isJira && !isInstanceOrGroupLevel"
v-if="isJira && !isInstanceOrGroupLevel && !hasSections"
:key="`${currentKey}-jira-issues-fields`"
v-bind="propsSource.jiraIssuesProps"
:is-validated="isValidated"
@request-jira-issue-types="onRequestJiraIssueTypes"
/>
</div>
</div>
<div v-if="isEditable" class="row">
<div :class="hasSections ? 'col' : 'col-lg-8 offset-lg-4'">
<div
v-if="isEditable"
class="footer-block row-content-block gl-display-flex gl-justify-content-space-between"
>
<div>
......
......@@ -16,6 +16,11 @@ export default {
JiraIssueCreationVulnerabilities: () =>
import('ee_component/integrations/edit/components/jira_issue_creation_vulnerabilities.vue'),
},
inject: {
hasSections: {
default: false,
},
},
props: {
showJiraIssuesIntegration: {
type: Boolean,
......@@ -101,9 +106,12 @@ export default {
<template>
<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">
<p>
<p v-if="!hasSections">
{{ $options.i18n.sectionDescription }}
</p>
<template v-if="showJiraIssuesIntegration">
......
......@@ -62,6 +62,11 @@ export default {
GlLink,
GlSprintf,
},
inject: {
hasSections: {
default: false,
},
},
props: {
initialTriggerCommit: {
type: Boolean,
......@@ -134,10 +139,12 @@ export default {
<template>
<div>
<gl-form-group
:label="__('Trigger')"
:label="hasSections ? null : __('Trigger')"
label-for="service[trigger]"
: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.',
)
"
......
<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
ATLASSIAN_REFERRER_GITLAB_COM = { atlOrigin: 'eyJpIjoiY2QyZTJiZDRkNGZhNGZlMWI3NzRkNTBmZmVlNzNiZTkiLCJwIjoianN3LWdpdGxhYi1pbnQifQ' }.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 :api_url, public_url: true, allow_blank: true
validates :username, presence: true, if: :activated?
......@@ -157,13 +160,31 @@ module Integrations
end
def sections
[
jira_issues_link_start = '<a href="%{url}">'.html_safe % { url: help_page_url('integration/jira/issues.html') }
sections = [
{
type: SECTION_TYPE_CONNECTION,
title: s_('Integrations|Connection details'),
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
def web_url(path = nil, **params)
......
......@@ -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."
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}"
msgstr ""
......
......@@ -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', () => {
......
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
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
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