Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
127e6d9610
commit
01a8b31afe
66 changed files with 1698 additions and 68 deletions
|
@ -3,6 +3,7 @@ extends:
|
|||
- plugin:@gitlab/i18n
|
||||
- plugin:no-jquery/slim
|
||||
- plugin:no-jquery/deprecated-3.4
|
||||
- ./tooling/eslint-config/conditionally_ignore_ee.js
|
||||
globals:
|
||||
__webpack_public_path__: true
|
||||
gl: false
|
||||
|
|
|
@ -8,6 +8,7 @@ scss_files:
|
|||
exclude:
|
||||
- 'app/assets/stylesheets/pages/emojis.scss'
|
||||
- 'app/assets/stylesheets/startup/startup-*.scss'
|
||||
- 'app/assets/stylesheets/lazy_bundles/select2.scss'
|
||||
|
||||
linters:
|
||||
# Reports when you use improper spacing around ! (the "bang") in !default,
|
||||
|
|
|
@ -1 +1 @@
|
|||
506c44cc07dcb804ce970ec1c02bb6e0d52320d8
|
||||
40d58655a42f71b6180a3cbaf369cc20b60e695a
|
||||
|
|
|
@ -467,6 +467,17 @@ export default {
|
|||
notebooks to a class of students, a corporate data science group,
|
||||
or a scientific research group.`)
|
||||
}}
|
||||
<gl-sprintf
|
||||
:message="
|
||||
s__(
|
||||
'ClusterIntegration|%{boldStart}Note:%{boldEnd} Requires Ingress to be installed.',
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #bold="{ content }">
|
||||
<b>{{ content }}</b>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
|
||||
<template v-if="ingressExternalEndpoint">
|
||||
|
|
|
@ -20,17 +20,7 @@ const handleStartupEvents = () => {
|
|||
}
|
||||
};
|
||||
|
||||
/* Wait for.... The methods can be used:
|
||||
- with a callback (preferred),
|
||||
waitFor(action)
|
||||
|
||||
- with then (discouraged),
|
||||
await waitFor().then(action);
|
||||
|
||||
- with await,
|
||||
await waitFor;
|
||||
action();
|
||||
-*/
|
||||
/* For `waitForCSSLoaded` methods, see docs.gitlab.com/ee/development/fe_guide/performance.html#important-considerations */
|
||||
export const waitForCSSLoaded = (action = () => {}) => {
|
||||
if (!gon?.features?.startupCss || allLinksLoaded()) {
|
||||
return new Promise(resolve => {
|
||||
|
|
|
@ -8,14 +8,14 @@ export default {
|
|||
GlModal,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isSavingOrTesting']),
|
||||
...mapGetters(['isDisabled']),
|
||||
primaryProps() {
|
||||
return {
|
||||
text: __('Save'),
|
||||
attributes: [
|
||||
{ variant: 'success' },
|
||||
{ category: 'primary' },
|
||||
{ disabled: this.isSavingOrTesting },
|
||||
{ disabled: this.isDisabled },
|
||||
],
|
||||
};
|
||||
},
|
||||
|
|
|
@ -12,6 +12,7 @@ import JiraIssuesFields from './jira_issues_fields.vue';
|
|||
import TriggerFields from './trigger_fields.vue';
|
||||
import DynamicField from './dynamic_field.vue';
|
||||
import ConfirmationModal from './confirmation_modal.vue';
|
||||
import ResetConfirmationModal from './reset_confirmation_modal.vue';
|
||||
|
||||
export default {
|
||||
name: 'IntegrationForm',
|
||||
|
@ -23,6 +24,7 @@ export default {
|
|||
TriggerFields,
|
||||
DynamicField,
|
||||
ConfirmationModal,
|
||||
ResetConfirmationModal,
|
||||
GlButton,
|
||||
},
|
||||
directives: {
|
||||
|
@ -30,8 +32,8 @@ export default {
|
|||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
computed: {
|
||||
...mapGetters(['currentKey', 'propsSource', 'isSavingOrTesting']),
|
||||
...mapState(['defaultState', 'override', 'isSaving', 'isTesting']),
|
||||
...mapGetters(['currentKey', 'propsSource', 'isDisabled']),
|
||||
...mapState(['defaultState', 'override', 'isSaving', 'isTesting', 'isResetting']),
|
||||
isEditable() {
|
||||
return this.propsSource.editable;
|
||||
},
|
||||
|
@ -47,9 +49,12 @@ export default {
|
|||
showJiraIssuesFields() {
|
||||
return this.isJira && this.glFeatures.jiraIssuesIntegration;
|
||||
},
|
||||
showReset() {
|
||||
return this.isInstanceOrGroupLevel && this.propsSource.resetPath;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['setOverride', 'setIsSaving', 'setIsTesting']),
|
||||
...mapActions(['setOverride', 'setIsSaving', 'setIsTesting', 'setIsResetting']),
|
||||
onSaveClick() {
|
||||
this.setIsSaving(true);
|
||||
eventHub.$emit('saveIntegration');
|
||||
|
@ -58,6 +63,7 @@ export default {
|
|||
this.setIsTesting(true);
|
||||
eventHub.$emit('testIntegration');
|
||||
},
|
||||
onResetClick() {},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -100,7 +106,7 @@ export default {
|
|||
category="primary"
|
||||
variant="success"
|
||||
:loading="isSaving"
|
||||
:disabled="isSavingOrTesting"
|
||||
:disabled="isDisabled"
|
||||
data-qa-selector="save_changes_button"
|
||||
>
|
||||
{{ __('Save changes') }}
|
||||
|
@ -113,7 +119,7 @@ export default {
|
|||
variant="success"
|
||||
type="submit"
|
||||
:loading="isSaving"
|
||||
:disabled="isSavingOrTesting"
|
||||
:disabled="isDisabled"
|
||||
data-qa-selector="save_changes_button"
|
||||
@click.prevent="onSaveClick"
|
||||
>
|
||||
|
@ -123,13 +129,27 @@ export default {
|
|||
<gl-button
|
||||
v-if="propsSource.canTest"
|
||||
:loading="isTesting"
|
||||
:disabled="isSavingOrTesting"
|
||||
:disabled="isDisabled"
|
||||
:href="propsSource.testPath"
|
||||
@click.prevent="onTestClick"
|
||||
>
|
||||
{{ __('Test settings') }}
|
||||
</gl-button>
|
||||
|
||||
<template v-if="showReset">
|
||||
<gl-button
|
||||
v-gl-modal.confirmResetIntegration
|
||||
category="secondary"
|
||||
variant="default"
|
||||
:loading="isResetting"
|
||||
:disabled="isDisabled"
|
||||
data-testid="reset-button"
|
||||
>
|
||||
{{ __('Reset') }}
|
||||
</gl-button>
|
||||
<reset-confirmation-modal @reset="onResetClick" />
|
||||
</template>
|
||||
|
||||
<gl-button class="btn-cancel" :href="propsSource.cancelPath">{{ __('Cancel') }}</gl-button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { GlModal } from '@gitlab/ui';
|
||||
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlModal,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isDisabled']),
|
||||
primaryProps() {
|
||||
return {
|
||||
text: __('Reset'),
|
||||
attributes: [
|
||||
{ variant: 'warning' },
|
||||
{ category: 'primary' },
|
||||
{ disabled: this.isDisabled },
|
||||
],
|
||||
};
|
||||
},
|
||||
cancelProps() {
|
||||
return {
|
||||
text: __('Cancel'),
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onReset() {
|
||||
this.$emit('reset');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-modal
|
||||
modal-id="confirmResetIntegration"
|
||||
size="sm"
|
||||
:title="s__('Integrations|Reset integration?')"
|
||||
:action-primary="primaryProps"
|
||||
:action-cancel="cancelProps"
|
||||
@primary="onReset"
|
||||
>
|
||||
<p>
|
||||
{{
|
||||
s__(
|
||||
'Integrations|Resetting this integration will clear the settings and deactivate this integration.',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p>
|
||||
{{ s__('Integrations|All projects inheriting these settings will also be reset.') }}
|
||||
</p>
|
||||
|
||||
<p class="gl-mb-0">
|
||||
{{ s__('Integrations|Projects using custom settings will not be affected.') }}
|
||||
</p>
|
||||
</gl-modal>
|
||||
</template>
|
|
@ -26,6 +26,7 @@ function parseDatasetToProps(data) {
|
|||
integrationLevel,
|
||||
cancelPath,
|
||||
testPath,
|
||||
resetPath,
|
||||
...booleanAttributes
|
||||
} = data;
|
||||
const {
|
||||
|
@ -49,6 +50,7 @@ function parseDatasetToProps(data) {
|
|||
editable,
|
||||
canTest,
|
||||
testPath,
|
||||
resetPath,
|
||||
triggerFieldsProps: {
|
||||
initialTriggerCommit: commitEvents,
|
||||
initialTriggerMergeRequest: mergeRequestEvents,
|
||||
|
|
|
@ -3,3 +3,5 @@ import * as types from './mutation_types';
|
|||
export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override);
|
||||
export const setIsSaving = ({ commit }, isSaving) => commit(types.SET_IS_SAVING, isSaving);
|
||||
export const setIsTesting = ({ commit }, isTesting) => commit(types.SET_IS_TESTING, isTesting);
|
||||
export const setIsResetting = ({ commit }, isResetting) =>
|
||||
commit(types.SET_IS_RESETTING, isResetting);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export const isInheriting = state => (state.defaultState === null ? false : !state.override);
|
||||
|
||||
export const isSavingOrTesting = state => state.isSaving || state.isTesting;
|
||||
export const isDisabled = state => state.isSaving || state.isTesting || state.isResetting;
|
||||
|
||||
export const propsSource = (state, getters) =>
|
||||
getters.isInheriting ? state.defaultState : state.customState;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export const SET_OVERRIDE = 'SET_OVERRIDE';
|
||||
export const SET_IS_SAVING = 'SET_IS_SAVING';
|
||||
export const SET_IS_TESTING = 'SET_IS_TESTING';
|
||||
export const SET_IS_RESETTING = 'SET_IS_RESETTING';
|
||||
|
|
|
@ -10,4 +10,7 @@ export default {
|
|||
[types.SET_IS_TESTING](state, isTesting) {
|
||||
state.isTesting = isTesting;
|
||||
},
|
||||
[types.SET_IS_RESETTING](state, isResetting) {
|
||||
state.isResetting = isResetting;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -7,5 +7,6 @@ export default ({ defaultState = null, customState = {} } = {}) => {
|
|||
customState,
|
||||
isSaving: false,
|
||||
isTesting: false,
|
||||
isResetting: false,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
export function loadCSSFile(path) {
|
||||
return new Promise(resolve => {
|
||||
if (!path) resolve();
|
||||
|
||||
if (document.querySelector(`link[href="${path}"]`)) {
|
||||
resolve();
|
||||
} else {
|
||||
|
|
|
@ -399,3 +399,15 @@ export const truncateNamespace = (string = '') => {
|
|||
* @returns {Boolean}
|
||||
*/
|
||||
export const hasContent = obj => isString(obj) && obj.trim() !== '';
|
||||
|
||||
/**
|
||||
* A utility function that validates if a
|
||||
* string is valid SHA1 hash format.
|
||||
*
|
||||
* @param {String} hash to validate
|
||||
*
|
||||
* @return {Boolean} true if valid
|
||||
*/
|
||||
export const isValidSha1Hash = str => {
|
||||
return /^[0-9a-f]{5,40}$/.test(str);
|
||||
};
|
||||
|
|
654
app/assets/stylesheets/lazy_bundles/select2.scss
Normal file
654
app/assets/stylesheets/lazy_bundles/select2.scss
Normal file
|
@ -0,0 +1,654 @@
|
|||
/*
|
||||
Version: 3.5.2 Timestamp: Sat Nov 1 14:43:36 EDT 2014
|
||||
Updated 2020-10-05 by TimZ
|
||||
*/
|
||||
.select2-container {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.select2-container,
|
||||
.select2-drop,
|
||||
.select2-search,
|
||||
.select2-search input {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice {
|
||||
display: block;
|
||||
height: 26px;
|
||||
padding: 0 0 0 8px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
border: 1px solid #aaa;
|
||||
white-space: nowrap;
|
||||
line-height: 26px;
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
background-clip: padding-box;
|
||||
|
||||
user-select: none;
|
||||
|
||||
background-color: #fff;
|
||||
background-image: linear-gradient(to top, #eee 0%, #fff 50%);
|
||||
}
|
||||
|
||||
html[dir='rtl'] .select2-container .select2-choice {
|
||||
padding: 0 8px 0 0;
|
||||
}
|
||||
|
||||
.select2-container.select2-drop-above .select2-choice {
|
||||
border-bottom-color: #aaa;
|
||||
|
||||
border-radius: 0 0 4px 4px;
|
||||
|
||||
background-image: linear-gradient(to bottom, #eee 0%, #fff 90%);
|
||||
}
|
||||
|
||||
.select2-container.select2-allowclear .select2-choice .select2-chosen {
|
||||
margin-right: 42px;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice > .select2-chosen {
|
||||
margin-right: 26px;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
float: none;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
html[dir='rtl'] .select2-container .select2-choice > .select2-chosen {
|
||||
margin-left: 26px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice abbr {
|
||||
display: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
position: absolute;
|
||||
right: 24px;
|
||||
top: 8px;
|
||||
|
||||
font-size: 1px;
|
||||
text-decoration: none;
|
||||
|
||||
border: 0;
|
||||
/* stylelint-disable-next-line function-url-quotes */
|
||||
background: url(image-path('select2.png')) right top no-repeat;
|
||||
cursor: pointer;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.select2-container.select2-allowclear .select2-choice abbr {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice abbr:hover {
|
||||
background-position: right -11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.select2-drop-mask {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
height: auto;
|
||||
width: auto;
|
||||
opacity: 0;
|
||||
z-index: 9998;
|
||||
/* styles required for IE to work */
|
||||
background-color: #fff;
|
||||
filter: alpha(opacity=0);
|
||||
}
|
||||
|
||||
.select2-drop {
|
||||
width: 100%;
|
||||
margin-top: -1px;
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
top: 100%;
|
||||
|
||||
background: #fff;
|
||||
color: #000;
|
||||
border: 1px solid #aaa;
|
||||
border-top: 0;
|
||||
|
||||
border-radius: 0 0 4px 4px;
|
||||
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.select2-drop.select2-drop-above {
|
||||
margin-top: 1px;
|
||||
border-top: 1px solid #aaa;
|
||||
border-bottom: 0;
|
||||
|
||||
border-radius: 4px 4px 0 0;
|
||||
|
||||
box-shadow: 0 -4px 5px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.select2-drop-active {
|
||||
border: 1px solid #5897fb;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.select2-drop.select2-drop-above.select2-drop-active {
|
||||
border-top: 1px solid #5897fb;
|
||||
}
|
||||
|
||||
.select2-drop-auto-width {
|
||||
border-top: 1px solid #aaa;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.select2-drop-auto-width .select2-search {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice .select2-arrow {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
border-left: 1px solid #aaa;
|
||||
border-radius: 0 4px 4px 0;
|
||||
|
||||
background-clip: padding-box;
|
||||
|
||||
background: #ccc;
|
||||
background-image: linear-gradient(to top, #ccc 0%, #eee 60%);
|
||||
}
|
||||
|
||||
html[dir='rtl'] .select2-container .select2-choice .select2-arrow {
|
||||
left: 0;
|
||||
right: auto;
|
||||
|
||||
border-left: 0;
|
||||
border-right: 1px solid #aaa;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice .select2-arrow b {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* stylelint-disable-next-line function-url-quotes */
|
||||
background: url(image-path("select2.png")) no-repeat 0 1px;
|
||||
}
|
||||
|
||||
html[dir='rtl'] .select2-container .select2-choice .select2-arrow b {
|
||||
background-position: 2px 1px;
|
||||
}
|
||||
|
||||
.select2-search {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
min-height: 26px;
|
||||
margin: 0;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
|
||||
position: relative;
|
||||
z-index: 10000;
|
||||
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.select2-search input {
|
||||
width: 100%;
|
||||
height: auto !important;
|
||||
min-height: 26px;
|
||||
padding: 4px 20px 4px 5px;
|
||||
margin: 0;
|
||||
|
||||
outline: 0;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 0;
|
||||
|
||||
box-shadow: none;
|
||||
/* stylelint-disable-next-line function-url-quotes */
|
||||
background: url(image-path('select2.png')) no-repeat 100% -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
|
||||
}
|
||||
|
||||
html[dir='rtl'] .select2-search input {
|
||||
padding: 4px 5px 4px 20px;
|
||||
/* stylelint-disable-next-line function-url-quotes */
|
||||
background: url(image-path('select2.png')) no-repeat -37px -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
|
||||
}
|
||||
|
||||
.select2-drop.select2-drop-above .select2-search input {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.select2-search input.select2-active {
|
||||
/* stylelint-disable-next-line function-url-quotes */
|
||||
background: url(image-path('select2-spinner.gif')) no-repeat 100%, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
|
||||
}
|
||||
|
||||
.select2-container-active .select2-choice,
|
||||
.select2-container-active .select2-choices {
|
||||
border: 1px solid #5897fb;
|
||||
outline: none;
|
||||
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice {
|
||||
border-bottom-color: transparent;
|
||||
box-shadow: 0 1px 0 #fff inset;
|
||||
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
background-color: #eee;
|
||||
background-image: linear-gradient(to top, #fff 0%, #eee 50%);
|
||||
}
|
||||
|
||||
.select2-dropdown-open.select2-drop-above .select2-choice,
|
||||
.select2-dropdown-open.select2-drop-above .select2-choices {
|
||||
border: 1px solid #5897fb;
|
||||
border-top-color: transparent;
|
||||
|
||||
background-image: linear-gradient(to bottom, #fff 0%, #eee 50%);
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice .select2-arrow {
|
||||
background: transparent;
|
||||
border-left: 0;
|
||||
filter: none;
|
||||
}
|
||||
|
||||
html[dir='rtl'] .select2-dropdown-open .select2-choice .select2-arrow {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice .select2-arrow b {
|
||||
background-position: -18px 1px;
|
||||
}
|
||||
|
||||
html[dir='rtl'] .select2-dropdown-open .select2-choice .select2-arrow b {
|
||||
background-position: -16px 1px;
|
||||
}
|
||||
|
||||
.select2-hidden-accessible {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
/* results */
|
||||
.select2-results {
|
||||
max-height: 200px;
|
||||
padding: 0 0 0 4px;
|
||||
margin: 4px 4px 4px 0;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
html[dir='rtl'] .select2-results {
|
||||
padding: 0 4px 0 0;
|
||||
margin: 4px 0 4px 4px;
|
||||
}
|
||||
|
||||
.select2-results ul.select2-result-sub {
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.select2-results li {
|
||||
list-style: none;
|
||||
display: list-item;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.select2-results li.select2-result-with-children > .select2-result-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.select2-results .select2-result-label {
|
||||
padding: 3px 7px 4px;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
|
||||
min-height: 1em;
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.select2-results-dept-1 .select2-result-label { padding-left: 20px; }
|
||||
.select2-results-dept-2 .select2-result-label { padding-left: 40px; }
|
||||
.select2-results-dept-3 .select2-result-label { padding-left: 60px; }
|
||||
.select2-results-dept-4 .select2-result-label { padding-left: 80px; }
|
||||
.select2-results-dept-5 .select2-result-label { padding-left: 100px; }
|
||||
.select2-results-dept-6 .select2-result-label { padding-left: 110px; }
|
||||
.select2-results-dept-7 .select2-result-label { padding-left: 120px; }
|
||||
|
||||
.select2-results .select2-highlighted {
|
||||
background: #3875d7;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.select2-results li em {
|
||||
background: #feffde;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.select2-results .select2-highlighted em {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.select2-results .select2-highlighted ul {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.select2-results .select2-no-results,
|
||||
.select2-results .select2-searching,
|
||||
.select2-results .select2-ajax-error,
|
||||
.select2-results .select2-selection-limit {
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
/*
|
||||
disabled look for disabled choices in the results dropdown
|
||||
*/
|
||||
.select2-results .select2-disabled.select2-highlighted {
|
||||
color: #666;
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select2-results .select2-disabled {
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select2-results .select2-selected {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select2-more-results.select2-active {
|
||||
/* stylelint-disable-next-line function-url-quotes */
|
||||
background: #f4f4f4 url(image-path('select2-spinner.gif')) no-repeat 100%;
|
||||
}
|
||||
|
||||
.select2-results .select2-ajax-error {
|
||||
background: rgba(255, 50, 50, 0.2);
|
||||
}
|
||||
|
||||
.select2-more-results {
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* disabled styles */
|
||||
|
||||
.select2-container.select2-container-disabled .select2-choice {
|
||||
background-color: #f4f4f4;
|
||||
background-image: none;
|
||||
border: 1px solid #ddd;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select2-container.select2-container-disabled .select2-choice .select2-arrow {
|
||||
background-color: #f4f4f4;
|
||||
background-image: none;
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.select2-container.select2-container-disabled .select2-choice abbr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* multiselect */
|
||||
|
||||
.select2-container-multi .select2-choices {
|
||||
height: auto !important;
|
||||
height: 1%;
|
||||
margin: 0;
|
||||
padding: 0 5px 0 0;
|
||||
position: relative;
|
||||
|
||||
border: 1px solid #aaa;
|
||||
cursor: text;
|
||||
overflow: hidden;
|
||||
|
||||
background-color: #fff;
|
||||
background-image: linear-gradient(to bottom, #eee 1%, #fff 15%);
|
||||
}
|
||||
|
||||
html[dir='rtl'] .select2-container-multi .select2-choices {
|
||||
padding: 0 0 0 5px;
|
||||
}
|
||||
|
||||
.select2-locked {
|
||||
padding: 3px 5px !important;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices {
|
||||
min-height: 26px;
|
||||
}
|
||||
|
||||
.select2-container-multi.select2-container-active .select2-choices {
|
||||
border: 1px solid #5897fb;
|
||||
outline: none;
|
||||
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices li {
|
||||
float: left;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
html[dir='rtl'] .select2-container-multi .select2-choices li {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-field {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-field input {
|
||||
padding: 5px;
|
||||
margin: 1px 0;
|
||||
|
||||
font-family: sans-serif;
|
||||
font-size: 100%;
|
||||
color: #666;
|
||||
outline: 0;
|
||||
border: 0;
|
||||
|
||||
box-shadow: none;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-field input.select2-active {
|
||||
/* stylelint-disable-next-line function-url-quotes */
|
||||
background: #fff url(image-path('select2-spinner.gif')) no-repeat 100% !important;
|
||||
}
|
||||
|
||||
.select2-default {
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice {
|
||||
padding: 3px 5px 3px 18px;
|
||||
margin: 3px 0 3px 5px;
|
||||
position: relative;
|
||||
|
||||
line-height: 13px;
|
||||
color: #333;
|
||||
cursor: default;
|
||||
border: 1px solid #aaa;
|
||||
|
||||
border-radius: 3px;
|
||||
|
||||
box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
|
||||
|
||||
background-clip: padding-box;
|
||||
|
||||
user-select: none;
|
||||
|
||||
background-color: #e4e4e4;
|
||||
background-image: linear-gradient(to bottom, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
|
||||
}
|
||||
|
||||
html[dir='rtl'] .select2-container-multi .select2-choices .select2-search-choice {
|
||||
margin: 3px 5px 3px 0;
|
||||
padding: 3px 18px 3px 5px;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice .select2-chosen {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice-focus {
|
||||
background: #d4d4d4;
|
||||
}
|
||||
|
||||
.select2-search-choice-close {
|
||||
display: block;
|
||||
width: 12px;
|
||||
height: 13px;
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
top: 4px;
|
||||
|
||||
font-size: 1px;
|
||||
outline: none;
|
||||
/* stylelint-disable-next-line function-url-quotes */
|
||||
background: url(image-path('select2.png')) right top no-repeat;
|
||||
}
|
||||
|
||||
html[dir='rtl'] .select2-search-choice-close {
|
||||
right: auto;
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-search-choice-close {
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
html[dir='rtl'] .select2-container-multi .select2-search-choice-close {
|
||||
left: auto;
|
||||
right: 2px;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover {
|
||||
background-position: right -11px;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close {
|
||||
background-position: right -11px;
|
||||
}
|
||||
|
||||
/* disabled styles */
|
||||
.select2-container-multi.select2-container-disabled .select2-choices {
|
||||
background-color: #f4f4f4;
|
||||
background-image: none;
|
||||
border: 1px solid #ddd;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice {
|
||||
padding: 3px 5px;
|
||||
border: 1px solid #ddd;
|
||||
background-image: none;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close {
|
||||
display: none;
|
||||
background: none;
|
||||
}
|
||||
/* end multiselect */
|
||||
|
||||
|
||||
.select2-result-selectable .select2-match,
|
||||
.select2-result-unselectable .select2-match {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.select2-offscreen,
|
||||
.select2-offscreen:focus {
|
||||
clip: rect(0 0 0 0) !important;
|
||||
width: 1px !important;
|
||||
height: 1px !important;
|
||||
border: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
position: absolute !important;
|
||||
outline: 0 !important;
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
}
|
||||
|
||||
.select2-display-none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select2-measure-scrollbar {
|
||||
position: absolute;
|
||||
top: -10000px;
|
||||
left: -10000px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
@media only screen and (min-resolution: 120dpi) {
|
||||
.select2-search input,
|
||||
.select2-search-choice-close,
|
||||
.select2-container .select2-choice abbr,
|
||||
.select2-container .select2-choice .select2-arrow b {
|
||||
/* stylelint-disable-next-line function-url-quotes */
|
||||
background-image: url(image-path("select2x2.png")) !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-size: 60px 40px !important;
|
||||
}
|
||||
|
||||
.select2-search input {
|
||||
background-position: 100% -21px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* End of select2.css */
|
||||
|
||||
@import './select2_overrides';
|
359
app/assets/stylesheets/lazy_bundles/select2_overrides.scss
Normal file
359
app/assets/stylesheets/lazy_bundles/select2_overrides.scss
Normal file
|
@ -0,0 +1,359 @@
|
|||
@import 'page_bundles/mixins_and_variables_and_functions';
|
||||
/** Select2 selectbox style override **/
|
||||
.select2-container {
|
||||
width: 100% !important;
|
||||
|
||||
&.input-md,
|
||||
&.input-lg {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-container,
|
||||
.select2-container.select2-drop-above {
|
||||
.select2-choice {
|
||||
background: $white;
|
||||
color: $gl-text-color;
|
||||
border-color: $border-color;
|
||||
height: 34px;
|
||||
padding: $gl-vert-padding $gl-input-padding;
|
||||
font-size: $gl-font-size;
|
||||
line-height: 1.42857143;
|
||||
border-radius: $gl-border-radius-base;
|
||||
|
||||
.select2-arrow {
|
||||
background-image: none;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
padding-top: 12px;
|
||||
padding-right: 20px;
|
||||
font-size: 10px;
|
||||
|
||||
b {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '\f078';
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
box-sizing: border-box;
|
||||
color: $gray-darkest;
|
||||
display: inline-block;
|
||||
font: normal normal normal 14px/1 FontAwesome;
|
||||
font-size: inherit;
|
||||
text-rendering: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-chosen {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: $gray-darkest;
|
||||
color: $gl-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
// Essentially we’re doing @include form-control-focus here (from
|
||||
// bootstrap/scss/mixins/_forms.scss), except that the bootstrap mixin adds a
|
||||
// `&:focus` selector and we’re never actually focusing the .select2-choice
|
||||
// link nor the .select2-container, the Select2 library focuses an off-screen
|
||||
// .select2-focusser element instead.
|
||||
&.select2-container-active:not(.select2-dropdown-open) {
|
||||
.select2-choice {
|
||||
color: $input-focus-color;
|
||||
background-color: $input-focus-bg;
|
||||
border-color: $input-focus-border-color;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
// Reusable focus “glow” box-shadow
|
||||
@mixin form-control-focus-glow {
|
||||
@if $enable-shadows {
|
||||
box-shadow: $input-box-shadow, $input-focus-box-shadow;
|
||||
} @else {
|
||||
box-shadow: $input-focus-box-shadow;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the focus “glow” shadow to the .select2-container if it also has
|
||||
// the .block-truncated class as that applies an overflow: hidden, thereby
|
||||
// hiding the glow of the nested .select2-choice element.
|
||||
&.block-truncated {
|
||||
@include form-control-focus-glow;
|
||||
}
|
||||
|
||||
// Apply the glow directly to the .select2-choice link if we’re not
|
||||
// block-truncating the container.
|
||||
&:not(.block-truncated) .select2-choice {
|
||||
@include form-control-focus-glow;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-invalid {
|
||||
~ .invalid-feedback {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.select2-choices,
|
||||
.select2-choice {
|
||||
border-color: $red-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select2-drop,
|
||||
.select2-drop.select2-drop-above {
|
||||
background: $white;
|
||||
box-shadow: 0 2px 4px $dropdown-shadow-color;
|
||||
border-radius: $gl-border-radius-base;
|
||||
border: 1px solid $border-color;
|
||||
min-width: 175px;
|
||||
color: $gl-text-color;
|
||||
z-index: 999;
|
||||
|
||||
.modal-open & {
|
||||
z-index: $zindex-modal + 200;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-drop-mask {
|
||||
z-index: 998;
|
||||
|
||||
.modal-open & {
|
||||
z-index: $zindex-modal + 100;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-drop.select2-drop-above.select2-drop-active {
|
||||
border-top: 1px solid $border-color;
|
||||
margin-top: -6px;
|
||||
}
|
||||
|
||||
.select2-container-active {
|
||||
.select2-choice,
|
||||
.select2-choices {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-dropdown-open,
|
||||
.select2-dropdown-open.select2-drop-above {
|
||||
.select2-choice {
|
||||
border-color: $gray-darkest;
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-container-multi {
|
||||
.select2-choices {
|
||||
border-radius: $border-radius-default;
|
||||
border-color: $border-color;
|
||||
background: none;
|
||||
|
||||
.select2-search-field input {
|
||||
padding: 5px $gl-input-padding;
|
||||
height: auto;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.select2-search-choice {
|
||||
margin: 5px 0 0 8px;
|
||||
box-shadow: none;
|
||||
border-color: $border-color;
|
||||
color: $gl-text-color;
|
||||
line-height: 15px;
|
||||
background-color: $gray-light;
|
||||
background-image: none;
|
||||
padding: 3px 18px 3px 5px;
|
||||
|
||||
.select2-search-choice-close {
|
||||
top: 5px;
|
||||
left: initial;
|
||||
right: 3px;
|
||||
}
|
||||
|
||||
&.select2-search-choice-focus {
|
||||
border-color: $gl-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select2-drop-active {
|
||||
margin-top: $dropdown-vertical-offset;
|
||||
font-size: 14px;
|
||||
|
||||
.select2-results {
|
||||
max-height: 350px;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-search {
|
||||
padding: $grid-size;
|
||||
|
||||
.select2-drop-auto-width & {
|
||||
padding: $grid-size;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: $grid-size;
|
||||
background: transparent image-url('select2.png');
|
||||
color: $gl-text-color;
|
||||
background-clip: content-box;
|
||||
background-origin: content-box;
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 0 bottom 0 !important;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: $border-radius-default;
|
||||
line-height: 16px;
|
||||
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
||||
|
||||
&:focus {
|
||||
border-color: $blue-300;
|
||||
}
|
||||
|
||||
&.select2-active {
|
||||
background-color: $white;
|
||||
background-image: image-url('select2-spinner.gif') !important;
|
||||
background-origin: content-box;
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 6px center !important;
|
||||
background-size: 16px 16px !important;
|
||||
}
|
||||
}
|
||||
|
||||
+ .select2-results {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-results {
|
||||
margin: 0;
|
||||
padding: #{$gl-padding / 2} 0;
|
||||
|
||||
.select2-no-results,
|
||||
.select2-searching,
|
||||
.select2-ajax-error,
|
||||
.select2-selection-limit {
|
||||
background: transparent;
|
||||
padding: #{$gl-padding / 2} $gl-padding;
|
||||
}
|
||||
|
||||
.select2-result-label,
|
||||
.select2-more-results {
|
||||
padding: #{$gl-padding / 2} $gl-padding;
|
||||
}
|
||||
|
||||
.select2-highlighted {
|
||||
background: transparent;
|
||||
color: $gl-text-color;
|
||||
|
||||
.select2-result-label {
|
||||
background: $gray-darker;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-result {
|
||||
padding: 0 1px;
|
||||
}
|
||||
|
||||
li.select2-result-with-children > .select2-result-label {
|
||||
font-weight: $gl-font-weight-bold;
|
||||
color: $gl-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-highlighted {
|
||||
.group-result {
|
||||
.group-path {
|
||||
color: $gray-700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select2-result-selectable,
|
||||
.select2-result-unselectable {
|
||||
.select2-match {
|
||||
font-weight: $gl-font-weight-bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.input-group {
|
||||
.select2-container {
|
||||
display: table-cell;
|
||||
max-width: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
.file-editor {
|
||||
.select2 {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.import-namespace-select {
|
||||
> .select2-choice {
|
||||
border-radius: $border-radius-default 0 0 $border-radius-default;
|
||||
position: relative;
|
||||
left: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.issue-form {
|
||||
.select2-container {
|
||||
width: 250px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.new_project,
|
||||
.edit-project,
|
||||
.import-project {
|
||||
.input-group {
|
||||
.select2-container {
|
||||
display: unset;
|
||||
max-width: unset;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.input-group-prepend,
|
||||
.input-group-append {
|
||||
+ .select2 a {
|
||||
border-radius: 0 $gl-border-radius-base $gl-border-radius-base 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-path {
|
||||
.select2-choice {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.transfer-project .select2-container {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.right-sidebar {
|
||||
.block {
|
||||
.select2-container span {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.block-truncated {
|
||||
> div:not(.block):not(.select2-display-none) {
|
||||
display: inline;
|
||||
}
|
||||
}
|
|
@ -208,9 +208,9 @@
|
|||
display: inline-flex;
|
||||
|
||||
.label,
|
||||
.btn {
|
||||
.btn:not(.gl-button) {
|
||||
padding: $gl-vert-padding $gl-btn-padding;
|
||||
border: 1px $border-color solid;
|
||||
border: 1px $gray-200 solid;
|
||||
font-size: $gl-font-size;
|
||||
line-height: $line-height-base;
|
||||
border-radius: 0;
|
||||
|
|
|
@ -93,7 +93,8 @@ module ServicesHelper
|
|||
editable: integration.editable?.to_s,
|
||||
cancel_path: scoped_integrations_path,
|
||||
can_test: integration.can_test?.to_s,
|
||||
test_path: scoped_test_integration_path(integration)
|
||||
test_path: scoped_test_integration_path(integration),
|
||||
reset_path: ''
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ module ApplicationSettingImplementation
|
|||
repository_checks_enabled: true,
|
||||
repository_storages_weighted: { default: 100 },
|
||||
repository_storages: ['default'],
|
||||
require_admin_approval_after_user_signup: false,
|
||||
require_admin_approval_after_user_signup: true,
|
||||
require_two_factor_authentication: false,
|
||||
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
|
||||
rsa_key_restriction: 0,
|
||||
|
|
|
@ -419,6 +419,10 @@ class Member < ApplicationRecord
|
|||
invite? && user_id.nil?
|
||||
end
|
||||
|
||||
def created_by_name
|
||||
created_by&.name
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def send_invite
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
.form-actions
|
||||
= button_tag class: 'btn gl-button btn-success btn-upload-file', id: 'submit-all', type: 'button' do
|
||||
= icon('spin spinner', class: 'js-loading-icon hidden' )
|
||||
.spinner.spinner-sm.gl-mr-2.js-loading-icon.hidden
|
||||
= button_title
|
||||
= link_to _("Cancel"), '#', class: "btn gl-button btn-cancel", "data-dismiss" => "modal"
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
|
||||
.js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id, ref: ref) } }
|
||||
|
||||
.commit-sha-group.d-none.d-sm-flex
|
||||
.commit-sha-group.btn-group.d-none.d-sm-flex
|
||||
.label.label-monospace.monospace
|
||||
= commit.short_id
|
||||
= clipboard_button(text: commit.id, title: _("Copy commit SHA"), class: "gl-button btn btn-default", container: "body")
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
- apply_is_default_styles = (selected.nil? || selected.empty?) && !no_default_styles
|
||||
%span.dropdown-toggle-text{ class: ("is-default" if apply_is_default_styles) }
|
||||
= multi_label_name(selected, label_name)
|
||||
= icon('chevron-down')
|
||||
= sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon gl-top-3")
|
||||
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable.dropdown-extended-height
|
||||
= render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create }
|
||||
- if show_create && project && can?(current_user, :admin_label, project)
|
||||
|
|
|
@ -96,7 +96,7 @@
|
|||
%button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable_type}[due_date]", ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], display: 'static' } }
|
||||
%span.dropdown-toggle-text
|
||||
= _('Due date')
|
||||
= icon('chevron-down', 'aria-hidden': 'true')
|
||||
= sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon gl-top-3")
|
||||
.dropdown-menu.dropdown-menu-due-date
|
||||
= dropdown_title(_('Due date'))
|
||||
= dropdown_content do
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
.btn-group{ role: 'group' }
|
||||
%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' }
|
||||
= sort_title
|
||||
= icon('chevron-down')
|
||||
= sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon gl-top-3")
|
||||
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
|
||||
%li
|
||||
= sortable_item(sort_title_priority, page_filter_path(sort: sort_value_priority), sort_title)
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
|
||||
%span.dropdown-toggle-text.is-default
|
||||
= issuable.issue_type.capitalize || _("Select type")
|
||||
= icon('chevron-down')
|
||||
= sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon gl-top-3")
|
||||
.dropdown-menu.dropdown-menu-selectable.dropdown-select
|
||||
.dropdown-title.gl-display-flex
|
||||
%span.gl-ml-auto
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update icons to svg for issuable pages
|
||||
merge_request: 47596
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Admin approval required on user registration by default
|
||||
merge_request: 46937
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add API get /invitations for project and group
|
||||
merge_request: 46046
|
||||
author:
|
||||
type: added
|
|
@ -202,6 +202,7 @@ module Gitlab
|
|||
config.assets.precompile << "page_bundles/xterm.css"
|
||||
config.assets.precompile << "page_bundles/alert_management_settings.css"
|
||||
config.assets.precompile << "lazy_bundles/cropper.css"
|
||||
config.assets.precompile << "lazy_bundles/select2.css"
|
||||
config.assets.precompile << "performance_bar.css"
|
||||
config.assets.precompile << "disable_animations.css"
|
||||
config.assets.precompile << "snippets.css"
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddDefaultTrueRequireAdminApprovalAfterUserSignupToApplicationSettings < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
change_column_default :application_settings, :require_admin_approval_after_user_signup, from: false, to: true
|
||||
end
|
||||
end
|
1
db/schema_migrations/20201107032257
Normal file
1
db/schema_migrations/20201107032257
Normal file
|
@ -0,0 +1 @@
|
|||
07160ee3c92e68273042df979640c3927abbb187f79e1a4645471e28061e1c2c
|
|
@ -9324,7 +9324,7 @@ CREATE TABLE application_settings (
|
|||
gitpod_enabled boolean DEFAULT false NOT NULL,
|
||||
gitpod_url text DEFAULT 'https://gitpod.io/'::text,
|
||||
abuse_notification_email character varying,
|
||||
require_admin_approval_after_user_signup boolean DEFAULT false NOT NULL,
|
||||
require_admin_approval_after_user_signup boolean DEFAULT true NOT NULL,
|
||||
help_page_documentation_base_url text,
|
||||
automatic_purchased_storage_allocation boolean DEFAULT false NOT NULL,
|
||||
encrypted_ci_jwt_signing_key text,
|
||||
|
|
|
@ -113,7 +113,6 @@ prometheus['enable'] = false
|
|||
gitlab_rails['auto_migrate'] = false
|
||||
alertmanager['enable'] = false
|
||||
gitaly['enable'] = false
|
||||
gitlab_monitor['enable'] = false
|
||||
gitlab_workhorse['enable'] = false
|
||||
nginx['enable'] = false
|
||||
postgres_exporter['enable'] = false
|
||||
|
|
|
@ -6,7 +6,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Invitations API
|
||||
|
||||
Use the Invitations API to send email to users you want to join a group or project.
|
||||
Use the Invitations API to send email to users you want to join a group or project, and to list pending
|
||||
invitations.
|
||||
|
||||
## Valid access levels
|
||||
|
||||
|
@ -64,3 +65,43 @@ When there was any error sending the email:
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
## List all invitations pending for a group or project
|
||||
|
||||
Gets a list of invited group or project members viewable by the authenticated user.
|
||||
Returns invitations to direct members only, and not through inherited ancestors' groups.
|
||||
|
||||
This function takes pagination parameters `page` and `per_page` to restrict the list of users.
|
||||
|
||||
```plaintext
|
||||
GET /groups/:id/invitations
|
||||
GET /projects/:id/invitations
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `page` | integer | no | Page to retrieve |
|
||||
| `per_page`| integer | no | Number of member invitations to return per page |
|
||||
| `query` | string | no | A query string to search for invited members by invite email. Query text must match email address exactly. When empty, returns all invitations. |
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/invitations?query=member@example.org"
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/invitations?query=member@example.org"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"invite_email": "member@example.org",
|
||||
"invited_at": "2020-10-22T14:13:35Z",
|
||||
"access_level": 30,
|
||||
"expires_at": "2020-11-22T14:13:35Z",
|
||||
"user_name": "Raymond Smith",
|
||||
"created_by_name": "Administrator"
|
||||
},
|
||||
]
|
||||
```
|
||||
|
|
|
@ -38,12 +38,16 @@ The following can be used as a template to get started:
|
|||
````markdown
|
||||
## Descriptive title
|
||||
|
||||
> Version history note.
|
||||
|
||||
One or two sentence description of what endpoint does.
|
||||
|
||||
```plaintext
|
||||
METHOD /endpoint
|
||||
```
|
||||
|
||||
Supported attributes:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:------------|:---------|:---------|:----------------------|
|
||||
| `attribute` | datatype | yes/no | Detailed description. |
|
||||
|
@ -65,6 +69,9 @@ Example response:
|
|||
```
|
||||
````
|
||||
|
||||
Adjust the [version history note accordingly](styleguide/index.md#version-text-in-the-version-history)
|
||||
to describe the GitLab release that introduced the API call.
|
||||
|
||||
## Method description
|
||||
|
||||
Use the following table headers to describe the methods. Attributes should
|
||||
|
|
|
@ -115,8 +115,36 @@ browser's developer console while on any page within GitLab.
|
|||
import initMyWidget from './my_widget';
|
||||
import { waitForCSSLoaded } from '~/helpers/startup_css_helper';
|
||||
|
||||
waitForCSSLoaded(initMyWidget);
|
||||
```
|
||||
|
||||
Note that `waitForCSSLoaded()` methods supports receiving the action in different ways:
|
||||
|
||||
- With a callback:
|
||||
|
||||
```javascript
|
||||
waitForCSSLoaded(action)
|
||||
```
|
||||
|
||||
- With `then()`:
|
||||
|
||||
```javascript
|
||||
waitForCSSLoaded().then(action);
|
||||
```
|
||||
|
||||
- With `await` followed by `action`:
|
||||
|
||||
```javascript
|
||||
await waitForCSSLoaded;
|
||||
action();
|
||||
```
|
||||
|
||||
For example, see how we use this in [app/assets/javascripts/pages/projects/graphs/charts/index.js](https://gitlab.com/gitlab-org/gitlab/-/commit/5e90885d6afd4497002df55bf015b338efcfc3c5#02e81de37f5b1716a3ef3222fa7f7edf22c40969_9_8):
|
||||
|
||||
```javascript
|
||||
waitForCSSLoaded(() => {
|
||||
initMyWidget();
|
||||
const languagesContainer = document.getElementById('js-languages-chart');
|
||||
//...
|
||||
});
|
||||
```
|
||||
|
||||
|
|
|
@ -188,6 +188,30 @@ Check this [page](vuex.md) for more details.
|
|||
- It is acceptable for Vue to listen to existing jQuery events using jQuery event listeners.
|
||||
- It is not recommended to add new jQuery events for Vue to interact with jQuery.
|
||||
|
||||
### Mixing Vue and JavaScript classes (in the data function)
|
||||
|
||||
In the [Vue documentation](https://vuejs.org/v2/api/#Options-Data) the Data function/object is defined as follows:
|
||||
|
||||
> The data object for the Vue instance. Vue will recursively convert its properties into getter/setters to make it “reactive”. The object must be plain: native objects such as browser API objects and prototype properties are ignored. A rule of thumb is that data should just be data - it is not recommended to observe objects with their own stateful behavior.
|
||||
|
||||
Based on the Vue guidance:
|
||||
|
||||
- **Do not** use or create a JavaScript class in your [data function](https://vuejs.org/v2/api/#data), such as `user: new User()`.
|
||||
- **Do not** add new JavaScript class implementations.
|
||||
- **Do** use [GraphQL](../api_graphql_styleguide.md), [Vuex](vuex.md) or a set of components if cannot use simple primitives or objects.
|
||||
- **Do** maintain existing implementations using such approaches.
|
||||
- **Do** Migrate components to a pure object model when there are substantial changes to it.
|
||||
- **Do** add business logic to helpers or utils, so you can test them separately from your component.
|
||||
|
||||
#### Why
|
||||
|
||||
There are additional reasons why having a JavaScript class presents maintainability issues on a huge codebase:
|
||||
|
||||
- Once a class is created, it is easy to extend it in a way that can infringe Vue reactivity and best practices.
|
||||
- A class adds a layer of abstraction, which makes the component API and its inner workings less clear.
|
||||
- It makes it harder to test. Since the class is instantiated by the component data function, it is harder to 'manage' component and class separately.
|
||||
- Adding OOP to a functional codebase adds yet another way of writing code, reducing consistency and clarity.
|
||||
|
||||
## Style guide
|
||||
|
||||
Please refer to the Vue section of our [style guide](style/vue.md)
|
||||
|
|
|
@ -27,9 +27,10 @@ To disable sign ups:
|
|||
|
||||
## Require administrator approval for new sign ups
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4491) in GitLab 13.5.
|
||||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4491) in GitLab 13.5.
|
||||
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/267568) in GitLab 13.6.
|
||||
|
||||
When this setting is enabled, any user visiting your GitLab domain and signing up for a new account must be explicitly [approved](../approving_users.md#approving-a-user) by an administrator before they can start using their account. This setting is only applicable if sign ups are enabled.
|
||||
When this setting is enabled, any user visiting your GitLab domain and signing up for a new account must be explicitly [approved](../approving_users.md#approving-a-user) by an administrator before they can start using their account. This setting is enabled by default for newly created instances. This setting is only applicable if sign ups are enabled.
|
||||
|
||||
To require administrator approval for new sign ups:
|
||||
|
||||
|
|
|
@ -8,7 +8,8 @@ module API
|
|||
expose :expires_at
|
||||
expose :invite_email
|
||||
expose :invite_token
|
||||
expose :user_id
|
||||
expose :user_name, if: -> (member, _) { member.user.present? }
|
||||
expose :created_by_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,6 +27,13 @@ module API
|
|||
members
|
||||
end
|
||||
|
||||
def retrieve_member_invitations(source, query = nil)
|
||||
members = source_members(source).where.not(invite_token: nil)
|
||||
members = members.includes(:user)
|
||||
members = members.where(invite_email: query) if query.present?
|
||||
members
|
||||
end
|
||||
|
||||
def source_members(source)
|
||||
source.members
|
||||
end
|
||||
|
@ -52,6 +59,10 @@ module API
|
|||
def present_members(members)
|
||||
present members, with: Entities::Member, current_user: current_user, show_seat_info: params[:show_seat_info]
|
||||
end
|
||||
|
||||
def present_member_invitations(invitations)
|
||||
present invitations, with: Entities::Invitation, current_user: current_user
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
module API
|
||||
class Invitations < ::API::Base
|
||||
include PaginationParams
|
||||
|
||||
feature_category :users
|
||||
|
||||
before { authenticate! }
|
||||
|
@ -29,6 +31,23 @@ module API
|
|||
|
||||
::Members::InviteService.new(current_user, params).execute(source)
|
||||
end
|
||||
|
||||
desc 'Get a list of group or project invitations viewable by the authenticated user' do
|
||||
detail 'This feature was introduced in GitLab 13.6'
|
||||
success Entities::Invitation
|
||||
end
|
||||
params do
|
||||
optional :query, type: String, desc: 'A query string to search for members'
|
||||
use :pagination
|
||||
end
|
||||
get ":id/invitations" do
|
||||
source = find_source(source_type, params[:id])
|
||||
query = params[:query]
|
||||
|
||||
invitations = paginate(retrieve_member_invitations(source, query))
|
||||
|
||||
present_member_invitations invitations
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,8 +16,8 @@ module Gitlab
|
|||
@in_memory_application_settings = nil
|
||||
end
|
||||
|
||||
def method_missing(name, *args, &block)
|
||||
current_application_settings.send(name, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
|
||||
def method_missing(name, *args, **kwargs, &block)
|
||||
current_application_settings.send(name, *args, **kwargs, &block) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
|
||||
def respond_to_missing?(name, include_private = false)
|
||||
|
|
|
@ -28,6 +28,7 @@ module Gitlab
|
|||
gon.sprite_icons = IconsHelper.sprite_icon_path
|
||||
gon.sprite_file_icons = IconsHelper.sprite_file_icons_path
|
||||
gon.emoji_sprites_css_path = ActionController::Base.helpers.stylesheet_path('emoji_sprites')
|
||||
gon.select2_css_path = ActionController::Base.helpers.stylesheet_path('lazy_bundles/select2.css')
|
||||
gon.test_env = Rails.env.test?
|
||||
gon.disable_animations = Gitlab.config.gitlab['disable_animations']
|
||||
gon.suggested_label_colors = LabelsHelper.suggested_colors
|
||||
|
|
|
@ -5725,6 +5725,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|%{boldStart}Note:%{boldEnd} Requires Ingress to be installed."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|%{externalIp}.nip.io"
|
||||
msgstr ""
|
||||
|
||||
|
@ -11172,6 +11175,9 @@ msgstr ""
|
|||
msgid "Export as CSV"
|
||||
msgstr ""
|
||||
|
||||
msgid "Export commit custody report"
|
||||
msgstr ""
|
||||
|
||||
msgid "Export group"
|
||||
msgstr ""
|
||||
|
||||
|
@ -14621,6 +14627,9 @@ msgstr ""
|
|||
msgid "Integrations|All details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|All projects inheriting these settings will also be reset."
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Comment detail:"
|
||||
msgstr ""
|
||||
|
||||
|
@ -14654,9 +14663,18 @@ msgstr ""
|
|||
msgid "Integrations|Issues created in Jira are shown here once you have created the issues in project setup in Jira."
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Projects using custom settings will not be affected."
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Projects using custom settings will not be impacted unless the project owner chooses to use parent level defaults."
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Reset integration?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Resetting this integration will clear the settings and deactivate this integration."
|
||||
msgstr ""
|
||||
|
||||
msgid "Integrations|Return to GitLab for Jira"
|
||||
msgstr ""
|
||||
|
||||
|
@ -14768,6 +14786,9 @@ msgstr ""
|
|||
msgid "Invalid file."
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid hash"
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid import params"
|
||||
msgstr ""
|
||||
|
||||
|
@ -16842,6 +16863,9 @@ msgstr ""
|
|||
msgid "Merge automatically (%{strategy})"
|
||||
msgstr ""
|
||||
|
||||
msgid "Merge commit SHA"
|
||||
msgstr ""
|
||||
|
||||
msgid "Merge commit message"
|
||||
msgstr ""
|
||||
|
||||
|
@ -23062,6 +23086,9 @@ msgstr ""
|
|||
msgid "Resend it"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reset"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reset authorization key"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -18,15 +18,13 @@ module QA
|
|||
end
|
||||
|
||||
def sign_up!(user)
|
||||
fill_element :new_user_first_name_field, user.first_name
|
||||
fill_element :new_user_last_name_field, user.last_name
|
||||
fill_element :new_user_username_field, user.username
|
||||
fill_element :new_user_email_field, user.email
|
||||
fill_element :new_user_password_field, user.password
|
||||
|
||||
signed_in = retry_until do
|
||||
signed_in = retry_until(raise_on_failure: false) do
|
||||
fill_element :new_user_first_name_field, user.first_name
|
||||
fill_element :new_user_last_name_field, user.last_name
|
||||
fill_element :new_user_username_field, user.username
|
||||
fill_element :new_user_email_field, user.email
|
||||
fill_element :new_user_password_field, user.password
|
||||
click_element :new_user_register_button if has_element?(:new_user_register_button)
|
||||
|
||||
click_element :get_started_button if has_element?(:get_started_button)
|
||||
|
||||
Page::Main::Menu.perform(&:has_personal_area?)
|
||||
|
|
|
@ -33,7 +33,7 @@ module QA
|
|||
|
||||
def api_client
|
||||
@api_client ||= Runtime::API::Client.as_admin
|
||||
rescue AuthorizationError => e
|
||||
rescue API::Client::AuthorizationError => e
|
||||
raise "Administrator access is required to set application settings. #{e.message}"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ module QA
|
|||
module Env
|
||||
extend self
|
||||
|
||||
attr_writer :personal_access_token, :ldap_username, :ldap_password
|
||||
attr_writer :personal_access_token
|
||||
|
||||
ENV_VARIABLES = Gitlab::QA::Runtime::Env::ENV_VARIABLES
|
||||
|
||||
|
@ -293,6 +293,11 @@ module QA
|
|||
@ldap_username ||= ENV['GITLAB_LDAP_USERNAME']
|
||||
end
|
||||
|
||||
def ldap_username=(ldap_username)
|
||||
@ldap_username = ldap_username # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
ENV['GITLAB_LDAP_USERNAME'] = ldap_username
|
||||
end
|
||||
|
||||
def ldap_password
|
||||
@ldap_password ||= ENV['GITLAB_LDAP_PASSWORD']
|
||||
end
|
||||
|
|
|
@ -13,11 +13,39 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
RSpec.describe 'Manage', :skip_signup_disabled do
|
||||
describe 'standard', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/936' do
|
||||
RSpec.describe 'Manage', :skip_signup_disabled, :requires_admin do
|
||||
describe 'while LDAP is enabled', :orchestrated, :ldap_no_tls, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/935' do
|
||||
before do
|
||||
# When LDAP is enabled, a previous test might have created a token for the LDAP 'tanuki' user who is not an admin
|
||||
# So we need to set it to nil in order to create a new token for admin user so that we are able to set_application_settings
|
||||
# Also, when GITLAB_LDAP_USERNAME is provided, it is used to create a token. This also needs to be set to nil temporarily
|
||||
# for the same reason as above.
|
||||
|
||||
@personal_access_token = Runtime::Env.personal_access_token
|
||||
Runtime::Env.personal_access_token = nil
|
||||
ldap_username = Runtime::Env.ldap_username
|
||||
Runtime::Env.ldap_username = nil
|
||||
|
||||
disable_require_admin_approval_after_user_signup
|
||||
|
||||
Runtime::Env.ldap_username = ldap_username
|
||||
end
|
||||
|
||||
it_behaves_like 'registration and login'
|
||||
|
||||
context 'when user account is deleted', :requires_admin do
|
||||
after do
|
||||
Runtime::Env.personal_access_token = @personal_access_token
|
||||
end
|
||||
end
|
||||
|
||||
describe 'standard', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/936' do
|
||||
before(:all) do
|
||||
disable_require_admin_approval_after_user_signup
|
||||
end
|
||||
|
||||
it_behaves_like 'registration and login'
|
||||
|
||||
context 'when user account is deleted' do
|
||||
let(:user) do
|
||||
Resource::User.fabricate_via_api! do |resource|
|
||||
resource.api_client = admin_api_client
|
||||
|
@ -61,11 +89,10 @@ module QA
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe 'Manage', :orchestrated, :ldap_no_tls, :skip_signup_disabled do
|
||||
describe 'while LDAP is enabled', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/935' do
|
||||
it_behaves_like 'registration and login'
|
||||
def disable_require_admin_approval_after_user_signup
|
||||
Runtime::ApplicationSettings.set_application_settings(require_admin_approval_after_user_signup: false)
|
||||
sleep 10 # It takes a moment for the setting to come into effect
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,6 +7,7 @@ RSpec.describe RegistrationsController do
|
|||
|
||||
before do
|
||||
stub_feature_flags(invisible_captcha: false)
|
||||
stub_application_setting(require_admin_approval_after_user_signup: false)
|
||||
end
|
||||
|
||||
describe '#new' do
|
||||
|
@ -76,10 +77,6 @@ RSpec.describe RegistrationsController do
|
|||
end
|
||||
|
||||
context 'when the `require_admin_approval_after_user_signup` setting is turned off' do
|
||||
before do
|
||||
stub_application_setting(require_admin_approval_after_user_signup: false)
|
||||
end
|
||||
|
||||
it 'signs up the user in `active` state' do
|
||||
subject
|
||||
created_user = User.find_by(email: 'new@user.com')
|
||||
|
|
|
@ -10,6 +10,7 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
|
|||
let(:group_invite) { group.group_members.invite.last }
|
||||
|
||||
before do
|
||||
stub_application_setting(require_admin_approval_after_user_signup: false)
|
||||
project.add_maintainer(owner)
|
||||
group.add_owner(owner)
|
||||
group.add_developer('user@example.com', owner)
|
||||
|
@ -97,6 +98,21 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do
|
|||
click_link 'Register now'
|
||||
end
|
||||
|
||||
context 'with admin appoval required enabled' do
|
||||
before do
|
||||
stub_application_setting(require_admin_approval_after_user_signup: true)
|
||||
end
|
||||
|
||||
let(:send_email_confirmation) { true }
|
||||
|
||||
it 'does not sign the user in' do
|
||||
fill_in_sign_up_form(new_user)
|
||||
|
||||
expect(current_path).to eq(new_user_session_path)
|
||||
expect(page).to have_content('You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator')
|
||||
end
|
||||
end
|
||||
|
||||
context 'email confirmation disabled' do
|
||||
let(:send_email_confirmation) { false }
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ RSpec.describe 'Projects > Snippets > Create Snippet', :js do
|
|||
click_button('Create snippet')
|
||||
wait_for_requests
|
||||
|
||||
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
|
||||
link = find('a.no-attachment-icon img.js-lazy-loaded[alt="banana_sample"]')['src']
|
||||
expect(link).to match(%r{/#{Regexp.escape(project.full_path)}/uploads/\h{32}/banana_sample\.gif\z})
|
||||
end
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ RSpec.describe 'User uploads file to note' do
|
|||
click_button 'Comment'
|
||||
wait_for_requests
|
||||
|
||||
expect(find('a.no-attachment-icon img[alt="dk"]')['src'])
|
||||
expect(find('a.no-attachment-icon img.js-lazy-loaded[alt="dk"]')['src'])
|
||||
.to match(%r{/#{project.full_path}/uploads/\h{32}/dk\.png$})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -43,6 +43,10 @@ end
|
|||
RSpec.describe 'Signup' do
|
||||
include TermsHelper
|
||||
|
||||
before do
|
||||
stub_application_setting(require_admin_approval_after_user_signup: false)
|
||||
end
|
||||
|
||||
let(:new_user) { build_stubbed(:user) }
|
||||
|
||||
def fill_in_signup_form
|
||||
|
@ -228,6 +232,22 @@ RSpec.describe 'Signup' do
|
|||
expect(current_path).to eq users_sign_up_welcome_path
|
||||
end
|
||||
end
|
||||
|
||||
context 'with required admin approval enabled' do
|
||||
before do
|
||||
stub_application_setting(require_admin_approval_after_user_signup: true)
|
||||
end
|
||||
|
||||
it 'creates the user but does not sign them in' do
|
||||
visit new_user_registration_path
|
||||
|
||||
fill_in_signup_form
|
||||
|
||||
expect { click_button 'Register' }.to change { User.count }.by(1)
|
||||
expect(current_path).to eq new_user_session_path
|
||||
expect(page).to have_content("You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with errors' do
|
||||
|
|
|
@ -5,6 +5,7 @@ import IntegrationForm from '~/integrations/edit/components/integration_form.vue
|
|||
import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue';
|
||||
import ActiveCheckbox from '~/integrations/edit/components/active_checkbox.vue';
|
||||
import ConfirmationModal from '~/integrations/edit/components/confirmation_modal.vue';
|
||||
import ResetConfirmationModal from '~/integrations/edit/components/reset_confirmation_modal.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';
|
||||
|
@ -44,6 +45,8 @@ describe('IntegrationForm', () => {
|
|||
const findOverrideDropdown = () => wrapper.find(OverrideDropdown);
|
||||
const findActiveCheckbox = () => wrapper.find(ActiveCheckbox);
|
||||
const findConfirmationModal = () => wrapper.find(ConfirmationModal);
|
||||
const findResetConfirmationModal = () => wrapper.find(ResetConfirmationModal);
|
||||
const findResetButton = () => wrapper.find('[data-testid="reset-button"]');
|
||||
const findJiraTriggerFields = () => wrapper.find(JiraTriggerFields);
|
||||
const findJiraIssuesFields = () => wrapper.find(JiraIssuesFields);
|
||||
const findTriggerFields = () => wrapper.find(TriggerFields);
|
||||
|
@ -75,6 +78,29 @@ describe('IntegrationForm', () => {
|
|||
|
||||
expect(findConfirmationModal().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('resetPath is empty', () => {
|
||||
it('does not render ResetConfirmationModal and button', () => {
|
||||
createComponent({
|
||||
integrationLevel: integrationLevels.INSTANCE,
|
||||
});
|
||||
|
||||
expect(findResetButton().exists()).toBe(false);
|
||||
expect(findResetConfirmationModal().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetPath is present', () => {
|
||||
it('renders ResetConfirmationModal and button', () => {
|
||||
createComponent({
|
||||
integrationLevel: integrationLevels.INSTANCE,
|
||||
resetPath: 'resetPath',
|
||||
});
|
||||
|
||||
expect(findResetButton().exists()).toBe(true);
|
||||
expect(findResetConfirmationModal().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('integrationLevel is group', () => {
|
||||
|
@ -85,6 +111,29 @@ describe('IntegrationForm', () => {
|
|||
|
||||
expect(findConfirmationModal().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('resetPath is empty', () => {
|
||||
it('does not render ResetConfirmationModal and button', () => {
|
||||
createComponent({
|
||||
integrationLevel: integrationLevels.GROUP,
|
||||
});
|
||||
|
||||
expect(findResetButton().exists()).toBe(false);
|
||||
expect(findResetConfirmationModal().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetPath is present', () => {
|
||||
it('renders ResetConfirmationModal and button', () => {
|
||||
createComponent({
|
||||
integrationLevel: integrationLevels.GROUP,
|
||||
resetPath: 'resetPath',
|
||||
});
|
||||
|
||||
expect(findResetButton().exists()).toBe(true);
|
||||
expect(findResetConfirmationModal().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('integrationLevel is project', () => {
|
||||
|
@ -95,6 +144,16 @@ describe('IntegrationForm', () => {
|
|||
|
||||
expect(findConfirmationModal().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not render ResetConfirmationModal and button', () => {
|
||||
createComponent({
|
||||
integrationLevel: 'project',
|
||||
resetPath: 'resetPath',
|
||||
});
|
||||
|
||||
expect(findResetButton().exists()).toBe(false);
|
||||
expect(findResetConfirmationModal().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('type is "slack"', () => {
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import testAction from 'helpers/vuex_action_helper';
|
||||
import createState from '~/integrations/edit/store/state';
|
||||
import { setOverride } from '~/integrations/edit/store/actions';
|
||||
import {
|
||||
setOverride,
|
||||
setIsSaving,
|
||||
setIsTesting,
|
||||
setIsResetting,
|
||||
} from '~/integrations/edit/store/actions';
|
||||
import * as types from '~/integrations/edit/store/mutation_types';
|
||||
|
||||
describe('Integration form store actions', () => {
|
||||
|
@ -15,4 +20,24 @@ describe('Integration form store actions', () => {
|
|||
return testAction(setOverride, true, state, [{ type: types.SET_OVERRIDE, payload: true }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setIsSaving', () => {
|
||||
it('should commit isSaving mutation', () => {
|
||||
return testAction(setIsSaving, true, state, [{ type: types.SET_IS_SAVING, payload: true }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setIsTesting', () => {
|
||||
it('should commit isTesting mutation', () => {
|
||||
return testAction(setIsTesting, true, state, [{ type: types.SET_IS_TESTING, payload: true }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setIsResetting', () => {
|
||||
it('should commit isResetting mutation', () => {
|
||||
return testAction(setIsResetting, true, state, [
|
||||
{ type: types.SET_IS_RESETTING, payload: true },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
import { currentKey, isInheriting, propsSource } from '~/integrations/edit/store/getters';
|
||||
import {
|
||||
currentKey,
|
||||
isInheriting,
|
||||
isDisabled,
|
||||
propsSource,
|
||||
} from '~/integrations/edit/store/getters';
|
||||
import createState from '~/integrations/edit/store/state';
|
||||
import mutations from '~/integrations/edit/store/mutations';
|
||||
import * as types from '~/integrations/edit/store/mutation_types';
|
||||
import { mockIntegrationProps } from '../mock_data';
|
||||
|
||||
describe('Integration form store getters', () => {
|
||||
|
@ -45,6 +52,29 @@ describe('Integration form store getters', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('isDisabled', () => {
|
||||
it.each`
|
||||
isSaving | isTesting | isResetting | expected
|
||||
${false} | ${false} | ${false} | ${false}
|
||||
${true} | ${false} | ${false} | ${true}
|
||||
${false} | ${true} | ${false} | ${true}
|
||||
${false} | ${false} | ${true} | ${true}
|
||||
${false} | ${true} | ${true} | ${true}
|
||||
${true} | ${false} | ${true} | ${true}
|
||||
${true} | ${true} | ${false} | ${true}
|
||||
${true} | ${true} | ${true} | ${true}
|
||||
`(
|
||||
'when isSaving = $isSaving, isTesting = $isTesting, isResetting = $isResetting then isDisabled = $expected',
|
||||
({ isSaving, isTesting, isResetting, expected }) => {
|
||||
mutations[types.SET_IS_SAVING](state, isSaving);
|
||||
mutations[types.SET_IS_TESTING](state, isTesting);
|
||||
mutations[types.SET_IS_RESETTING](state, isResetting);
|
||||
|
||||
expect(isDisabled(state)).toBe(expected);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('propsSource', () => {
|
||||
beforeEach(() => {
|
||||
state.defaultState = defaultState;
|
||||
|
|
|
@ -16,4 +16,28 @@ describe('Integration form store mutations', () => {
|
|||
expect(state.override).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`${types.SET_IS_SAVING}`, () => {
|
||||
it('sets isSaving', () => {
|
||||
mutations[types.SET_IS_SAVING](state, true);
|
||||
|
||||
expect(state.isSaving).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`${types.SET_IS_TESTING}`, () => {
|
||||
it('sets isTesting', () => {
|
||||
mutations[types.SET_IS_TESTING](state, true);
|
||||
|
||||
expect(state.isTesting).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`${types.SET_IS_RESETTING}`, () => {
|
||||
it('sets isResetting', () => {
|
||||
mutations[types.SET_IS_RESETTING](state, true);
|
||||
|
||||
expect(state.isResetting).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ describe('Integration form state factory', () => {
|
|||
customState: {},
|
||||
isSaving: false,
|
||||
isTesting: false,
|
||||
isResetting: false,
|
||||
override: false,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -325,4 +325,19 @@ describe('text_utility', () => {
|
|||
expect(textUtils.hasContent(txt)).toEqual(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidSha1Hash', () => {
|
||||
const validSha1Hash = '92d10c15';
|
||||
const stringOver40 = new Array(42).join('a');
|
||||
|
||||
it.each`
|
||||
hash | valid
|
||||
${validSha1Hash} | ${true}
|
||||
${'__characters'} | ${false}
|
||||
${'abc'} | ${false}
|
||||
${stringOver40} | ${false}
|
||||
`(`returns $valid for $hash`, ({ hash, valid }) => {
|
||||
expect(textUtils.isValidSha1Hash(hash)).toBe(valid);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,7 +7,8 @@ RSpec.describe API::Invitations do
|
|||
let(:developer) { create(:user) }
|
||||
let(:access_requester) { create(:user) }
|
||||
let(:stranger) { create(:user) }
|
||||
let(:email) { 'email@example.org' }
|
||||
let(:email) { 'email1@example.com' }
|
||||
let(:email2) { 'email2@example.com' }
|
||||
|
||||
let(:project) do
|
||||
create(:project, :public, creator_id: maintainer.id, namespace: maintainer.namespace) do |project|
|
||||
|
@ -75,7 +76,7 @@ RSpec.describe API::Invitations do
|
|||
|
||||
it 'invites a list of new email addresses' do
|
||||
expect do
|
||||
email_list = 'email1@example.com,email2@example.com'
|
||||
email_list = [email, email2].join(',')
|
||||
|
||||
post api("/#{source_type.pluralize}/#{source.id}/invitations", maintainer),
|
||||
params: { email: email_list, access_level: Member::DEVELOPER }
|
||||
|
@ -204,4 +205,97 @@ RSpec.describe API::Invitations do
|
|||
let(:source) { group }
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'GET /:source_type/:id/invitations' do |source_type|
|
||||
context "with :source_type == #{source_type.pluralize}" do
|
||||
it_behaves_like 'a 404 response when source is private' do
|
||||
let(:route) { get invitations_url(source, stranger) }
|
||||
end
|
||||
|
||||
%i[maintainer developer access_requester stranger].each do |type|
|
||||
context "when authenticated as a #{type}" do
|
||||
it 'returns 200' do
|
||||
user = public_send(type)
|
||||
|
||||
get invitations_url(source, user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.size).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'avoids N+1 queries' do
|
||||
# Establish baseline
|
||||
get invitations_url(source, maintainer)
|
||||
|
||||
control = ActiveRecord::QueryRecorder.new do
|
||||
get invitations_url(source, maintainer)
|
||||
end
|
||||
|
||||
invite_member_by_email(source, source_type, email, maintainer)
|
||||
|
||||
expect do
|
||||
get invitations_url(source, maintainer)
|
||||
end.not_to exceed_query_limit(control)
|
||||
end
|
||||
|
||||
it 'does not find confirmed members' do
|
||||
get invitations_url(source, developer)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.size).to eq(0)
|
||||
expect(json_response.map { |u| u['id'] }).not_to match_array [maintainer.id, developer.id]
|
||||
end
|
||||
|
||||
it 'finds all members with no query string specified' do
|
||||
invite_member_by_email(source, source_type, email, developer)
|
||||
invite_member_by_email(source, source_type, email2, developer)
|
||||
|
||||
get invitations_url(source, developer), params: { query: '' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.count).to eq(2)
|
||||
expect(json_response.map { |u| u['invite_email'] }).to match_array [email, email2]
|
||||
end
|
||||
|
||||
it 'finds the invitation by invite_email with query string' do
|
||||
invite_member_by_email(source, source_type, email, developer)
|
||||
invite_member_by_email(source, source_type, email2, developer)
|
||||
|
||||
get invitations_url(source, developer), params: { query: email }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.count).to eq(1)
|
||||
expect(json_response.first['invite_email']).to eq(email)
|
||||
expect(json_response.first['created_by_name']).to eq(developer.name)
|
||||
expect(json_response.first['user_name']).to eq(nil)
|
||||
end
|
||||
|
||||
def invite_member_by_email(source, source_type, email, created_by)
|
||||
create(:"#{source_type}_member", invite_token: '123', invite_email: email, source: source, user: nil, created_by: created_by)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /projects/:id/invitations' do
|
||||
it_behaves_like 'GET /:source_type/:id/invitations', 'project' do
|
||||
let(:source) { project }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /groups/:id/invitations' do
|
||||
it_behaves_like 'GET /:source_type/:id/invitations', 'group' do
|
||||
let(:source) { group }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -41,7 +41,7 @@ RSpec.describe API::Settings, 'Settings' do
|
|||
expect(json_response['spam_check_endpoint_enabled']).to be_falsey
|
||||
expect(json_response['spam_check_endpoint_url']).to be_nil
|
||||
expect(json_response['wiki_page_max_content_bytes']).to be_a(Integer)
|
||||
expect(json_response['require_admin_approval_after_user_signup']).to eq(false)
|
||||
expect(json_response['require_admin_approval_after_user_signup']).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
4
tooling/README.md
Normal file
4
tooling/README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Tooling
|
||||
|
||||
This directory contains tools and configuration for development only.
|
||||
|
5
tooling/eslint-config/conditionally_ignore_ee.js
Normal file
5
tooling/eslint-config/conditionally_ignore_ee.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
/* eslint-disable import/no-commonjs */
|
||||
|
||||
const IS_EE = require('../../config/helpers/is_ee_env');
|
||||
|
||||
module.exports = IS_EE ? {} : { ignorePatterns: ['ee/**/*.*'] };
|
Loading…
Reference in a new issue