Commit 130cc53c authored by Ash McKenzie's avatar Ash McKenzie

Merge branch...

Merge branch '218250-project-level-integration-implement-project-level-setting-selector' into 'master'

Add override selector for project-level integrations

See merge request gitlab-org/gitlab!34742
parents a7f26963 835d4a5a
<script>
import { mapGetters } from 'vuex';
import eventHub from '../event_hub';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { GlFormGroup, GlToggle } from '@gitlab/ui';
......@@ -21,6 +22,9 @@ export default {
activated: this.initialActivated,
};
},
computed: {
...mapGetters(['isInheriting']),
},
mounted() {
// Initialize view
this.$nextTick(() => {
......@@ -42,6 +46,7 @@ export default {
v-model="activated"
name="service[active]"
class="gl-display-block gl-line-height-0"
:disabled="isInheriting"
@change="onToggle"
/>
</gl-form-group>
......@@ -50,7 +55,12 @@ export default {
<div class="form-group row" role="group">
<label for="service[active]" class="col-form-label col-sm-2">{{ __('Active') }}</label>
<div class="col-sm-10 pt-1">
<gl-toggle v-model="activated" name="service[active]" @change="onToggle" />
<gl-toggle
v-model="activated"
name="service[active]"
:disabled="isInheriting"
@change="onToggle"
/>
</div>
</div>
</div>
......
<script>
import { mapGetters } from 'vuex';
import eventHub from '../event_hub';
import { capitalize, lowerCase, isEmpty } from 'lodash';
import { __, sprintf } from '~/locale';
......@@ -59,6 +60,7 @@ export default {
};
},
computed: {
...mapGetters(['isInheriting']),
isCheckbox() {
return this.type === 'checkbox';
},
......@@ -107,6 +109,7 @@ export default {
id: this.fieldId,
name: this.fieldName,
state: this.valid,
readonly: this.isInheriting,
};
},
valid() {
......@@ -142,12 +145,15 @@ export default {
</template>
<template v-if="isCheckbox">
<input :name="fieldName" type="hidden" value="false" />
<gl-form-checkbox v-model="model" v-bind="sharedProps">
<input :name="fieldName" type="hidden" :value="model || false" />
<gl-form-checkbox :id="fieldId" v-model="model" :disabled="isInheriting">
{{ humanizedTitle }}
</gl-form-checkbox>
</template>
<gl-form-select v-else-if="isSelect" v-model="model" v-bind="sharedProps" :options="options" />
<template v-else-if="isSelect">
<input type="hidden" :name="fieldName" :value="model" />
<gl-form-select :id="fieldId" v-model="model" :options="options" :disabled="isInheriting" />
</template>
<gl-form-textarea
v-else-if="isTextarea"
v-model="model"
......
<script>
import { mapState, mapActions, mapGetters } from 'vuex';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import OverrideDropdown from './override_dropdown.vue';
import ActiveToggle from './active_toggle.vue';
import JiraTriggerFields from './jira_trigger_fields.vue';
import JiraIssuesFields from './jira_issues_fields.vue';
......@@ -9,6 +12,7 @@ import DynamicField from './dynamic_field.vue';
export default {
name: 'IntegrationForm',
components: {
OverrideDropdown,
ActiveToggle,
JiraTriggerFields,
JiraIssuesFields,
......@@ -16,55 +20,55 @@ export default {
DynamicField,
},
mixins: [glFeatureFlagsMixin()],
props: {
activeToggleProps: {
type: Object,
required: true,
},
showActive: {
type: Boolean,
required: true,
},
triggerFieldsProps: {
type: Object,
required: true,
},
jiraIssuesProps: {
type: Object,
required: true,
},
triggerEvents: {
type: Array,
required: false,
default: () => [],
},
fields: {
type: Array,
required: false,
default: () => [],
},
type: {
type: String,
required: true,
},
},
computed: {
...mapGetters(['currentKey', 'propsSource']),
...mapState(['adminState', 'override']),
isJira() {
return this.type === 'jira';
return this.propsSource.type === 'jira';
},
showJiraIssuesFields() {
return this.isJira && this.glFeatures.jiraIssuesIntegration;
},
},
methods: {
...mapActions(['setOverride']),
},
};
</script>
<template>
<div>
<active-toggle v-if="showActive" v-bind="activeToggleProps" />
<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" />
<override-dropdown
v-if="adminState !== null"
:inherit-from-id="adminState.id"
:override="override"
@change="setOverride"
/>
<active-toggle
v-if="propsSource.showActive"
:key="`${currentKey}-active-toggle`"
v-bind="propsSource.activeToggleProps"
/>
<jira-trigger-fields
v-if="isJira"
:key="`${currentKey}-jira-trigger-fields`"
v-bind="propsSource.triggerFieldsProps"
/>
<trigger-fields
v-else-if="propsSource.triggerEvents.length"
:key="`${currentKey}-trigger-fields`"
:events="propsSource.triggerEvents"
:type="propsSource.type"
/>
<dynamic-field
v-for="field in propsSource.fields"
:key="`${currentKey}-${field.name}`"
v-bind="field"
/>
<jira-issues-fields
v-if="showJiraIssuesFields"
:key="`${currentKey}-jira-issues-fields`"
v-bind="propsSource.jiraIssuesProps"
/>
</div>
</template>
......@@ -89,8 +89,8 @@ export default {
}}
</p>
<template v-if="showJiraIssuesIntegration">
<input name="service[issues_enabled]" type="hidden" value="false" />
<gl-form-checkbox v-model="enableJiraIssues" name="service[issues_enabled]">
<input name="service[issues_enabled]" type="hidden" :value="enableJiraIssues || false" />
<gl-form-checkbox v-model="enableJiraIssues">
{{ s__('JiraService|Enable Jira issues') }}
<template #help>
{{
......
<script>
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { mapGetters } from 'vuex';
import { s__ } from '~/locale';
import { GlFormGroup, GlFormCheckbox, GlFormRadio } from '@gitlab/ui';
......@@ -55,6 +56,7 @@ export default {
};
},
computed: {
...mapGetters(['isInheriting']),
showEnableComments() {
return this.triggerCommit || this.triggerMergeRequest;
},
......@@ -73,13 +75,17 @@ export default {
)
"
>
<input name="service[commit_events]" type="hidden" value="false" />
<gl-form-checkbox v-model="triggerCommit" name="service[commit_events]">
<input name="service[commit_events]" type="hidden" :value="triggerCommit || false" />
<gl-form-checkbox v-model="triggerCommit" :disabled="isInheriting">
{{ __('Commit') }}
</gl-form-checkbox>
<input name="service[merge_requests_events]" type="hidden" value="false" />
<gl-form-checkbox v-model="triggerMergeRequest" name="service[merge_requests_events]">
<input
name="service[merge_requests_events]"
type="hidden"
:value="triggerMergeRequest || false"
/>
<gl-form-checkbox v-model="triggerMergeRequest" :disabled="isInheriting">
{{ __('Merge request') }}
</gl-form-checkbox>
</gl-form-group>
......@@ -89,8 +95,12 @@ export default {
:label="s__('Integrations|Comment settings:')"
data-testid="comment-settings"
>
<input name="service[comment_on_event_enabled]" type="hidden" value="false" />
<gl-form-checkbox v-model="enableComments" name="service[comment_on_event_enabled]">
<input
name="service[comment_on_event_enabled]"
type="hidden"
:value="enableComments || false"
/>
<gl-form-checkbox v-model="enableComments" :disabled="isInheriting">
{{ s__('Integrations|Enable comments') }}
</gl-form-checkbox>
</gl-form-group>
......@@ -100,12 +110,18 @@ export default {
:label="s__('Integrations|Comment detail:')"
data-testid="comment-detail"
>
<input
v-if="isInheriting"
name="service[comment_detail]"
type="hidden"
:value="commentDetail"
/>
<gl-form-radio
v-for="commentDetailOption in commentDetailOptions"
:key="commentDetailOption.value"
v-model="commentDetail"
:value="commentDetailOption.value"
name="service[comment_detail]"
:disabled="isInheriting"
>
{{ commentDetailOption.label }}
<template #help>
......@@ -126,13 +142,17 @@ export default {
}}
</label>
<input name="service[commit_events]" type="hidden" value="false" />
<gl-form-checkbox v-model="triggerCommit" name="service[commit_events]">
<input name="service[commit_events]" type="hidden" :value="triggerCommit || false" />
<gl-form-checkbox v-model="triggerCommit" :disabled="isInheriting">
{{ __('Commit') }}
</gl-form-checkbox>
<input name="service[merge_requests_events]" type="hidden" value="false" />
<gl-form-checkbox v-model="triggerMergeRequest" name="service[merge_requests_events]">
<input
name="service[merge_requests_events]"
type="hidden"
:value="triggerMergeRequest || false"
/>
<gl-form-checkbox v-model="triggerMergeRequest" :disabled="isInheriting">
{{ __('Merge request') }}
</gl-form-checkbox>
......@@ -144,8 +164,12 @@ export default {
<label>
{{ s__('Integrations|Comment settings:') }}
</label>
<input name="service[comment_on_event_enabled]" type="hidden" value="false" />
<gl-form-checkbox v-model="enableComments" name="service[comment_on_event_enabled]">
<input
name="service[comment_on_event_enabled]"
type="hidden"
:value="enableComments || false"
/>
<gl-form-checkbox v-model="enableComments" :disabled="isInheriting">
{{ s__('Integrations|Enable comments') }}
</gl-form-checkbox>
......@@ -153,12 +177,18 @@ export default {
<label>
{{ s__('Integrations|Comment detail:') }}
</label>
<input
v-if="isInheriting"
name="service[comment_detail]"
type="hidden"
:value="commentDetail"
/>
<gl-form-radio
v-for="commentDetailOption in commentDetailOptions"
:key="commentDetailOption.value"
v-model="commentDetail"
:value="commentDetailOption.value"
name="service[comment_detail]"
:disabled="isInheriting"
>
{{ commentDetailOption.label }}
<template #help>
......
<script>
import { s__ } from '~/locale';
import { GlNewDropdown, GlNewDropdownItem } from '@gitlab/ui';
const dropdownOptions = [
{
value: false,
text: s__('Integrations|Use instance level settings'),
},
{
value: true,
text: s__('Integrations|Use custom settings'),
},
];
export default {
dropdownOptions,
name: 'OverrideDropdown',
components: {
GlNewDropdown,
GlNewDropdownItem,
},
props: {
inheritFromId: {
type: Number,
required: true,
},
override: {
type: Boolean,
required: true,
},
},
data() {
return {
selected: dropdownOptions.find(x => x.value === this.override),
};
},
methods: {
onClick(option) {
this.selected = option;
this.$emit('change', option.value);
},
},
};
</script>
<template>
<div
class="gl-display-flex gl-justify-content-space-between gl-align-items-baseline gl-py-4 gl-mt-5 gl-mb-6 gl-border-t-1 gl-border-t-solid gl-border-b-1 gl-border-b-solid gl-border-gray-100"
>
<span>{{ s__('Integrations|This integration has multiple settings available.') }}</span>
<input name="service[inherit_from_id]" :value="override ? '' : inheritFromId" type="hidden" />
<gl-new-dropdown :text="selected.text">
<gl-new-dropdown-item
v-for="option in $options.dropdownOptions"
:key="option.value"
@click="onClick(option)"
>
{{ option.text }}
</gl-new-dropdown-item>
</gl-new-dropdown>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { startCase } from 'lodash';
import { __ } from '~/locale';
import { GlFormGroup, GlFormCheckbox, GlFormInput } from '@gitlab/ui';
......@@ -32,6 +33,7 @@ export default {
},
},
computed: {
...mapGetters(['isInheriting']),
placeholder() {
return placeholderForType[this.type];
},
......@@ -57,8 +59,8 @@ export default {
>
<div id="trigger-fields" class="gl-pt-3">
<gl-form-group v-for="event in events" :key="event.title" :description="event.description">
<input :name="checkboxName(event.name)" type="hidden" value="false" />
<gl-form-checkbox v-model="event.value" :name="checkboxName(event.name)">
<input :name="checkboxName(event.name)" type="hidden" :value="event.value || false" />
<gl-form-checkbox v-model="event.value" :disabled="isInheriting">
{{ startCase(event.title) }}
</gl-form-checkbox>
<gl-form-input
......@@ -66,6 +68,7 @@ export default {
v-model="event.field.value"
:name="fieldName(event.field.name)"
:placeholder="placeholder"
:readonly="isInheriting"
/>
</gl-form-group>
</div>
......
import Vue from 'vue';
import { createStore } from './store';
import { parseBoolean } from '~/lib/utils/common_utils';
import IntegrationForm from './components/integration_form.vue';
export default el => {
if (!el) {
return null;
}
function parseBooleanInData(data) {
const result = {};
Object.entries(data).forEach(([key, value]) => {
result[key] = parseBoolean(value);
});
return result;
}
function parseBooleanInData(data) {
const result = {};
Object.entries(data).forEach(([key, value]) => {
result[key] = parseBoolean(value);
});
return result;
}
function parseDatasetToProps(data) {
const {
id,
type,
commentDetail,
projectKey,
......@@ -23,8 +21,9 @@ export default el => {
editProjectPath,
triggerEvents,
fields,
inheritFromId,
...booleanAttributes
} = el.dataset;
} = data;
const {
showActive,
activated,
......@@ -35,33 +34,53 @@ export default el => {
enableJiraIssues,
} = parseBooleanInData(booleanAttributes);
return {
activeToggleProps: {
initialActivated: activated,
},
showActive,
type,
triggerFieldsProps: {
initialTriggerCommit: commitEvents,
initialTriggerMergeRequest: mergeRequestEvents,
initialEnableComments: enableComments,
initialCommentDetail: commentDetail,
},
jiraIssuesProps: {
showJiraIssuesIntegration,
initialEnableJiraIssues: enableJiraIssues,
initialProjectKey: projectKey,
upgradePlanPath,
editProjectPath,
},
triggerEvents: JSON.parse(triggerEvents),
fields: JSON.parse(fields),
inheritFromId: parseInt(inheritFromId, 10),
id: parseInt(id, 10),
};
}
export default (el, adminEl) => {
if (!el) {
return null;
}
const props = parseDatasetToProps(el.dataset);
const initialState = {
adminState: null,
customState: props,
};
if (adminEl) {
initialState.adminState = Object.freeze(parseDatasetToProps(adminEl.dataset));
}
return new Vue({
el,
store: createStore(initialState),
render(createElement) {
return createElement(IntegrationForm, {
props: {
activeToggleProps: {
initialActivated: activated,
},
showActive,
type,
triggerFieldsProps: {
initialTriggerCommit: commitEvents,
initialTriggerMergeRequest: mergeRequestEvents,
initialEnableComments: enableComments,
initialCommentDetail: commentDetail,
},
jiraIssuesProps: {
showJiraIssuesIntegration,
initialEnableJiraIssues: enableJiraIssues,
initialProjectKey: projectKey,
upgradePlanPath,
editProjectPath,
},
triggerEvents: JSON.parse(triggerEvents),
fields: JSON.parse(fields),
},
});
return createElement(IntegrationForm);
},
});
};
import * as types from './mutation_types';
// eslint-disable-next-line import/prefer-default-export
export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override);
export const isInheriting = state => (state.adminState === null ? false : !state.override);
export const propsSource = (state, getters) =>
getters.isInheriting ? state.adminState : state.customState;
export const currentKey = (state, getters) => (getters.isInheriting ? 'admin' : 'custom');
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
import createState from './state';
Vue.use(Vuex);
// eslint-disable-next-line import/prefer-default-export
export const createStore = (initialState = {}) =>
new Vuex.Store({
actions,
getters,
mutations,
state: createState(initialState),
});
// eslint-disable-next-line import/prefer-default-export
export const SET_OVERRIDE = 'SET_OVERRIDE';
import * as types from './mutation_types';
export default {
[types.SET_OVERRIDE](state, override) {
state.override = override;
},
};
export default ({ adminState = null, customState = {} } = {}) => {
const override = adminState !== null ? adminState.id !== customState.inheritFromId : false;
return {
override,
adminState,
customState,
};
};
......@@ -22,7 +22,10 @@ export default class IntegrationSettingsForm {
init() {
// Init Vue component
initForm(document.querySelector('.js-vue-integration-settings'));
initForm(
document.querySelector('.js-vue-integration-settings'),
document.querySelector('.js-vue-admin-integration-settings'),
);
eventHub.$on('toggle', active => {
this.formActive = active;
this.handleServiceToggle();
......
......@@ -31,6 +31,7 @@ module ServiceParams
:external_wiki_url,
:google_iap_service_account_json,
:google_iap_audience_client_id,
:inherit_from_id,
# We're using `issues_events` and `merge_requests_events`
# in the view so we still need to explicitly state them
# here. `Service#event_names` would only give
......
......@@ -21,10 +21,12 @@ class Projects::ServicesController < Projects::ApplicationController
layout "project_settings"
def edit
@admin_integration = Service.instance_for(service.type)
end
def update
@service.attributes = service_params[:service]
@service.inherit_from_id = nil if service_params[:service][:inherit_from_id].blank?
saved = @service.save(context: :manual_change)
......
......@@ -101,6 +101,7 @@ module ServicesHelper
def integration_form_data(integration)
{
id: integration.id,
show_active: integration.show_active_box?.to_s,
activated: (integration.active || integration.new_record?).to_s,
type: integration.to_param,
......@@ -109,7 +110,8 @@ module ServicesHelper
enable_comments: integration.comment_on_event_enabled.to_s,
comment_detail: integration.comment_detail,
trigger_events: trigger_events_for_service(integration),
fields: fields_for_service(integration)
fields: fields_for_service(integration),
inherit_from_id: integration.inherit_from_id
}
end
......
......@@ -364,6 +364,10 @@ class Service < ApplicationRecord
exists?(instance: true, type: type)
end
def self.instance_for(type)
find_by(instance: true, type: type)
end
# override if needed
def supports_data_fields?
false
......
......@@ -8,6 +8,8 @@
= markdown integration.help
.service-settings
- if @admin_integration
.js-vue-admin-integration-settings{ data: integration_form_data(@admin_integration) }
.js-vue-integration-settings{ data: integration_form_data(integration) }
- if show_service_trigger_events?(integration)
......
---
title: Add override selector for project-level integrations
merge_request: 34742
author:
type: added
......@@ -12748,6 +12748,15 @@ msgstr ""
msgid "Integrations|Standard"
msgstr ""
msgid "Integrations|This integration has multiple settings available."
msgstr ""
msgid "Integrations|Use custom settings"
msgstr ""
msgid "Integrations|Use instance level settings"
msgstr ""
msgid "Integrations|When a Jira issue is mentioned in a commit or merge request a remote link and comment (if enabled) will be created."
msgstr ""
......
......@@ -179,6 +179,23 @@ RSpec.describe Projects::ServicesController do
it_behaves_like 'service update'
end
context 'wehn param `inherit_from_id` is set to empty string' do
let(:service_params) { { inherit_from_id: '' } }
it 'sets inherit_from_id to nil' do
expect(service.reload.inherit_from_id).to eq(nil)
end
end
context 'wehn param `inherit_from_id` is set to some value' do
let(:instance_service) { create(:jira_service, :instance) }
let(:service_params) { { inherit_from_id: instance_service.id } }
it 'sets inherit_from_id to value' do
expect(service.reload.inherit_from_id).to eq(instance_service.id)
end
end
end
describe 'as JSON' do
......
......@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'Disable individual triggers', :js do
include_context 'project service activation'
let(:checkbox_selector) { 'input[type=checkbox][name$="_events]"]' }
let(:checkbox_selector) { 'input[name$="_events]"]' }
before do
visit_project_integration(service_name)
......@@ -18,7 +18,7 @@ RSpec.describe 'Disable individual triggers', :js do
event_count = HipchatService.supported_events.count
expect(page).to have_content "Trigger"
expect(page).to have_css(checkbox_selector, count: event_count)
expect(page).to have_css(checkbox_selector, visible: :all, count: event_count)
end
end
......@@ -27,7 +27,7 @@ RSpec.describe 'Disable individual triggers', :js do
it "doesn't show unnecessary Trigger checkboxes" do
expect(page).not_to have_content "Trigger"
expect(page).not_to have_css(checkbox_selector)
expect(page).not_to have_css(checkbox_selector, visible: :all)
end
end
end
import { mount } from '@vue/test-utils';
import ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
import { GlToggle } from '@gitlab/ui';
import ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
const GL_TOGGLE_ACTIVE_CLASS = 'is-checked';
const GL_TOGGLE_DISABLED_CLASS = 'is-disabled';
describe('ActiveToggle', () => {
let wrapper;
......@@ -11,9 +13,12 @@ describe('ActiveToggle', () => {
initialActivated: true,
};
const createComponent = props => {
const createComponent = (props = {}, isInheriting = false) => {
wrapper = mount(ActiveToggle, {
propsData: { ...defaultProps, ...props },
computed: {
isInheriting: () => isInheriting,
},
});
};
......@@ -29,6 +34,15 @@ describe('ActiveToggle', () => {
const findInputInToggle = () => findGlToggle().find('input');
describe('template', () => {
describe('is inheriting adminSettings', () => {
it('renders GlToggle as disabled', () => {
createComponent({}, true);
expect(findGlToggle().exists()).toBe(true);
expect(findButtonInToggle().classes()).toContain(GL_TOGGLE_DISABLED_CLASS);
});
});
describe('initialActivated is false', () => {
it('renders GlToggle as inactive', () => {
createComponent({
......
......@@ -14,9 +14,12 @@ describe('DynamicField', () => {
value: '1',
};
const createComponent = props => {
const createComponent = (props, isInheriting = false) => {
wrapper = mount(DynamicField, {
propsData: { ...defaultProps, ...props },
computed: {
isInheriting: () => isInheriting,
},
});
};
......@@ -34,108 +37,143 @@ describe('DynamicField', () => {
const findGlFormTextarea = () => wrapper.find(GlFormTextarea);
describe('template', () => {
describe('dynamic field', () => {
describe('type is checkbox', () => {
beforeEach(() => {
createComponent({
type: 'checkbox',
describe.each([[true, 'disabled', 'readonly'], [false, undefined, undefined]])(
'dynamic field, when isInheriting = `%p`',
(isInheriting, disabled, readonly) => {
describe('type is checkbox', () => {
beforeEach(() => {
createComponent(
{
type: 'checkbox',
},
isInheriting,
);
});
});
it('renders GlFormCheckbox', () => {
expect(findGlFormCheckbox().exists()).toBe(true);
});
it(`renders GlFormCheckbox, which ${isInheriting ? 'is' : 'is not'} disabled`, () => {
expect(findGlFormCheckbox().exists()).toBe(true);
expect(
findGlFormCheckbox()
.find('[type=checkbox]')
.attributes('disabled'),
).toBe(disabled);
});
it('does not render other types of input', () => {
expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false);
expect(findGlFormInput().exists()).toBe(false);
it('does not render other types of input', () => {
expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false);
expect(findGlFormInput().exists()).toBe(false);
});
});
});
describe('type is select', () => {
beforeEach(() => {
createComponent({
type: 'select',
choices: [['all', 'All details'], ['standard', 'Standard']],
describe('type is select', () => {
beforeEach(() => {
createComponent(
{
type: 'select',
choices: [['all', 'All details'], ['standard', 'Standard']],
},
isInheriting,
);
});
});
it('renders findGlFormSelect', () => {
expect(findGlFormSelect().exists()).toBe(true);
expect(findGlFormSelect().findAll('option')).toHaveLength(2);
});
it(`renders GlFormSelect, which ${isInheriting ? 'is' : 'is not'} disabled`, () => {
expect(findGlFormSelect().exists()).toBe(true);
expect(findGlFormSelect().findAll('option')).toHaveLength(2);
expect(
findGlFormSelect()
.find('select')
.attributes('disabled'),
).toBe(disabled);
});
it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false);
expect(findGlFormInput().exists()).toBe(false);
it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false);
expect(findGlFormInput().exists()).toBe(false);
});
});
});
describe('type is textarea', () => {
beforeEach(() => {
createComponent({
type: 'textarea',
describe('type is textarea', () => {
beforeEach(() => {
createComponent(
{
type: 'textarea',
},
isInheriting,
);
});
});
it('renders findGlFormTextarea', () => {
expect(findGlFormTextarea().exists()).toBe(true);
});
it(`renders GlFormTextarea, which ${isInheriting ? 'is' : 'is not'} readonly`, () => {
expect(findGlFormTextarea().exists()).toBe(true);
expect(
findGlFormTextarea()
.find('textarea')
.attributes('readonly'),
).toBe(readonly);
});
it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormInput().exists()).toBe(false);
it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormInput().exists()).toBe(false);
});
});
});
describe('type is password', () => {
beforeEach(() => {
createComponent({
type: 'password',
describe('type is password', () => {
beforeEach(() => {
createComponent(
{
type: 'password',
},
isInheriting,
);
});
});
it('renders GlFormInput', () => {
expect(findGlFormInput().exists()).toBe(true);
expect(findGlFormInput().attributes('type')).toBe('password');
});
it(`renders GlFormInput, which ${isInheriting ? 'is' : 'is not'} readonly`, () => {
expect(findGlFormInput().exists()).toBe(true);
expect(findGlFormInput().attributes('type')).toBe('password');
expect(findGlFormInput().attributes('readonly')).toBe(readonly);
});
it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false);
it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false);
});
});
});
describe('type is text', () => {
beforeEach(() => {
createComponent({
type: 'text',
required: true,
describe('type is text', () => {
beforeEach(() => {
createComponent(
{
type: 'text',
required: true,
},
isInheriting,
);
});
});
it('renders GlFormInput', () => {
expect(findGlFormInput().exists()).toBe(true);
expect(findGlFormInput().attributes()).toMatchObject({
type: 'text',
id: 'service_project_url',
name: 'service[project_url]',
placeholder: defaultProps.placeholder,
required: 'required',
it(`renders GlFormInput, which ${isInheriting ? 'is' : 'is not'} readonly`, () => {
expect(findGlFormInput().exists()).toBe(true);
expect(findGlFormInput().attributes()).toMatchObject({
type: 'text',
id: 'service_project_url',
name: 'service[project_url]',
placeholder: defaultProps.placeholder,
required: 'required',
});
expect(findGlFormInput().attributes('readonly')).toBe(readonly);
});
});
it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false);
it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false);
});
});
});
});
},
);
describe('help text', () => {
it('renders description with help text', () => {
......
import { shallowMount } from '@vue/test-utils';
import { createStore } from '~/integrations/edit/store';
import IntegrationForm from '~/integrations/edit/components/integration_form.vue';
import OverrideDropdown from '~/integrations/edit/components/override_dropdown.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';
import { mockIntegrationProps } from 'jest/integrations/edit/mock_data';
describe('IntegrationForm', () => {
let wrapper;
const defaultProps = {
activeToggleProps: {
initialActivated: true,
},
showActive: true,
triggerFieldsProps: {
initialTriggerCommit: false,
initialTriggerMergeRequest: false,
initialEnableComments: false,
},
jiraIssuesProps: {},
type: '',
};
const createComponent = (props, featureFlags = {}) => {
const createComponent = (customStateProps = {}, featureFlags = {}, initialState = {}) => {
wrapper = shallowMount(IntegrationForm, {
propsData: { ...defaultProps, ...props },
propsData: {},
store: createStore({
customState: { ...mockIntegrationProps, ...customStateProps },
...initialState,
}),
stubs: {
OverrideDropdown,
ActiveToggle,
JiraTriggerFields,
TriggerFields,
},
provide: {
glFeatures: featureFlags,
......@@ -43,6 +38,7 @@ describe('IntegrationForm', () => {
}
});
const findOverrideDropdown = () => wrapper.find(OverrideDropdown);
const findActiveToggle = () => wrapper.find(ActiveToggle);
const findJiraTriggerFields = () => wrapper.find(JiraTriggerFields);
const findJiraIssuesFields = () => wrapper.find(JiraIssuesFields);
......@@ -140,5 +136,35 @@ describe('IntegrationForm', () => {
});
});
});
describe('adminState state is null', () => {
it('does not render OverrideDropdown', () => {
createComponent(
{},
{},
{
adminState: null,
},
);
expect(findOverrideDropdown().exists()).toBe(false);
});
});
describe('adminState state is an object', () => {
it('renders OverrideDropdown', () => {
createComponent(
{},
{},
{
adminState: {
...mockIntegrationProps,
},
},
);
expect(findOverrideDropdown().exists()).toBe(true);
});
});
});
});
import { mount } from '@vue/test-utils';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import { GlFormCheckbox, GlFormInput } from '@gitlab/ui';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
describe('JiraIssuesFields', () => {
let wrapper;
......
import { mount } from '@vue/test-utils';
import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
import { GlFormCheckbox } from '@gitlab/ui';
import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
describe('JiraTriggerFields', () => {
let wrapper;
......@@ -11,9 +11,12 @@ describe('JiraTriggerFields', () => {
initialEnableComments: false,
};
const createComponent = props => {
const createComponent = (props, isInheriting = false) => {
wrapper = mount(JiraTriggerFields, {
propsData: { ...defaultProps, ...props },
computed: {
isInheriting: () => isInheriting,
},
});
};
......@@ -93,5 +96,23 @@ describe('JiraTriggerFields', () => {
expect(findCommentDetail().isVisible()).toBe(true);
});
});
it('disables checkboxes and radios if inheriting', () => {
createComponent(
{
initialTriggerCommit: true,
initialEnableComments: true,
},
true,
);
wrapper.findAll('[type=checkbox]').wrappers.forEach(checkbox => {
expect(checkbox.attributes('disabled')).toBe('disabled');
});
wrapper.findAll('[type=radio]').wrappers.forEach(radio => {
expect(radio.attributes('disabled')).toBe('disabled');
});
});
});
});
......@@ -9,9 +9,12 @@ describe('TriggerFields', () => {
type: 'slack',
};
const createComponent = props => {
const createComponent = (props, isInheriting = false) => {
wrapper = mount(TriggerFields, {
propsData: { ...defaultProps, ...props },
computed: {
isInheriting: () => isInheriting,
},
});
};
......@@ -22,10 +25,11 @@ describe('TriggerFields', () => {
}
});
const findAllGlFormGroups = () => wrapper.find('#trigger-fields').findAll(GlFormGroup);
const findAllGlFormCheckboxes = () => wrapper.findAll(GlFormCheckbox);
const findAllGlFormInputs = () => wrapper.findAll(GlFormInput);
describe('template', () => {
describe.each([true, false])('template, isInheriting = `%p`', isInheriting => {
it('renders a label with text "Trigger"', () => {
createComponent();
......@@ -51,9 +55,12 @@ describe('TriggerFields', () => {
];
beforeEach(() => {
createComponent({
events,
});
createComponent(
{
events,
},
isInheriting,
);
});
it('does not render GlFormInput for each event', () => {
......@@ -69,8 +76,10 @@ describe('TriggerFields', () => {
});
});
it('renders GlFormCheckbox for each event', () => {
const checkboxes = findAllGlFormCheckboxes();
it(`renders GlFormCheckbox and corresponding hidden input for each event, which ${
isInheriting ? 'is' : 'is not'
} disabled`, () => {
const checkboxes = findAllGlFormGroups();
const expectedResults = [
{ labelText: 'Push', inputName: 'service[push_event]' },
{ labelText: 'Merge Request', inputName: 'service[merge_requests_event]' },
......@@ -78,14 +87,22 @@ describe('TriggerFields', () => {
expect(checkboxes).toHaveLength(2);
checkboxes.wrappers.forEach((checkbox, index) => {
const checkBox = checkbox.find(GlFormCheckbox);
expect(checkbox.find('label').text()).toBe(expectedResults[index].labelText);
expect(checkbox.find('input').attributes('name')).toBe(expectedResults[index].inputName);
expect(checkbox.vm.$attrs.checked).toBe(events[index].value);
expect(checkbox.find('[type=hidden]').attributes('name')).toBe(
expectedResults[index].inputName,
);
expect(checkbox.find('[type=hidden]').attributes('value')).toBe(
events[index].value.toString(),
);
expect(checkBox.vm.$attrs.disabled).toBe(isInheriting);
expect(checkBox.vm.$attrs.checked).toBe(events[index].value);
});
});
});
describe('events with field property', () => {
describe('events with field property, isInheriting = `%p`', () => {
const events = [
{
field: {
......@@ -102,16 +119,21 @@ describe('TriggerFields', () => {
];
beforeEach(() => {
createComponent({
events,
});
createComponent(
{
events,
},
isInheriting,
);
});
it('renders GlFormCheckbox for each event', () => {
expect(findAllGlFormCheckboxes()).toHaveLength(2);
});
it('renders GlFormInput for each event', () => {
it(`renders GlFormInput for each event, which ${
isInheriting ? 'is' : 'is not'
} readonly`, () => {
const fields = findAllGlFormInputs();
const expectedResults = [
{
......@@ -128,6 +150,7 @@ describe('TriggerFields', () => {
fields.wrappers.forEach((field, index) => {
expect(field.attributes()).toMatchObject(expectedResults[index]);
expect(field.vm.$attrs.readonly).toBe(isInheriting);
expect(field.vm.$attrs.value).toBe(events[index].field.value);
});
});
......
// eslint-disable-next-line import/prefer-default-export
export const mockIntegrationProps = {
id: 25,
activeToggleProps: {
initialActivated: true,
},
showActive: true,
triggerFieldsProps: {
initialTriggerCommit: false,
initialTriggerMergeRequest: false,
initialEnableComments: false,
},
jiraIssuesProps: {},
triggerEvents: [],
fields: [],
type: '',
inheritFromId: 25,
};
import createState from '~/integrations/edit/store/state';
import { setOverride } from '~/integrations/edit/store/actions';
import * as types from '~/integrations/edit/store/mutation_types';
import testAction from 'helpers/vuex_action_helper';
describe('Integration form store actions', () => {
let state;
beforeEach(() => {
state = createState();
});
describe('setOverride', () => {
it('should commit override mutation', () => {
return testAction(setOverride, true, state, [{ type: types.SET_OVERRIDE, payload: true }]);
});
});
});
import { currentKey, isInheriting, propsSource } from '~/integrations/edit/store/getters';
import createState from '~/integrations/edit/store/state';
import { mockIntegrationProps } from '../mock_data';
describe('Integration form store getters', () => {
let state;
const customState = { ...mockIntegrationProps, type: 'CustomState' };
const adminState = { ...mockIntegrationProps, type: 'AdminState' };
beforeEach(() => {
state = createState({ customState });
});
describe('isInheriting', () => {
describe('when adminState is null', () => {
it('returns false', () => {
expect(isInheriting(state)).toBe(false);
});
});
describe('when adminState is an object', () => {
beforeEach(() => {
state.adminState = adminState;
});
describe('when override is false', () => {
beforeEach(() => {
state.override = false;
});
it('returns false', () => {
expect(isInheriting(state)).toBe(true);
});
});
describe('when override is true', () => {
beforeEach(() => {
state.override = true;
});
it('returns true', () => {
expect(isInheriting(state)).toBe(false);
});
});
});
});
describe('propsSource', () => {
beforeEach(() => {
state.adminState = adminState;
});
it('equals adminState if inheriting', () => {
expect(propsSource(state, { isInheriting: true })).toEqual(adminState);
});
it('equals customState if not inheriting', () => {
expect(propsSource(state, { isInheriting: false })).toEqual(customState);
});
});
describe('currentKey', () => {
it('equals `admin` if inheriting', () => {
expect(currentKey(state, { isInheriting: true })).toEqual('admin');
});
it('equals `custom` if not inheriting', () => {
expect(currentKey(state, { isInheriting: false })).toEqual('custom');
});
});
});
import mutations from '~/integrations/edit/store/mutations';
import createState from '~/integrations/edit/store/state';
import * as types from '~/integrations/edit/store/mutation_types';
describe('Integration form store mutations', () => {
let state;
beforeEach(() => {
state = createState();
});
describe(`${types.SET_OVERRIDE}`, () => {
it('sets override', () => {
mutations[types.SET_OVERRIDE](state, true);
expect(state.override).toBe(true);
});
});
});
import createState from '~/integrations/edit/store/state';
describe('Integration form state factory', () => {
it('states default to null', () => {
expect(createState()).toEqual({
adminState: null,
customState: {},
override: false,
});
});
describe('override is initialized correctly', () => {
it.each([
[{ id: 25 }, { inheritFromId: null }, true],
[{ id: 25 }, { inheritFromId: 27 }, true],
[{ id: 25 }, { inheritFromId: 25 }, false],
[null, { inheritFromId: null }, false],
[null, { inheritFromId: 25 }, false],
])(
'for adminState: %p, customState: %p: override = `%p`',
(adminState, customState, expected) => {
expect(createState({ adminState, customState }).override).toEqual(expected);
},
);
});
});
......@@ -7,4 +7,28 @@ RSpec.describe ServicesHelper do
it { expect(event_action_title('comment')).to eq 'Comment' }
it { expect(event_action_title('something')).to eq 'Something' }
end
describe '#integration_form_data' do
subject { helper.integration_form_data(integration) }
context 'Jira service' do
let(:integration) { build(:jira_service) }
it 'includes Jira specific fields' do
is_expected.to include(
:id,
:show_active,
:activated,
:type,
:merge_request_events,
:commit_events,
:enable_comments,
:comment_detail,
:trigger_events,
:fields,
:inherit_from_id
)
end
end
end
end
......@@ -386,6 +386,33 @@ RSpec.describe Service do
end
end
describe 'instance' do
describe '.instance_for' do
let_it_be(:jira_service) { create(:jira_service, :instance) }
let_it_be(:slack_service) { create(:slack_service, :instance) }
subject { described_class.instance_for(type) }
context 'Hipchat serivce' do
let(:type) { 'HipchatService' }
it { is_expected.to eq(nil) }
end
context 'Jira serivce' do
let(:type) { 'JiraService' }
it { is_expected.to eq(jira_service) }
end
context 'Slack serivce' do
let(:type) { 'SlackService' }
it { is_expected.to eq(slack_service) }
end
end
end
describe "{property}_changed?" do
let(:service) do
BambooService.create(
......
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