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> <script>
import { mapGetters } from 'vuex';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { GlFormGroup, GlToggle } from '@gitlab/ui'; import { GlFormGroup, GlToggle } from '@gitlab/ui';
...@@ -21,6 +22,9 @@ export default { ...@@ -21,6 +22,9 @@ export default {
activated: this.initialActivated, activated: this.initialActivated,
}; };
}, },
computed: {
...mapGetters(['isInheriting']),
},
mounted() { mounted() {
// Initialize view // Initialize view
this.$nextTick(() => { this.$nextTick(() => {
...@@ -42,6 +46,7 @@ export default { ...@@ -42,6 +46,7 @@ export default {
v-model="activated" v-model="activated"
name="service[active]" name="service[active]"
class="gl-display-block gl-line-height-0" class="gl-display-block gl-line-height-0"
:disabled="isInheriting"
@change="onToggle" @change="onToggle"
/> />
</gl-form-group> </gl-form-group>
...@@ -50,7 +55,12 @@ export default { ...@@ -50,7 +55,12 @@ export default {
<div class="form-group row" role="group"> <div class="form-group row" role="group">
<label for="service[active]" class="col-form-label col-sm-2">{{ __('Active') }}</label> <label for="service[active]" class="col-form-label col-sm-2">{{ __('Active') }}</label>
<div class="col-sm-10 pt-1"> <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> </div>
</div> </div>
......
<script> <script>
import { mapGetters } from 'vuex';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { capitalize, lowerCase, isEmpty } from 'lodash'; import { capitalize, lowerCase, isEmpty } from 'lodash';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
...@@ -59,6 +60,7 @@ export default { ...@@ -59,6 +60,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapGetters(['isInheriting']),
isCheckbox() { isCheckbox() {
return this.type === 'checkbox'; return this.type === 'checkbox';
}, },
...@@ -107,6 +109,7 @@ export default { ...@@ -107,6 +109,7 @@ export default {
id: this.fieldId, id: this.fieldId,
name: this.fieldName, name: this.fieldName,
state: this.valid, state: this.valid,
readonly: this.isInheriting,
}; };
}, },
valid() { valid() {
...@@ -142,12 +145,15 @@ export default { ...@@ -142,12 +145,15 @@ export default {
</template> </template>
<template v-if="isCheckbox"> <template v-if="isCheckbox">
<input :name="fieldName" type="hidden" value="false" /> <input :name="fieldName" type="hidden" :value="model || false" />
<gl-form-checkbox v-model="model" v-bind="sharedProps"> <gl-form-checkbox :id="fieldId" v-model="model" :disabled="isInheriting">
{{ humanizedTitle }} {{ humanizedTitle }}
</gl-form-checkbox> </gl-form-checkbox>
</template> </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 <gl-form-textarea
v-else-if="isTextarea" v-else-if="isTextarea"
v-model="model" v-model="model"
......
<script> <script>
import { mapState, mapActions, mapGetters } from 'vuex';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import OverrideDropdown from './override_dropdown.vue';
import ActiveToggle from './active_toggle.vue'; import ActiveToggle from './active_toggle.vue';
import JiraTriggerFields from './jira_trigger_fields.vue'; import JiraTriggerFields from './jira_trigger_fields.vue';
import JiraIssuesFields from './jira_issues_fields.vue'; import JiraIssuesFields from './jira_issues_fields.vue';
...@@ -9,6 +12,7 @@ import DynamicField from './dynamic_field.vue'; ...@@ -9,6 +12,7 @@ import DynamicField from './dynamic_field.vue';
export default { export default {
name: 'IntegrationForm', name: 'IntegrationForm',
components: { components: {
OverrideDropdown,
ActiveToggle, ActiveToggle,
JiraTriggerFields, JiraTriggerFields,
JiraIssuesFields, JiraIssuesFields,
...@@ -16,55 +20,55 @@ export default { ...@@ -16,55 +20,55 @@ export default {
DynamicField, DynamicField,
}, },
mixins: [glFeatureFlagsMixin()], 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: { computed: {
...mapGetters(['currentKey', 'propsSource']),
...mapState(['adminState', 'override']),
isJira() { isJira() {
return this.type === 'jira'; return this.propsSource.type === 'jira';
}, },
showJiraIssuesFields() { showJiraIssuesFields() {
return this.isJira && this.glFeatures.jiraIssuesIntegration; return this.isJira && this.glFeatures.jiraIssuesIntegration;
}, },
}, },
methods: {
...mapActions(['setOverride']),
},
}; };
</script> </script>
<template> <template>
<div> <div>
<active-toggle v-if="showActive" v-bind="activeToggleProps" /> <override-dropdown
<jira-trigger-fields v-if="isJira" v-bind="triggerFieldsProps" /> v-if="adminState !== null"
<trigger-fields v-else-if="triggerEvents.length" :events="triggerEvents" :type="type" /> :inherit-from-id="adminState.id"
<dynamic-field v-for="field in fields" :key="field.name" v-bind="field" /> :override="override"
<jira-issues-fields v-if="showJiraIssuesFields" v-bind="jiraIssuesProps" /> @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> </div>
</template> </template>
...@@ -89,8 +89,8 @@ export default { ...@@ -89,8 +89,8 @@ export default {
}} }}
</p> </p>
<template v-if="showJiraIssuesIntegration"> <template v-if="showJiraIssuesIntegration">
<input name="service[issues_enabled]" type="hidden" value="false" /> <input name="service[issues_enabled]" type="hidden" :value="enableJiraIssues || false" />
<gl-form-checkbox v-model="enableJiraIssues" name="service[issues_enabled]"> <gl-form-checkbox v-model="enableJiraIssues">
{{ s__('JiraService|Enable Jira issues') }} {{ s__('JiraService|Enable Jira issues') }}
<template #help> <template #help>
{{ {{
......
<script> <script>
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { mapGetters } from 'vuex';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { GlFormGroup, GlFormCheckbox, GlFormRadio } from '@gitlab/ui'; import { GlFormGroup, GlFormCheckbox, GlFormRadio } from '@gitlab/ui';
...@@ -55,6 +56,7 @@ export default { ...@@ -55,6 +56,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapGetters(['isInheriting']),
showEnableComments() { showEnableComments() {
return this.triggerCommit || this.triggerMergeRequest; return this.triggerCommit || this.triggerMergeRequest;
}, },
...@@ -73,13 +75,17 @@ export default { ...@@ -73,13 +75,17 @@ export default {
) )
" "
> >
<input name="service[commit_events]" type="hidden" value="false" /> <input name="service[commit_events]" type="hidden" :value="triggerCommit || false" />
<gl-form-checkbox v-model="triggerCommit" name="service[commit_events]"> <gl-form-checkbox v-model="triggerCommit" :disabled="isInheriting">
{{ __('Commit') }} {{ __('Commit') }}
</gl-form-checkbox> </gl-form-checkbox>
<input name="service[merge_requests_events]" type="hidden" value="false" /> <input
<gl-form-checkbox v-model="triggerMergeRequest" name="service[merge_requests_events]"> name="service[merge_requests_events]"
type="hidden"
:value="triggerMergeRequest || false"
/>
<gl-form-checkbox v-model="triggerMergeRequest" :disabled="isInheriting">
{{ __('Merge request') }} {{ __('Merge request') }}
</gl-form-checkbox> </gl-form-checkbox>
</gl-form-group> </gl-form-group>
...@@ -89,8 +95,12 @@ export default { ...@@ -89,8 +95,12 @@ export default {
:label="s__('Integrations|Comment settings:')" :label="s__('Integrations|Comment settings:')"
data-testid="comment-settings" data-testid="comment-settings"
> >
<input name="service[comment_on_event_enabled]" type="hidden" value="false" /> <input
<gl-form-checkbox v-model="enableComments" name="service[comment_on_event_enabled]"> name="service[comment_on_event_enabled]"
type="hidden"
:value="enableComments || false"
/>
<gl-form-checkbox v-model="enableComments" :disabled="isInheriting">
{{ s__('Integrations|Enable comments') }} {{ s__('Integrations|Enable comments') }}
</gl-form-checkbox> </gl-form-checkbox>
</gl-form-group> </gl-form-group>
...@@ -100,12 +110,18 @@ export default { ...@@ -100,12 +110,18 @@ export default {
:label="s__('Integrations|Comment detail:')" :label="s__('Integrations|Comment detail:')"
data-testid="comment-detail" data-testid="comment-detail"
> >
<input
v-if="isInheriting"
name="service[comment_detail]"
type="hidden"
:value="commentDetail"
/>
<gl-form-radio <gl-form-radio
v-for="commentDetailOption in commentDetailOptions" v-for="commentDetailOption in commentDetailOptions"
:key="commentDetailOption.value" :key="commentDetailOption.value"
v-model="commentDetail" v-model="commentDetail"
:value="commentDetailOption.value" :value="commentDetailOption.value"
name="service[comment_detail]" :disabled="isInheriting"
> >
{{ commentDetailOption.label }} {{ commentDetailOption.label }}
<template #help> <template #help>
...@@ -126,13 +142,17 @@ export default { ...@@ -126,13 +142,17 @@ export default {
}} }}
</label> </label>
<input name="service[commit_events]" type="hidden" value="false" /> <input name="service[commit_events]" type="hidden" :value="triggerCommit || false" />
<gl-form-checkbox v-model="triggerCommit" name="service[commit_events]"> <gl-form-checkbox v-model="triggerCommit" :disabled="isInheriting">
{{ __('Commit') }} {{ __('Commit') }}
</gl-form-checkbox> </gl-form-checkbox>
<input name="service[merge_requests_events]" type="hidden" value="false" /> <input
<gl-form-checkbox v-model="triggerMergeRequest" name="service[merge_requests_events]"> name="service[merge_requests_events]"
type="hidden"
:value="triggerMergeRequest || false"
/>
<gl-form-checkbox v-model="triggerMergeRequest" :disabled="isInheriting">
{{ __('Merge request') }} {{ __('Merge request') }}
</gl-form-checkbox> </gl-form-checkbox>
...@@ -144,8 +164,12 @@ export default { ...@@ -144,8 +164,12 @@ export default {
<label> <label>
{{ s__('Integrations|Comment settings:') }} {{ s__('Integrations|Comment settings:') }}
</label> </label>
<input name="service[comment_on_event_enabled]" type="hidden" value="false" /> <input
<gl-form-checkbox v-model="enableComments" name="service[comment_on_event_enabled]"> name="service[comment_on_event_enabled]"
type="hidden"
:value="enableComments || false"
/>
<gl-form-checkbox v-model="enableComments" :disabled="isInheriting">
{{ s__('Integrations|Enable comments') }} {{ s__('Integrations|Enable comments') }}
</gl-form-checkbox> </gl-form-checkbox>
...@@ -153,12 +177,18 @@ export default { ...@@ -153,12 +177,18 @@ export default {
<label> <label>
{{ s__('Integrations|Comment detail:') }} {{ s__('Integrations|Comment detail:') }}
</label> </label>
<input
v-if="isInheriting"
name="service[comment_detail]"
type="hidden"
:value="commentDetail"
/>
<gl-form-radio <gl-form-radio
v-for="commentDetailOption in commentDetailOptions" v-for="commentDetailOption in commentDetailOptions"
:key="commentDetailOption.value" :key="commentDetailOption.value"
v-model="commentDetail" v-model="commentDetail"
:value="commentDetailOption.value" :value="commentDetailOption.value"
name="service[comment_detail]" :disabled="isInheriting"
> >
{{ commentDetailOption.label }} {{ commentDetailOption.label }}
<template #help> <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> <script>
import { mapGetters } from 'vuex';
import { startCase } from 'lodash'; import { startCase } from 'lodash';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { GlFormGroup, GlFormCheckbox, GlFormInput } from '@gitlab/ui'; import { GlFormGroup, GlFormCheckbox, GlFormInput } from '@gitlab/ui';
...@@ -32,6 +33,7 @@ export default { ...@@ -32,6 +33,7 @@ export default {
}, },
}, },
computed: { computed: {
...mapGetters(['isInheriting']),
placeholder() { placeholder() {
return placeholderForType[this.type]; return placeholderForType[this.type];
}, },
...@@ -57,8 +59,8 @@ export default { ...@@ -57,8 +59,8 @@ export default {
> >
<div id="trigger-fields" class="gl-pt-3"> <div id="trigger-fields" class="gl-pt-3">
<gl-form-group v-for="event in events" :key="event.title" :description="event.description"> <gl-form-group v-for="event in events" :key="event.title" :description="event.description">
<input :name="checkboxName(event.name)" type="hidden" value="false" /> <input :name="checkboxName(event.name)" type="hidden" :value="event.value || false" />
<gl-form-checkbox v-model="event.value" :name="checkboxName(event.name)"> <gl-form-checkbox v-model="event.value" :disabled="isInheriting">
{{ startCase(event.title) }} {{ startCase(event.title) }}
</gl-form-checkbox> </gl-form-checkbox>
<gl-form-input <gl-form-input
...@@ -66,6 +68,7 @@ export default { ...@@ -66,6 +68,7 @@ export default {
v-model="event.field.value" v-model="event.field.value"
:name="fieldName(event.field.name)" :name="fieldName(event.field.name)"
:placeholder="placeholder" :placeholder="placeholder"
:readonly="isInheriting"
/> />
</gl-form-group> </gl-form-group>
</div> </div>
......
import Vue from 'vue'; import Vue from 'vue';
import { createStore } from './store';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import IntegrationForm from './components/integration_form.vue'; import IntegrationForm from './components/integration_form.vue';
export default el => { function parseBooleanInData(data) {
if (!el) { const result = {};
return null; Object.entries(data).forEach(([key, value]) => {
} result[key] = parseBoolean(value);
});
function parseBooleanInData(data) { return result;
const result = {}; }
Object.entries(data).forEach(([key, value]) => {
result[key] = parseBoolean(value);
});
return result;
}
function parseDatasetToProps(data) {
const { const {
id,
type, type,
commentDetail, commentDetail,
projectKey, projectKey,
...@@ -23,8 +21,9 @@ export default el => { ...@@ -23,8 +21,9 @@ export default el => {
editProjectPath, editProjectPath,
triggerEvents, triggerEvents,
fields, fields,
inheritFromId,
...booleanAttributes ...booleanAttributes
} = el.dataset; } = data;
const { const {
showActive, showActive,
activated, activated,
...@@ -35,33 +34,53 @@ export default el => { ...@@ -35,33 +34,53 @@ export default el => {
enableJiraIssues, enableJiraIssues,
} = parseBooleanInData(booleanAttributes); } = 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({ return new Vue({
el, el,
store: createStore(initialState),
render(createElement) { render(createElement) {
return createElement(IntegrationForm, { 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),
},
});
}, },
}); });
}; };
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 { ...@@ -22,7 +22,10 @@ export default class IntegrationSettingsForm {
init() { init() {
// Init Vue component // 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 => { eventHub.$on('toggle', active => {
this.formActive = active; this.formActive = active;
this.handleServiceToggle(); this.handleServiceToggle();
......
...@@ -31,6 +31,7 @@ module ServiceParams ...@@ -31,6 +31,7 @@ module ServiceParams
:external_wiki_url, :external_wiki_url,
:google_iap_service_account_json, :google_iap_service_account_json,
:google_iap_audience_client_id, :google_iap_audience_client_id,
:inherit_from_id,
# We're using `issues_events` and `merge_requests_events` # We're using `issues_events` and `merge_requests_events`
# in the view so we still need to explicitly state them # in the view so we still need to explicitly state them
# here. `Service#event_names` would only give # here. `Service#event_names` would only give
......
...@@ -21,10 +21,12 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -21,10 +21,12 @@ class Projects::ServicesController < Projects::ApplicationController
layout "project_settings" layout "project_settings"
def edit def edit
@admin_integration = Service.instance_for(service.type)
end end
def update def update
@service.attributes = service_params[:service] @service.attributes = service_params[:service]
@service.inherit_from_id = nil if service_params[:service][:inherit_from_id].blank?
saved = @service.save(context: :manual_change) saved = @service.save(context: :manual_change)
......
...@@ -101,6 +101,7 @@ module ServicesHelper ...@@ -101,6 +101,7 @@ module ServicesHelper
def integration_form_data(integration) def integration_form_data(integration)
{ {
id: integration.id,
show_active: integration.show_active_box?.to_s, show_active: integration.show_active_box?.to_s,
activated: (integration.active || integration.new_record?).to_s, activated: (integration.active || integration.new_record?).to_s,
type: integration.to_param, type: integration.to_param,
...@@ -109,7 +110,8 @@ module ServicesHelper ...@@ -109,7 +110,8 @@ module ServicesHelper
enable_comments: integration.comment_on_event_enabled.to_s, enable_comments: integration.comment_on_event_enabled.to_s,
comment_detail: integration.comment_detail, comment_detail: integration.comment_detail,
trigger_events: trigger_events_for_service(integration), 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 end
......
...@@ -364,6 +364,10 @@ class Service < ApplicationRecord ...@@ -364,6 +364,10 @@ class Service < ApplicationRecord
exists?(instance: true, type: type) exists?(instance: true, type: type)
end end
def self.instance_for(type)
find_by(instance: true, type: type)
end
# override if needed # override if needed
def supports_data_fields? def supports_data_fields?
false false
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
= markdown integration.help = markdown integration.help
.service-settings .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) } .js-vue-integration-settings{ data: integration_form_data(integration) }
- if show_service_trigger_events?(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 "" ...@@ -12748,6 +12748,15 @@ msgstr ""
msgid "Integrations|Standard" msgid "Integrations|Standard"
msgstr "" 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." 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 "" msgstr ""
......
...@@ -179,6 +179,23 @@ RSpec.describe Projects::ServicesController do ...@@ -179,6 +179,23 @@ RSpec.describe Projects::ServicesController do
it_behaves_like 'service update' it_behaves_like 'service update'
end 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 end
describe 'as JSON' do describe 'as JSON' do
......
...@@ -5,7 +5,7 @@ require 'spec_helper' ...@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'Disable individual triggers', :js do RSpec.describe 'Disable individual triggers', :js do
include_context 'project service activation' include_context 'project service activation'
let(:checkbox_selector) { 'input[type=checkbox][name$="_events]"]' } let(:checkbox_selector) { 'input[name$="_events]"]' }
before do before do
visit_project_integration(service_name) visit_project_integration(service_name)
...@@ -18,7 +18,7 @@ RSpec.describe 'Disable individual triggers', :js do ...@@ -18,7 +18,7 @@ RSpec.describe 'Disable individual triggers', :js do
event_count = HipchatService.supported_events.count event_count = HipchatService.supported_events.count
expect(page).to have_content "Trigger" 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
end end
...@@ -27,7 +27,7 @@ RSpec.describe 'Disable individual triggers', :js do ...@@ -27,7 +27,7 @@ RSpec.describe 'Disable individual triggers', :js do
it "doesn't show unnecessary Trigger checkboxes" do it "doesn't show unnecessary Trigger checkboxes" do
expect(page).not_to have_content "Trigger" 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 end
end end
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
import { GlToggle } from '@gitlab/ui'; import { GlToggle } from '@gitlab/ui';
import ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
const GL_TOGGLE_ACTIVE_CLASS = 'is-checked'; const GL_TOGGLE_ACTIVE_CLASS = 'is-checked';
const GL_TOGGLE_DISABLED_CLASS = 'is-disabled';
describe('ActiveToggle', () => { describe('ActiveToggle', () => {
let wrapper; let wrapper;
...@@ -11,9 +13,12 @@ describe('ActiveToggle', () => { ...@@ -11,9 +13,12 @@ describe('ActiveToggle', () => {
initialActivated: true, initialActivated: true,
}; };
const createComponent = props => { const createComponent = (props = {}, isInheriting = false) => {
wrapper = mount(ActiveToggle, { wrapper = mount(ActiveToggle, {
propsData: { ...defaultProps, ...props }, propsData: { ...defaultProps, ...props },
computed: {
isInheriting: () => isInheriting,
},
}); });
}; };
...@@ -29,6 +34,15 @@ describe('ActiveToggle', () => { ...@@ -29,6 +34,15 @@ describe('ActiveToggle', () => {
const findInputInToggle = () => findGlToggle().find('input'); const findInputInToggle = () => findGlToggle().find('input');
describe('template', () => { 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', () => { describe('initialActivated is false', () => {
it('renders GlToggle as inactive', () => { it('renders GlToggle as inactive', () => {
createComponent({ createComponent({
......
...@@ -14,9 +14,12 @@ describe('DynamicField', () => { ...@@ -14,9 +14,12 @@ describe('DynamicField', () => {
value: '1', value: '1',
}; };
const createComponent = props => { const createComponent = (props, isInheriting = false) => {
wrapper = mount(DynamicField, { wrapper = mount(DynamicField, {
propsData: { ...defaultProps, ...props }, propsData: { ...defaultProps, ...props },
computed: {
isInheriting: () => isInheriting,
},
}); });
}; };
...@@ -34,108 +37,143 @@ describe('DynamicField', () => { ...@@ -34,108 +37,143 @@ describe('DynamicField', () => {
const findGlFormTextarea = () => wrapper.find(GlFormTextarea); const findGlFormTextarea = () => wrapper.find(GlFormTextarea);
describe('template', () => { describe('template', () => {
describe('dynamic field', () => { describe.each([[true, 'disabled', 'readonly'], [false, undefined, undefined]])(
describe('type is checkbox', () => { 'dynamic field, when isInheriting = `%p`',
beforeEach(() => { (isInheriting, disabled, readonly) => {
createComponent({ describe('type is checkbox', () => {
type: 'checkbox', beforeEach(() => {
createComponent(
{
type: 'checkbox',
},
isInheriting,
);
}); });
});
it('renders GlFormCheckbox', () => { it(`renders GlFormCheckbox, which ${isInheriting ? 'is' : 'is not'} disabled`, () => {
expect(findGlFormCheckbox().exists()).toBe(true); expect(findGlFormCheckbox().exists()).toBe(true);
}); expect(
findGlFormCheckbox()
.find('[type=checkbox]')
.attributes('disabled'),
).toBe(disabled);
});
it('does not render other types of input', () => { it('does not render other types of input', () => {
expect(findGlFormSelect().exists()).toBe(false); expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false); expect(findGlFormTextarea().exists()).toBe(false);
expect(findGlFormInput().exists()).toBe(false); expect(findGlFormInput().exists()).toBe(false);
});
}); });
});
describe('type is select', () => { describe('type is select', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent(
type: 'select', {
choices: [['all', 'All details'], ['standard', 'Standard']], type: 'select',
choices: [['all', 'All details'], ['standard', 'Standard']],
},
isInheriting,
);
}); });
});
it('renders findGlFormSelect', () => { it(`renders GlFormSelect, which ${isInheriting ? 'is' : 'is not'} disabled`, () => {
expect(findGlFormSelect().exists()).toBe(true); expect(findGlFormSelect().exists()).toBe(true);
expect(findGlFormSelect().findAll('option')).toHaveLength(2); expect(findGlFormSelect().findAll('option')).toHaveLength(2);
}); expect(
findGlFormSelect()
.find('select')
.attributes('disabled'),
).toBe(disabled);
});
it('does not render other types of input', () => { it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false); expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false); expect(findGlFormTextarea().exists()).toBe(false);
expect(findGlFormInput().exists()).toBe(false); expect(findGlFormInput().exists()).toBe(false);
});
}); });
});
describe('type is textarea', () => { describe('type is textarea', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent(
type: 'textarea', {
type: 'textarea',
},
isInheriting,
);
}); });
});
it('renders findGlFormTextarea', () => { it(`renders GlFormTextarea, which ${isInheriting ? 'is' : 'is not'} readonly`, () => {
expect(findGlFormTextarea().exists()).toBe(true); expect(findGlFormTextarea().exists()).toBe(true);
}); expect(
findGlFormTextarea()
.find('textarea')
.attributes('readonly'),
).toBe(readonly);
});
it('does not render other types of input', () => { it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false); expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormSelect().exists()).toBe(false); expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormInput().exists()).toBe(false); expect(findGlFormInput().exists()).toBe(false);
});
}); });
});
describe('type is password', () => { describe('type is password', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent(
type: 'password', {
type: 'password',
},
isInheriting,
);
}); });
});
it('renders GlFormInput', () => { it(`renders GlFormInput, which ${isInheriting ? 'is' : 'is not'} readonly`, () => {
expect(findGlFormInput().exists()).toBe(true); expect(findGlFormInput().exists()).toBe(true);
expect(findGlFormInput().attributes('type')).toBe('password'); expect(findGlFormInput().attributes('type')).toBe('password');
}); expect(findGlFormInput().attributes('readonly')).toBe(readonly);
});
it('does not render other types of input', () => { it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false); expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormSelect().exists()).toBe(false); expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false); expect(findGlFormTextarea().exists()).toBe(false);
});
}); });
});
describe('type is text', () => { describe('type is text', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent(
type: 'text', {
required: true, type: 'text',
required: true,
},
isInheriting,
);
}); });
});
it('renders GlFormInput', () => { it(`renders GlFormInput, which ${isInheriting ? 'is' : 'is not'} readonly`, () => {
expect(findGlFormInput().exists()).toBe(true); expect(findGlFormInput().exists()).toBe(true);
expect(findGlFormInput().attributes()).toMatchObject({ expect(findGlFormInput().attributes()).toMatchObject({
type: 'text', type: 'text',
id: 'service_project_url', id: 'service_project_url',
name: 'service[project_url]', name: 'service[project_url]',
placeholder: defaultProps.placeholder, placeholder: defaultProps.placeholder,
required: 'required', required: 'required',
});
expect(findGlFormInput().attributes('readonly')).toBe(readonly);
}); });
});
it('does not render other types of input', () => { it('does not render other types of input', () => {
expect(findGlFormCheckbox().exists()).toBe(false); expect(findGlFormCheckbox().exists()).toBe(false);
expect(findGlFormSelect().exists()).toBe(false); expect(findGlFormSelect().exists()).toBe(false);
expect(findGlFormTextarea().exists()).toBe(false); expect(findGlFormTextarea().exists()).toBe(false);
});
}); });
}); },
}); );
describe('help text', () => { describe('help text', () => {
it('renders description with help text', () => { it('renders description with help text', () => {
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { createStore } from '~/integrations/edit/store';
import IntegrationForm from '~/integrations/edit/components/integration_form.vue'; 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 ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue'; import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue'; import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import TriggerFields from '~/integrations/edit/components/trigger_fields.vue'; import TriggerFields from '~/integrations/edit/components/trigger_fields.vue';
import DynamicField from '~/integrations/edit/components/dynamic_field.vue'; import DynamicField from '~/integrations/edit/components/dynamic_field.vue';
import { mockIntegrationProps } from 'jest/integrations/edit/mock_data';
describe('IntegrationForm', () => { describe('IntegrationForm', () => {
let wrapper; let wrapper;
const defaultProps = { const createComponent = (customStateProps = {}, featureFlags = {}, initialState = {}) => {
activeToggleProps: {
initialActivated: true,
},
showActive: true,
triggerFieldsProps: {
initialTriggerCommit: false,
initialTriggerMergeRequest: false,
initialEnableComments: false,
},
jiraIssuesProps: {},
type: '',
};
const createComponent = (props, featureFlags = {}) => {
wrapper = shallowMount(IntegrationForm, { wrapper = shallowMount(IntegrationForm, {
propsData: { ...defaultProps, ...props }, propsData: {},
store: createStore({
customState: { ...mockIntegrationProps, ...customStateProps },
...initialState,
}),
stubs: { stubs: {
OverrideDropdown,
ActiveToggle, ActiveToggle,
JiraTriggerFields, JiraTriggerFields,
TriggerFields,
}, },
provide: { provide: {
glFeatures: featureFlags, glFeatures: featureFlags,
...@@ -43,6 +38,7 @@ describe('IntegrationForm', () => { ...@@ -43,6 +38,7 @@ describe('IntegrationForm', () => {
} }
}); });
const findOverrideDropdown = () => wrapper.find(OverrideDropdown);
const findActiveToggle = () => wrapper.find(ActiveToggle); const findActiveToggle = () => wrapper.find(ActiveToggle);
const findJiraTriggerFields = () => wrapper.find(JiraTriggerFields); const findJiraTriggerFields = () => wrapper.find(JiraTriggerFields);
const findJiraIssuesFields = () => wrapper.find(JiraIssuesFields); const findJiraIssuesFields = () => wrapper.find(JiraIssuesFields);
...@@ -140,5 +136,35 @@ describe('IntegrationForm', () => { ...@@ -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 { mount } from '@vue/test-utils';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import { GlFormCheckbox, GlFormInput } from '@gitlab/ui'; import { GlFormCheckbox, GlFormInput } from '@gitlab/ui';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
describe('JiraIssuesFields', () => { describe('JiraIssuesFields', () => {
let wrapper; let wrapper;
......
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
import { GlFormCheckbox } from '@gitlab/ui'; import { GlFormCheckbox } from '@gitlab/ui';
import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
describe('JiraTriggerFields', () => { describe('JiraTriggerFields', () => {
let wrapper; let wrapper;
...@@ -11,9 +11,12 @@ describe('JiraTriggerFields', () => { ...@@ -11,9 +11,12 @@ describe('JiraTriggerFields', () => {
initialEnableComments: false, initialEnableComments: false,
}; };
const createComponent = props => { const createComponent = (props, isInheriting = false) => {
wrapper = mount(JiraTriggerFields, { wrapper = mount(JiraTriggerFields, {
propsData: { ...defaultProps, ...props }, propsData: { ...defaultProps, ...props },
computed: {
isInheriting: () => isInheriting,
},
}); });
}; };
...@@ -93,5 +96,23 @@ describe('JiraTriggerFields', () => { ...@@ -93,5 +96,23 @@ describe('JiraTriggerFields', () => {
expect(findCommentDetail().isVisible()).toBe(true); 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', () => { ...@@ -9,9 +9,12 @@ describe('TriggerFields', () => {
type: 'slack', type: 'slack',
}; };
const createComponent = props => { const createComponent = (props, isInheriting = false) => {
wrapper = mount(TriggerFields, { wrapper = mount(TriggerFields, {
propsData: { ...defaultProps, ...props }, propsData: { ...defaultProps, ...props },
computed: {
isInheriting: () => isInheriting,
},
}); });
}; };
...@@ -22,10 +25,11 @@ describe('TriggerFields', () => { ...@@ -22,10 +25,11 @@ describe('TriggerFields', () => {
} }
}); });
const findAllGlFormGroups = () => wrapper.find('#trigger-fields').findAll(GlFormGroup);
const findAllGlFormCheckboxes = () => wrapper.findAll(GlFormCheckbox); const findAllGlFormCheckboxes = () => wrapper.findAll(GlFormCheckbox);
const findAllGlFormInputs = () => wrapper.findAll(GlFormInput); const findAllGlFormInputs = () => wrapper.findAll(GlFormInput);
describe('template', () => { describe.each([true, false])('template, isInheriting = `%p`', isInheriting => {
it('renders a label with text "Trigger"', () => { it('renders a label with text "Trigger"', () => {
createComponent(); createComponent();
...@@ -51,9 +55,12 @@ describe('TriggerFields', () => { ...@@ -51,9 +55,12 @@ describe('TriggerFields', () => {
]; ];
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent(
events, {
}); events,
},
isInheriting,
);
}); });
it('does not render GlFormInput for each event', () => { it('does not render GlFormInput for each event', () => {
...@@ -69,8 +76,10 @@ describe('TriggerFields', () => { ...@@ -69,8 +76,10 @@ describe('TriggerFields', () => {
}); });
}); });
it('renders GlFormCheckbox for each event', () => { it(`renders GlFormCheckbox and corresponding hidden input for each event, which ${
const checkboxes = findAllGlFormCheckboxes(); isInheriting ? 'is' : 'is not'
} disabled`, () => {
const checkboxes = findAllGlFormGroups();
const expectedResults = [ const expectedResults = [
{ labelText: 'Push', inputName: 'service[push_event]' }, { labelText: 'Push', inputName: 'service[push_event]' },
{ labelText: 'Merge Request', inputName: 'service[merge_requests_event]' }, { labelText: 'Merge Request', inputName: 'service[merge_requests_event]' },
...@@ -78,14 +87,22 @@ describe('TriggerFields', () => { ...@@ -78,14 +87,22 @@ describe('TriggerFields', () => {
expect(checkboxes).toHaveLength(2); expect(checkboxes).toHaveLength(2);
checkboxes.wrappers.forEach((checkbox, index) => { checkboxes.wrappers.forEach((checkbox, index) => {
const checkBox = checkbox.find(GlFormCheckbox);
expect(checkbox.find('label').text()).toBe(expectedResults[index].labelText); expect(checkbox.find('label').text()).toBe(expectedResults[index].labelText);
expect(checkbox.find('input').attributes('name')).toBe(expectedResults[index].inputName); expect(checkbox.find('[type=hidden]').attributes('name')).toBe(
expect(checkbox.vm.$attrs.checked).toBe(events[index].value); 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 = [ const events = [
{ {
field: { field: {
...@@ -102,16 +119,21 @@ describe('TriggerFields', () => { ...@@ -102,16 +119,21 @@ describe('TriggerFields', () => {
]; ];
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent(
events, {
}); events,
},
isInheriting,
);
}); });
it('renders GlFormCheckbox for each event', () => { it('renders GlFormCheckbox for each event', () => {
expect(findAllGlFormCheckboxes()).toHaveLength(2); 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 fields = findAllGlFormInputs();
const expectedResults = [ const expectedResults = [
{ {
...@@ -128,6 +150,7 @@ describe('TriggerFields', () => { ...@@ -128,6 +150,7 @@ describe('TriggerFields', () => {
fields.wrappers.forEach((field, index) => { fields.wrappers.forEach((field, index) => {
expect(field.attributes()).toMatchObject(expectedResults[index]); expect(field.attributes()).toMatchObject(expectedResults[index]);
expect(field.vm.$attrs.readonly).toBe(isInheriting);
expect(field.vm.$attrs.value).toBe(events[index].field.value); 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 ...@@ -7,4 +7,28 @@ RSpec.describe ServicesHelper do
it { expect(event_action_title('comment')).to eq 'Comment' } it { expect(event_action_title('comment')).to eq 'Comment' }
it { expect(event_action_title('something')).to eq 'Something' } it { expect(event_action_title('something')).to eq 'Something' }
end 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 end
...@@ -386,6 +386,33 @@ RSpec.describe Service do ...@@ -386,6 +386,33 @@ RSpec.describe Service do
end end
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 describe "{property}_changed?" do
let(:service) do let(:service) do
BambooService.create( 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