Commit 8ca4e503 authored by Mike Greiling's avatar Mike Greiling

Merge branch '223787-add-controls-to-enable-viewing-jira-issues-within-gitlab' into 'master'

Add controls to enable viewing Jira issues within GitLab

See merge request gitlab-org/gitlab!35417
parents 061750ec 181d8e5e
<script>
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ActiveToggle from './active_toggle.vue';
import JiraTriggerFields from './jira_trigger_fields.vue';
import JiraIssuesFields from './jira_issues_fields.vue';
import TriggerFields from './trigger_fields.vue';
import DynamicField from './dynamic_field.vue';
......@@ -9,9 +11,11 @@ export default {
components: {
ActiveToggle,
JiraTriggerFields,
JiraIssuesFields,
TriggerFields,
DynamicField,
},
mixins: [glFeatureFlagsMixin()],
props: {
activeToggleProps: {
type: Object,
......@@ -25,6 +29,10 @@ export default {
type: Object,
required: true,
},
jiraIssuesProps: {
type: Object,
required: true,
},
triggerEvents: {
type: Array,
required: false,
......@@ -44,6 +52,9 @@ export default {
isJira() {
return this.type === 'jira';
},
showJiraIssuesFields() {
return this.isJira && this.glFeatures.jiraIntegration;
},
},
};
</script>
......@@ -54,5 +65,6 @@ export default {
<jira-trigger-fields v-if="isJira" v-bind="triggerFieldsProps" />
<trigger-fields v-else-if="triggerEvents.length" :events="triggerEvents" :type="type" />
<dynamic-field v-for="field in fields" :key="field.name" v-bind="field" />
<jira-issues-fields v-if="showJiraIssuesFields" v-bind="jiraIssuesProps" />
</div>
</template>
<script>
import { GlFormGroup, GlFormCheckbox, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui';
export default {
name: 'JiraIssuesFields',
components: {
GlFormGroup,
GlFormCheckbox,
GlFormInput,
GlSprintf,
GlLink,
},
props: {
initialEnableJiraIssues: {
type: Boolean,
required: false,
},
initialProjectKey: {
type: String,
required: false,
default: null,
},
editProjectPath: {
type: String,
required: false,
default: null,
},
},
data() {
return {
enableJiraIssues: this.initialEnableJiraIssues,
projectKey: this.initialProjectKey,
};
},
};
</script>
<template>
<div>
<gl-form-group
:label="s__('JiraService|View Jira issues in GitLab')"
label-for="jira-issue-settings"
>
<div id="jira-issue-settings">
<p>
{{
s__(
'JiraService|Work on Jira issues without leaving GitLab. Adds a Jira menu to access your list of issues and view any issue as read-only.',
)
}}
</p>
<input name="service[issues_enabled]" type="hidden" value="false" />
<gl-form-checkbox v-model="enableJiraIssues" name="service[issues_enabled]">
{{ s__('JiraService|Enable Jira issues') }}
<template #help>
{{
s__(
'JiraService|Warning: All GitLab users that have access to this GitLab project will be able to view all issues from the Jira project specified below.',
)
}}
</template>
</gl-form-checkbox>
</div>
</gl-form-group>
<gl-form-group :label="s__('JiraService|Jira project key')">
<gl-form-input
v-model="projectKey"
type="text"
name="service[project_key]"
:placeholder="s__('JiraService|e.g. AB')"
:disabled="!enableJiraIssues"
/>
</gl-form-group>
<p>
<gl-sprintf
:message="
s__(
'JiraService|Displaying Jira issues while leaving the GitLab issue functionality enabled might be confusing. Consider %{linkStart}disabling GitLab issues%{linkEnd} if they won’t otherwise be used.',
)
"
>
<template #link="{ content }">
<gl-link :href="editProjectPath" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
</div>
</template>
......@@ -15,13 +15,22 @@ export default el => {
return result;
}
const { type, commentDetail, triggerEvents, fields, ...booleanAttributes } = el.dataset;
const {
type,
commentDetail,
projectKey,
editProjectPath,
triggerEvents,
fields,
...booleanAttributes
} = el.dataset;
const {
showActive,
activated,
commitEvents,
mergeRequestEvents,
enableComments,
enableJiraIssues,
} = parseBooleanInData(booleanAttributes);
return new Vue({
......@@ -40,6 +49,11 @@ export default el => {
initialEnableComments: enableComments,
initialCommentDetail: commentDetail,
},
jiraIssuesProps: {
initialEnableJiraIssues: enableJiraIssues,
initialProjectKey: projectKey,
editProjectPath,
},
triggerEvents: JSON.parse(triggerEvents),
fields: JSON.parse(fields),
},
......
......@@ -13,6 +13,7 @@ class Projects::ServicesController < Projects::ApplicationController
before_action :redirect_deprecated_prometheus_service, only: [:update]
before_action only: :edit do
push_frontend_feature_flag(:integration_form_refactor, default_enabled: true)
push_frontend_feature_flag(:jira_integration, @project)
end
respond_to :html
......
......@@ -5,6 +5,7 @@ module EE
extend ::Gitlab::Utils::Override
ALLOWED_PARAMS_EE = [
:issues_enabled,
:jenkins_url,
:multiproject_enabled,
:pass_unstable,
......
......@@ -9,6 +9,21 @@ module EE
::Feature.enabled?(:jira_integration, @project) && @project.jira_service.issues_enabled
end
override :integration_form_data
def integration_form_data(integration)
form_data = super
if integration.is_a?(JiraService)
form_data.merge!(
enable_jira_issues: integration.issues_enabled.to_s,
project_key: integration.project_key,
edit_project_path: @project ? edit_project_path(@project, anchor: 'js-shared-permissions') : nil
)
end
form_data
end
def add_to_slack_link(project, slack_app_id)
"https://slack.com/oauth/authorize?scope=commands&client_id=#{slack_app_id}&redirect_uri=#{slack_auth_project_settings_slack_url(project)}&state=#{escaped_form_authenticity_token}"
end
......
......@@ -17,6 +17,26 @@ RSpec.describe EE::ServicesHelper do
subject { controller_class.new }
describe '#integration_form_data' do
subject { helper.integration_form_data(integration) }
context 'Slack service' do
let(:integration) { build(:slack_service) }
it 'does not include Jira specific fields' do
is_expected.not_to include(:enable_jira_issues, :project_key, :edit_project_path)
end
end
context 'Jira service' do
let(:integration) { build(:jira_service) }
it 'includes Jira specific fields' do
is_expected.to include(:enable_jira_issues, :project_key, :edit_project_path)
end
end
end
describe '#add_to_slack_link' do
it 'encodes a masked CSRF token' do
expect(subject).to receive(:form_authenticity_token).and_return('a token')
......
......@@ -12943,6 +12943,12 @@ msgstr ""
msgid "JiraService|%{user_link} mentioned this issue in %{entity_link} of %{project_link}%{branch}:{quote}%{entity_message}{quote}"
msgstr ""
msgid "JiraService|Displaying Jira issues while leaving the GitLab issue functionality enabled might be confusing. Consider %{linkStart}disabling GitLab issues%{linkEnd} if they won’t otherwise be used."
msgstr ""
msgid "JiraService|Enable Jira issues"
msgstr ""
msgid "JiraService|Events for %{noteable_model_name} are disabled."
msgstr ""
......@@ -12967,6 +12973,9 @@ msgstr ""
msgid "JiraService|Jira issue tracker"
msgstr ""
msgid "JiraService|Jira project key"
msgstr ""
msgid "JiraService|Open Jira"
msgstr ""
......@@ -12988,9 +12997,21 @@ msgstr ""
msgid "JiraService|Username or Email"
msgstr ""
msgid "JiraService|View Jira issues in GitLab"
msgstr ""
msgid "JiraService|Warning: All GitLab users that have access to this GitLab project will be able to view all issues from the Jira project specified below."
msgstr ""
msgid "JiraService|Web URL"
msgstr ""
msgid "JiraService|Work on Jira issues without leaving GitLab. Adds a Jira menu to access your list of issues and view any issue as read-only."
msgstr ""
msgid "JiraService|e.g. AB"
msgstr ""
msgid "JiraService|transition ids can have only numbers which can be split with , or ;"
msgstr ""
......
......@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import IntegrationForm from '~/integrations/edit/components/integration_form.vue';
import ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import TriggerFields from '~/integrations/edit/components/trigger_fields.vue';
import DynamicField from '~/integrations/edit/components/dynamic_field.vue';
......@@ -18,16 +19,20 @@ describe('IntegrationForm', () => {
initialTriggerMergeRequest: false,
initialEnableComments: false,
},
jiraIssuesProps: {},
type: '',
};
const createComponent = props => {
const createComponent = (props, featureFlags = {}) => {
wrapper = shallowMount(IntegrationForm, {
propsData: { ...defaultProps, ...props },
stubs: {
ActiveToggle,
JiraTriggerFields,
},
provide: {
glFeatures: featureFlags,
},
});
};
......@@ -40,6 +45,7 @@ describe('IntegrationForm', () => {
const findActiveToggle = () => wrapper.find(ActiveToggle);
const findJiraTriggerFields = () => wrapper.find(JiraTriggerFields);
const findJiraIssuesFields = () => wrapper.find(JiraIssuesFields);
const findTriggerFields = () => wrapper.find(TriggerFields);
describe('template', () => {
......@@ -62,23 +68,41 @@ describe('IntegrationForm', () => {
});
describe('type is "slack"', () => {
it('does not render JiraTriggerFields', () => {
createComponent({
type: 'slack',
});
beforeEach(() => {
createComponent({ type: 'slack' });
});
it('does not render JiraTriggerFields', () => {
expect(findJiraTriggerFields().exists()).toBe(false);
});
it('does not render JiraIssuesFields', () => {
expect(findJiraIssuesFields().exists()).toBe(false);
});
});
describe('type is "jira"', () => {
it('renders JiraTriggerFields', () => {
createComponent({
type: 'jira',
});
createComponent({ type: 'jira' });
expect(findJiraTriggerFields().exists()).toBe(true);
});
describe('featureFlag jiraIntegration is false', () => {
it('does not render JiraIssuesFields', () => {
createComponent({ type: 'jira' }, { jiraIntegration: false });
expect(findJiraIssuesFields().exists()).toBe(false);
});
});
describe('featureFlag jiraIntegration is true', () => {
it('renders JiraIssuesFields', () => {
createComponent({ type: 'jira' }, { jiraIntegration: true });
expect(findJiraIssuesFields().exists()).toBe(true);
});
});
});
describe('triggerEvents is present', () => {
......
import { mount } from '@vue/test-utils';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import { GlFormCheckbox, GlFormInput } from '@gitlab/ui';
describe('JiraIssuesFields', () => {
let wrapper;
const defaultProps = {
editProjectPath: '/edit',
};
const createComponent = props => {
wrapper = mount(JiraIssuesFields, {
propsData: { ...defaultProps, ...props },
});
};
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
const findEnableCheckbox = () => wrapper.find(GlFormCheckbox);
const findProjectKey = () => wrapper.find(GlFormInput);
describe('template', () => {
describe('Enable Jira issues checkbox', () => {
beforeEach(() => {
createComponent({ initialProjectKey: '' });
});
// As per https://vuejs.org/v2/guide/forms.html#Checkbox-1,
// browsers don't include unchecked boxes in form submissions.
it('includes issues_enabled as false even if unchecked', () => {
expect(wrapper.contains('input[name="service[issues_enabled]"]')).toBe(true);
});
it('disables project_key input', () => {
expect(findProjectKey().attributes('disabled')).toBe('disabled');
});
describe('on enable issues', () => {
it('enables project_key input', () => {
findEnableCheckbox().vm.$emit('input', true);
return wrapper.vm.$nextTick().then(() => {
expect(findProjectKey().attributes('disabled')).toBeUndefined();
});
});
});
});
it('contains link to editProjectPath', () => {
createComponent();
expect(wrapper.contains(`a[href="${defaultProps.editProjectPath}"]`)).toBe(true);
});
});
});
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