Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e0df70a614
commit
921173681c
|
@ -102,7 +102,7 @@
|
||||||
- name: postgres:11.6
|
- name: postgres:11.6
|
||||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||||
- name: redis:5.0-alpine
|
- name: redis:5.0-alpine
|
||||||
- name: elasticsearch:7.10.1
|
- name: elasticsearch:7.11.1
|
||||||
command: ["elasticsearch", "-E", "discovery.type=single-node"]
|
command: ["elasticsearch", "-E", "discovery.type=single-node"]
|
||||||
variables:
|
variables:
|
||||||
POSTGRES_HOST_AUTH_METHOD: trust
|
POSTGRES_HOST_AUTH_METHOD: trust
|
||||||
|
@ -113,7 +113,7 @@
|
||||||
- name: postgres:12
|
- name: postgres:12
|
||||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||||
- name: redis:5.0-alpine
|
- name: redis:5.0-alpine
|
||||||
- name: elasticsearch:7.10.1
|
- name: elasticsearch:7.11.1
|
||||||
command: ["elasticsearch", "-E", "discovery.type=single-node"]
|
command: ["elasticsearch", "-E", "discovery.type=single-node"]
|
||||||
variables:
|
variables:
|
||||||
POSTGRES_HOST_AUTH_METHOD: trust
|
POSTGRES_HOST_AUTH_METHOD: trust
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default {
|
||||||
GlTooltip: GlTooltipDirective,
|
GlTooltip: GlTooltipDirective,
|
||||||
},
|
},
|
||||||
mixins: [boardCardInner],
|
mixins: [boardCardInner],
|
||||||
inject: ['groupId', 'rootPath', 'scopedLabelsAvailable'],
|
inject: ['rootPath', 'scopedLabelsAvailable'],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
|
@ -107,7 +107,7 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['isEpicBoard']),
|
...mapGetters(['isEpicBoard', 'isGroupBoard', 'isProjectBoard']),
|
||||||
isNewForm() {
|
isNewForm() {
|
||||||
return this.currentPage === formType.new;
|
return this.currentPage === formType.new;
|
||||||
},
|
},
|
||||||
|
@ -178,8 +178,8 @@ export default {
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
...variables,
|
...variables,
|
||||||
projectPath: this.projectId ? this.fullPath : undefined,
|
projectPath: this.isProjectBoard ? this.fullPath : undefined,
|
||||||
groupPath: this.groupId ? this.fullPath : undefined,
|
groupPath: this.isGroupBoard ? this.fullPath : undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
boardScopeMutationVariables() {
|
boardScopeMutationVariables() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlButton } from '@gitlab/ui';
|
import { GlButton } from '@gitlab/ui';
|
||||||
import { mapActions, mapState } from 'vuex';
|
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||||
import { getMilestone } from 'ee_else_ce/boards/boards_util';
|
import { getMilestone } from 'ee_else_ce/boards/boards_util';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||||
|
@ -32,8 +32,9 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['selectedProject']),
|
...mapState(['selectedProject']),
|
||||||
|
...mapGetters(['isGroupBoard']),
|
||||||
disabled() {
|
disabled() {
|
||||||
if (this.groupId) {
|
if (this.isGroupBoard) {
|
||||||
return this.title === '' || !this.selectedProject.name;
|
return this.title === '' || !this.selectedProject.name;
|
||||||
}
|
}
|
||||||
return this.title === '';
|
return this.title === '';
|
||||||
|
@ -98,7 +99,7 @@ export default {
|
||||||
name="issue_title"
|
name="issue_title"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
<project-select v-if="groupId" :group-id="groupId" :list="list" />
|
<project-select v-if="isGroupBoard" :group-id="groupId" :list="list" />
|
||||||
<div class="clearfix gl-mt-3">
|
<div class="clearfix gl-mt-3">
|
||||||
<gl-button
|
<gl-button
|
||||||
ref="submitButton"
|
ref="submitButton"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlButton } from '@gitlab/ui';
|
import { GlButton } from '@gitlab/ui';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
import { getMilestone } from 'ee_else_ce/boards/boards_util';
|
import { getMilestone } from 'ee_else_ce/boards/boards_util';
|
||||||
import ListIssue from 'ee_else_ce/boards/models/issue';
|
import ListIssue from 'ee_else_ce/boards/models/issue';
|
||||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||||
|
@ -31,8 +32,9 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapGetters(['isGroupBoard']),
|
||||||
disabled() {
|
disabled() {
|
||||||
if (this.groupId) {
|
if (this.isGroupBoard) {
|
||||||
return this.title === '' || !this.selectedProject.name;
|
return this.title === '' || !this.selectedProject.name;
|
||||||
}
|
}
|
||||||
return this.title === '';
|
return this.title === '';
|
||||||
|
@ -110,7 +112,7 @@ export default {
|
||||||
name="issue_title"
|
name="issue_title"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
<project-select v-if="groupId" :group-id="groupId" :list="list" />
|
<project-select v-if="isGroupBoard" :group-id="groupId" :list="list" />
|
||||||
<div class="clearfix gl-mt-3">
|
<div class="clearfix gl-mt-3">
|
||||||
<gl-button
|
<gl-button
|
||||||
ref="submitButton"
|
ref="submitButton"
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
GlModalDirective,
|
GlModalDirective,
|
||||||
} from '@gitlab/ui';
|
} from '@gitlab/ui';
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
|
import { mapGetters, mapState } from 'vuex';
|
||||||
|
|
||||||
import BoardForm from 'ee_else_ce/boards/components/board_form.vue';
|
import BoardForm from 'ee_else_ce/boards/components/board_form.vue';
|
||||||
|
|
||||||
|
@ -109,8 +110,10 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapState(['boardType']),
|
||||||
|
...mapGetters(['isGroupBoard']),
|
||||||
parentType() {
|
parentType() {
|
||||||
return this.groupId ? 'group' : 'project';
|
return this.boardType;
|
||||||
},
|
},
|
||||||
loading() {
|
loading() {
|
||||||
return this.loadingRecentBoards || Boolean(this.loadingBoards);
|
return this.loadingRecentBoards || Boolean(this.loadingBoards);
|
||||||
|
@ -171,7 +174,7 @@ export default {
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
boardQuery() {
|
boardQuery() {
|
||||||
return this.groupId ? groupQuery : projectQuery;
|
return this.isGroupBoard ? groupQuery : projectQuery;
|
||||||
},
|
},
|
||||||
loadBoards(toggleDropdown = true) {
|
loadBoards(toggleDropdown = true) {
|
||||||
if (toggleDropdown && this.boards.length > 0) {
|
if (toggleDropdown && this.boards.length > 0) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
GlModalDirective,
|
GlModalDirective,
|
||||||
} from '@gitlab/ui';
|
} from '@gitlab/ui';
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
|
import { mapGetters, mapState } from 'vuex';
|
||||||
|
|
||||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||||
import httpStatusCodes from '~/lib/utils/http_status';
|
import httpStatusCodes from '~/lib/utils/http_status';
|
||||||
|
@ -108,8 +109,10 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapState(['boardType']),
|
||||||
|
...mapGetters(['isGroupBoard']),
|
||||||
parentType() {
|
parentType() {
|
||||||
return this.groupId ? 'group' : 'project';
|
return this.boardType;
|
||||||
},
|
},
|
||||||
loading() {
|
loading() {
|
||||||
return this.loadingRecentBoards || Boolean(this.loadingBoards);
|
return this.loadingRecentBoards || Boolean(this.loadingBoards);
|
||||||
|
@ -167,7 +170,7 @@ export default {
|
||||||
return { fullPath: this.state.endpoints.fullPath };
|
return { fullPath: this.state.endpoints.fullPath };
|
||||||
},
|
},
|
||||||
query() {
|
query() {
|
||||||
return this.groupId ? groupQuery : projectQuery;
|
return this.isGroupBoard ? groupQuery : projectQuery;
|
||||||
},
|
},
|
||||||
loadingKey: 'loadingBoards',
|
loadingKey: 'loadingBoards',
|
||||||
update(data) {
|
update(data) {
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
<script>
|
||||||
|
import { mapActions } from 'vuex';
|
||||||
|
import { historyPushState } from '~/lib/utils/common_utils';
|
||||||
|
import { setUrlParams } from '~/lib/utils/url_utility';
|
||||||
|
import { __ } from '~/locale';
|
||||||
|
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
i18n: {
|
||||||
|
search: __('Search'),
|
||||||
|
},
|
||||||
|
components: { FilteredSearch },
|
||||||
|
props: {
|
||||||
|
search: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
initialSearch() {
|
||||||
|
return [{ type: 'filtered-search-term', value: { data: this.search } }];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['performSearch']),
|
||||||
|
handleSearch(filters) {
|
||||||
|
let itemValue = '';
|
||||||
|
const [item] = filters;
|
||||||
|
|
||||||
|
if (filters.length === 0) {
|
||||||
|
itemValue = '';
|
||||||
|
} else {
|
||||||
|
itemValue = item?.value?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
historyPushState(setUrlParams({ search: itemValue }, window.location.href));
|
||||||
|
|
||||||
|
this.performSearch();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<filtered-search
|
||||||
|
class="gl-w-full"
|
||||||
|
namespace=""
|
||||||
|
:tokens="[]"
|
||||||
|
:search-input-placeholder="$options.i18n.search"
|
||||||
|
:initial-filter-value="initialSearch"
|
||||||
|
@onFilter="handleSearch"
|
||||||
|
/>
|
||||||
|
</template>
|
|
@ -0,0 +1,25 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import store from '~/boards/stores';
|
||||||
|
import { queryToObject } from '~/lib/utils/url_utility';
|
||||||
|
import FilteredSearch from './components/filtered_search.vue';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const queryParams = queryToObject(window.location.search);
|
||||||
|
const el = document.getElementById('js-board-filtered-search');
|
||||||
|
|
||||||
|
/*
|
||||||
|
When https://github.com/vuejs/vue-apollo/pull/1153 is merged and deployed
|
||||||
|
we can remove apolloProvider option from here. Currently without it its causing
|
||||||
|
an error
|
||||||
|
*/
|
||||||
|
|
||||||
|
return new Vue({
|
||||||
|
el,
|
||||||
|
store,
|
||||||
|
apolloProvider: {},
|
||||||
|
render: (createElement) =>
|
||||||
|
createElement(FilteredSearch, {
|
||||||
|
props: { search: queryParams.search },
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
};
|
|
@ -53,7 +53,6 @@ let issueBoardsApp;
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const $boardApp = document.getElementById('board-app');
|
const $boardApp = document.getElementById('board-app');
|
||||||
|
|
||||||
// check for browser back and trigger a hard reload to circumvent browser caching.
|
// check for browser back and trigger a hard reload to circumvent browser caching.
|
||||||
window.addEventListener('pageshow', (event) => {
|
window.addEventListener('pageshow', (event) => {
|
||||||
const isNavTypeBackForward =
|
const isNavTypeBackForward =
|
||||||
|
@ -73,6 +72,14 @@ export default () => {
|
||||||
boardsStore.setTimeTrackingLimitToHours($boardApp.dataset.timeTrackingLimitToHours);
|
boardsStore.setTimeTrackingLimitToHours($boardApp.dataset.timeTrackingLimitToHours);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gon?.features?.boardsFilteredSearch) {
|
||||||
|
import('~/boards/filtered_search')
|
||||||
|
.then(({ default: initFilteredSearch }) => {
|
||||||
|
initFilteredSearch(apolloProvider);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @gitlab/no-runtime-template-compiler
|
// eslint-disable-next-line @gitlab/no-runtime-template-compiler
|
||||||
issueBoardsApp = new Vue({
|
issueBoardsApp = new Vue({
|
||||||
el: $boardApp,
|
el: $boardApp,
|
||||||
|
@ -164,8 +171,15 @@ export default () => {
|
||||||
eventHub.$off('initialBoardLoad', this.initialBoardLoad);
|
eventHub.$off('initialBoardLoad', this.initialBoardLoad);
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.filterManager = new FilteredSearchBoards(boardsStore.filter, true, boardsStore.cantEdit);
|
if (!gon.features?.boardsFilteredSearch) {
|
||||||
this.filterManager.setup();
|
this.filterManager = new FilteredSearchBoards(
|
||||||
|
boardsStore.filter,
|
||||||
|
true,
|
||||||
|
boardsStore.cantEdit,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.filterManager.setup();
|
||||||
|
}
|
||||||
|
|
||||||
this.performSearch();
|
this.performSearch();
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { find } from 'lodash';
|
import { find } from 'lodash';
|
||||||
import { inactiveId } from '../constants';
|
import { BoardType, inactiveId } from '../constants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
isGroupBoard: (state) => state.boardType === BoardType.group,
|
||||||
|
isProjectBoard: (state) => state.boardType === BoardType.project,
|
||||||
isSidebarOpen: (state) => state.activeId !== inactiveId,
|
isSidebarOpen: (state) => state.activeId !== inactiveId,
|
||||||
isSwimlanesOn: () => false,
|
isSwimlanesOn: () => false,
|
||||||
getBoardItemById: (state) => (id) => {
|
getBoardItemById: (state) => (id) => {
|
||||||
|
|
|
@ -1,29 +1,36 @@
|
||||||
<script>
|
<script>
|
||||||
/* eslint-disable vue/no-v-html */
|
import {
|
||||||
import { GlLoadingIcon, GlBadge, GlTooltipDirective } from '@gitlab/ui';
|
GlLoadingIcon,
|
||||||
import { visitUrl } from '../../lib/utils/url_utility';
|
GlBadge,
|
||||||
import identicon from '../../vue_shared/components/identicon.vue';
|
GlIcon,
|
||||||
|
GlTooltipDirective,
|
||||||
|
GlSafeHtmlDirective,
|
||||||
|
} from '@gitlab/ui';
|
||||||
|
import { visitUrl } from '~/lib/utils/url_utility';
|
||||||
|
import identicon from '~/vue_shared/components/identicon.vue';
|
||||||
|
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
|
||||||
import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '../constants';
|
import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '../constants';
|
||||||
import eventHub from '../event_hub';
|
import eventHub from '../event_hub';
|
||||||
|
|
||||||
import itemActions from './item_actions.vue';
|
import itemActions from './item_actions.vue';
|
||||||
import itemCaret from './item_caret.vue';
|
import itemCaret from './item_caret.vue';
|
||||||
import itemStats from './item_stats.vue';
|
import itemStats from './item_stats.vue';
|
||||||
import itemStatsValue from './item_stats_value.vue';
|
|
||||||
import itemTypeIcon from './item_type_icon.vue';
|
import itemTypeIcon from './item_type_icon.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
directives: {
|
directives: {
|
||||||
GlTooltip: GlTooltipDirective,
|
GlTooltip: GlTooltipDirective,
|
||||||
|
SafeHtml: GlSafeHtmlDirective,
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
GlBadge,
|
GlBadge,
|
||||||
GlLoadingIcon,
|
GlLoadingIcon,
|
||||||
|
GlIcon,
|
||||||
|
UserAccessRoleBadge,
|
||||||
identicon,
|
identicon,
|
||||||
itemCaret,
|
itemCaret,
|
||||||
itemTypeIcon,
|
itemTypeIcon,
|
||||||
itemStats,
|
itemStats,
|
||||||
itemStatsValue,
|
|
||||||
itemActions,
|
itemActions,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
@ -91,6 +98,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -140,28 +148,31 @@ export default {
|
||||||
data-testid="group-name"
|
data-testid="group-name"
|
||||||
:href="group.relativePath"
|
:href="group.relativePath"
|
||||||
:title="group.fullName"
|
:title="group.fullName"
|
||||||
class="no-expand gl-mt-3 gl-mr-3 gl-text-gray-900!"
|
class="no-expand gl-mr-3 gl-mt-3 gl-text-gray-900!"
|
||||||
:itemprop="microdata.nameItemprop"
|
:itemprop="microdata.nameItemprop"
|
||||||
>{{
|
>
|
||||||
|
{{
|
||||||
// ending bracket must be by closing tag to prevent
|
// ending bracket must be by closing tag to prevent
|
||||||
// link hover text-decoration from over-extending
|
// link hover text-decoration from over-extending
|
||||||
group.name
|
group.name
|
||||||
}}</a
|
}}
|
||||||
>
|
</a>
|
||||||
<item-stats-value
|
<gl-icon
|
||||||
:icon-name="visibilityIcon"
|
v-gl-tooltip.hover.bottom
|
||||||
|
class="gl-display-inline-flex gl-align-items-center gl-mr-3 gl-mt-3 gl-text-gray-500"
|
||||||
|
:name="visibilityIcon"
|
||||||
:title="visibilityTooltip"
|
:title="visibilityTooltip"
|
||||||
css-class="item-visibility d-inline-flex align-items-center gl-mt-3 gl-mr-2 text-secondary"
|
data-testid="group-visibility-icon"
|
||||||
/>
|
/>
|
||||||
<span v-if="group.permission" class="user-access-role gl-mt-3">
|
<user-access-role-badge v-if="group.permission" class="gl-mt-3">
|
||||||
{{ group.permission }}
|
{{ group.permission }}
|
||||||
</span>
|
</user-access-role-badge>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="group.description" class="description">
|
<div v-if="group.description" class="description">
|
||||||
<span
|
<span
|
||||||
|
v-safe-html:[$options.safeHtmlConfig]="group.description"
|
||||||
:itemprop="microdata.descriptionItemprop"
|
:itemprop="microdata.descriptionItemprop"
|
||||||
data-testid="group-description"
|
data-testid="group-description"
|
||||||
v-html="group.description"
|
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { deprecatedCreateFlash as flash } from '~/flash';
|
||||||
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
|
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
|
||||||
import { __, sprintf } from '~/locale';
|
import { __, sprintf } from '~/locale';
|
||||||
import eventHub from '~/sidebar/event_hub';
|
import eventHub from '~/sidebar/event_hub';
|
||||||
|
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
|
||||||
import { splitCamelCase } from '../../lib/utils/text_utility';
|
import { splitCamelCase } from '../../lib/utils/text_utility';
|
||||||
import ReplyButton from './note_actions/reply_button.vue';
|
import ReplyButton from './note_actions/reply_button.vue';
|
||||||
|
|
||||||
|
@ -17,6 +18,7 @@ export default {
|
||||||
ReplyButton,
|
ReplyButton,
|
||||||
GlButton,
|
GlButton,
|
||||||
GlDropdownItem,
|
GlDropdownItem,
|
||||||
|
UserAccessRoleBadge,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
GlTooltip: GlTooltipDirective,
|
GlTooltip: GlTooltipDirective,
|
||||||
|
@ -226,24 +228,30 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="note-actions">
|
<div class="note-actions">
|
||||||
<span
|
<user-access-role-badge
|
||||||
v-if="isAuthor"
|
v-if="isAuthor"
|
||||||
class="note-role user-access-role has-tooltip d-none d-md-inline-block"
|
v-gl-tooltip
|
||||||
|
class="gl-mx-3 d-none d-md-inline-block"
|
||||||
:title="displayAuthorBadgeText"
|
:title="displayAuthorBadgeText"
|
||||||
>{{ __('Author') }}</span
|
|
||||||
>
|
>
|
||||||
<span
|
{{ __('Author') }}
|
||||||
|
</user-access-role-badge>
|
||||||
|
<user-access-role-badge
|
||||||
v-if="accessLevel"
|
v-if="accessLevel"
|
||||||
class="note-role user-access-role has-tooltip"
|
v-gl-tooltip
|
||||||
|
class="gl-mx-3"
|
||||||
:title="displayMemberBadgeText"
|
:title="displayMemberBadgeText"
|
||||||
>{{ accessLevel }}</span
|
|
||||||
>
|
>
|
||||||
<span
|
{{ accessLevel }}
|
||||||
|
</user-access-role-badge>
|
||||||
|
<user-access-role-badge
|
||||||
v-else-if="isContributor"
|
v-else-if="isContributor"
|
||||||
class="note-role user-access-role has-tooltip"
|
v-gl-tooltip
|
||||||
|
class="gl-mx-3"
|
||||||
:title="displayContributorBadgeText"
|
:title="displayContributorBadgeText"
|
||||||
>{{ __('Contributor') }}</span
|
|
||||||
>
|
>
|
||||||
|
{{ __('Contributor') }}
|
||||||
|
</user-access-role-badge>
|
||||||
<gl-button
|
<gl-button
|
||||||
v-if="canResolve"
|
v-if="canResolve"
|
||||||
ref="resolveButton"
|
ref="resolveButton"
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
} from '@gitlab/ui';
|
} from '@gitlab/ui';
|
||||||
import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '~/groups/constants';
|
import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '~/groups/constants';
|
||||||
import csrf from '~/lib/utils/csrf';
|
import csrf from '~/lib/utils/csrf';
|
||||||
|
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -20,6 +21,7 @@ export default {
|
||||||
GlButton,
|
GlButton,
|
||||||
GlTooltip,
|
GlTooltip,
|
||||||
GlLink,
|
GlLink,
|
||||||
|
UserAccessRoleBadge,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
GlTooltip: GlTooltipDirective,
|
GlTooltip: GlTooltipDirective,
|
||||||
|
@ -72,7 +74,9 @@ export default {
|
||||||
<template>
|
<template>
|
||||||
<li :class="rowClass" class="group-row">
|
<li :class="rowClass" class="group-row">
|
||||||
<div class="group-row-contents gl-display-flex gl-align-items-center gl-py-3 gl-pr-5">
|
<div class="group-row-contents gl-display-flex gl-align-items-center gl-py-3 gl-pr-5">
|
||||||
<div class="folder-toggle-wrap gl-mr-2 gl-display-flex gl-align-items-center">
|
<div
|
||||||
|
class="folder-toggle-wrap gl-mr-3 gl-display-flex gl-align-items-center gl-text-gray-500"
|
||||||
|
>
|
||||||
<gl-icon name="folder-o" />
|
<gl-icon name="folder-o" />
|
||||||
</div>
|
</div>
|
||||||
<gl-link
|
<gl-link
|
||||||
|
@ -84,12 +88,12 @@ export default {
|
||||||
<div class="gl-min-w-0 gl-display-flex gl-flex-grow-1 gl-flex-shrink-1 gl-align-items-center">
|
<div class="gl-min-w-0 gl-display-flex gl-flex-grow-1 gl-flex-shrink-1 gl-align-items-center">
|
||||||
<div class="gl-min-w-0 gl-flex-grow-1 flex-shrink-1">
|
<div class="gl-min-w-0 gl-flex-grow-1 flex-shrink-1">
|
||||||
<div class="title gl-display-flex gl-align-items-center gl-flex-wrap gl-mr-3">
|
<div class="title gl-display-flex gl-align-items-center gl-flex-wrap gl-mr-3">
|
||||||
<gl-link :href="group.relative_path" class="gl-mt-3 gl-mr-3 gl-text-gray-900!">{{
|
<gl-link :href="group.relative_path" class="gl-mt-3 gl-mr-3 gl-text-gray-900!">
|
||||||
group.full_name
|
{{ group.full_name }}
|
||||||
}}</gl-link>
|
</gl-link>
|
||||||
<gl-icon
|
<gl-icon
|
||||||
v-gl-tooltip.hover.bottom
|
v-gl-tooltip.hover.bottom
|
||||||
class="gl-mr-0 gl-inline-flex gl-mt-3 text-secondary"
|
class="gl-display-inline-flex gl-mt-3 gl-mr-3 gl-text-gray-500"
|
||||||
:name="visibilityIcon"
|
:name="visibilityIcon"
|
||||||
:title="visibilityTooltip"
|
:title="visibilityTooltip"
|
||||||
/>
|
/>
|
||||||
|
@ -99,11 +103,11 @@ export default {
|
||||||
class="gl-display-none gl-sm-display-flex gl-mt-3 gl-mr-1"
|
class="gl-display-none gl-sm-display-flex gl-mt-3 gl-mr-1"
|
||||||
>{{ __('pending removal') }}</gl-badge
|
>{{ __('pending removal') }}</gl-badge
|
||||||
>
|
>
|
||||||
<span v-if="group.permission" class="user-access-role gl-mt-3">
|
<user-access-role-badge v-if="group.permission" class="gl-mt-3">
|
||||||
{{ group.permission }}
|
{{ group.permission }}
|
||||||
</span>
|
</user-access-role-badge>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="group.description" class="description">
|
<div v-if="group.description" class="description gl-line-height-20">
|
||||||
<span v-safe-html="group.markdown_description"> </span>
|
<span v-safe-html="group.markdown_description"> </span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -208,6 +208,7 @@ export default {
|
||||||
ref="searchBox"
|
ref="searchBox"
|
||||||
v-model.trim="query"
|
v-model.trim="query"
|
||||||
:placeholder="i18n.searchPlaceholder"
|
:placeholder="i18n.searchPlaceholder"
|
||||||
|
autocomplete="off"
|
||||||
@input="onSearchBoxInput"
|
@input="onSearchBoxInput"
|
||||||
@keydown.enter.prevent="onSearchBoxEnter"
|
@keydown.enter.prevent="onSearchBoxEnter"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* This component applies particular styling to GlBadge that isn't
|
||||||
|
* available in the current GlBadge variants.
|
||||||
|
* Where possible, prefer one of the supported GlBadge variants.
|
||||||
|
* Discussion issue: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1247
|
||||||
|
*/
|
||||||
|
import { GlBadge } from '@gitlab/ui';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'UserAccessRoleBadge',
|
||||||
|
components: {
|
||||||
|
GlBadge,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<gl-badge class="gl-bg-transparent! gl-inset-border-1-gray-100!">
|
||||||
|
<slot></slot>
|
||||||
|
</gl-badge>
|
||||||
|
</template>
|
|
@ -519,10 +519,6 @@ table.pipeline-project-metrics tr td {
|
||||||
margin-left: 25px;
|
margin-left: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-visibility {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-updated {
|
.last-updated {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-width: 250px;
|
min-width: 250px;
|
||||||
|
|
|
@ -9,6 +9,7 @@ class Groups::BoardsController < Groups::ApplicationController
|
||||||
before_action :assign_endpoint_vars
|
before_action :assign_endpoint_vars
|
||||||
before_action do
|
before_action do
|
||||||
push_frontend_feature_flag(:graphql_board_lists, group, default_enabled: false)
|
push_frontend_feature_flag(:graphql_board_lists, group, default_enabled: false)
|
||||||
|
push_frontend_feature_flag(:boards_filtered_search, group)
|
||||||
end
|
end
|
||||||
|
|
||||||
feature_category :boards
|
feature_category :boards
|
||||||
|
|
|
@ -2026,10 +2026,10 @@ class Project < ApplicationRecord
|
||||||
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
||||||
break variables unless Gitlab.config.dependency_proxy.enabled
|
break variables unless Gitlab.config.dependency_proxy.enabled
|
||||||
|
|
||||||
variables.append(key: 'CI_DEPENDENCY_PROXY_SERVER', value: "#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}")
|
variables.append(key: 'CI_DEPENDENCY_PROXY_SERVER', value: Gitlab.host_with_port)
|
||||||
variables.append(
|
variables.append(
|
||||||
key: 'CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX',
|
key: 'CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX',
|
||||||
value: "#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}/#{namespace.root_ancestor.path}#{DependencyProxy::URL_SUFFIX}"
|
value: "#{Gitlab.host_with_port}/#{namespace.root_ancestor.path}#{DependencyProxy::URL_SUFFIX}"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -48,4 +48,4 @@
|
||||||
.text-secondary= s_('DeployTokens|Allows write access to the package registry.')
|
.text-secondary= s_('DeployTokens|Allows write access to the package registry.')
|
||||||
|
|
||||||
.gl-mt-3
|
.gl-mt-3
|
||||||
= f.submit s_('DeployTokens|Create deploy token'), class: 'btn gl-button btn-success qa-create-deploy-token'
|
= f.submit s_('DeployTokens|Create deploy token'), class: 'btn gl-button btn-confirm qa-create-deploy-token'
|
||||||
|
|
|
@ -21,178 +21,181 @@
|
||||||
- if @can_bulk_update
|
- if @can_bulk_update
|
||||||
.check-all-holder.d-none.d-sm-block.hidden
|
.check-all-holder.d-none.d-sm-block.hidden
|
||||||
= check_box_tag "check-all-issues", nil, false, class: "check-all-issues left"
|
= check_box_tag "check-all-issues", nil, false, class: "check-all-issues left"
|
||||||
.issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
|
- if Feature.enabled?(:boards_filtered_search, @group)
|
||||||
.filtered-search-box
|
#js-board-filtered-search
|
||||||
- if type != :boards_modal && type != :boards
|
- else
|
||||||
- text = tag.span(sprite_icon('history'), class: "d-md-none") + tag.span(_('Recent searches'), class: "d-none d-md-inline")
|
.issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
|
||||||
= dropdown_tag(text,
|
.filtered-search-box
|
||||||
options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
|
- if type != :boards_modal && type != :boards
|
||||||
toggle_class: "btn filtered-search-history-dropdown-toggle-button",
|
- text = tag.span(sprite_icon('history'), class: "d-md-none") + tag.span(_('Recent searches'), class: "d-none d-md-inline")
|
||||||
dropdown_class: "filtered-search-history-dropdown",
|
= dropdown_tag(text,
|
||||||
content_class: "filtered-search-history-dropdown-content" }) do
|
options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
|
||||||
.js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } }
|
toggle_class: "btn filtered-search-history-dropdown-toggle-button",
|
||||||
.filtered-search-box-input-container.droplab-dropdown
|
dropdown_class: "filtered-search-history-dropdown",
|
||||||
.scroll-container
|
content_class: "filtered-search-history-dropdown-content" }) do
|
||||||
%ul.tokens-container.list-unstyled
|
.js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } }
|
||||||
%li.input-token
|
.filtered-search-box-input-container.droplab-dropdown
|
||||||
%input.form-control.filtered-search{ search_filter_input_options(type, placeholder) }
|
.scroll-container
|
||||||
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
|
%ul.tokens-container.list-unstyled
|
||||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
%li.input-token
|
||||||
%li.filter-dropdown-item{ data: {hint: "#{'{{hint}}'}", tag: "#{'{{tag}}'}", action: "#{'{{hint === \'search\' ? \'submit\' : \'\' }}'}" } }
|
%input.form-control.filtered-search{ search_filter_input_options(type, placeholder) }
|
||||||
%button.btn.btn-link{ type: 'button' }
|
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
|
||||||
-# Encapsulate static class name `{{icon}}` inside #{} to bypass
|
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||||
-# haml lint's ClassAttributeWithStaticValue
|
%li.filter-dropdown-item{ data: {hint: "#{'{{hint}}'}", tag: "#{'{{tag}}'}", action: "#{'{{hint === \'search\' ? \'submit\' : \'\' }}'}" } }
|
||||||
%svg
|
%button.btn.btn-link{ type: 'button' }
|
||||||
%use{ 'xlink:href': "#{'{{icon}}'}" }
|
-# Encapsulate static class name `{{icon}}` inside #{} to bypass
|
||||||
%span.js-filter-hint
|
-# haml lint's ClassAttributeWithStaticValue
|
||||||
{{formattedKey}}
|
%svg
|
||||||
#js-dropdown-operator.filtered-search-input-dropdown-menu.dropdown-menu
|
%use{ 'xlink:href': "#{'{{icon}}'}" }
|
||||||
%ul.filter-dropdown{ data: { dropdown: true, dynamic: true } }
|
%span.js-filter-hint
|
||||||
%li.filter-dropdown-item{ data: { value: "{{ title }}" } }
|
{{formattedKey}}
|
||||||
%button.btn.btn-link{ type: 'button' }
|
#js-dropdown-operator.filtered-search-input-dropdown-menu.dropdown-menu
|
||||||
{{ title }}
|
%ul.filter-dropdown{ data: { dropdown: true, dynamic: true } }
|
||||||
%span.btn-helptext
|
%li.filter-dropdown-item{ data: { value: "{{ title }}" } }
|
||||||
{{ help }}
|
%button.btn.btn-link{ type: 'button' }
|
||||||
#js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu
|
{{ title }}
|
||||||
- if current_user
|
%span.btn-helptext
|
||||||
|
{{ help }}
|
||||||
|
#js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu
|
||||||
|
- if current_user
|
||||||
|
%ul{ data: { dropdown: true } }
|
||||||
|
= render 'shared/issuable/user_dropdown_item',
|
||||||
|
user: current_user
|
||||||
|
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||||
|
= render 'shared/issuable/user_dropdown_item',
|
||||||
|
user: User.new(username: '{{username}}', name: '{{name}}'),
|
||||||
|
avatar: { lazy: true, url: '{{avatar_url}}' }
|
||||||
|
#js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu
|
||||||
%ul{ data: { dropdown: true } }
|
%ul{ data: { dropdown: true } }
|
||||||
|
%li.filter-dropdown-item{ data: { value: 'None' } }
|
||||||
|
%button.btn.btn-link{ type: 'button' }
|
||||||
|
= _('None')
|
||||||
|
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
||||||
|
%button.btn.btn-link{ type: 'button' }
|
||||||
|
= _('Any')
|
||||||
|
%li.divider.droplab-item-ignore
|
||||||
|
- if current_user
|
||||||
|
= render 'shared/issuable/user_dropdown_item',
|
||||||
|
user: current_user
|
||||||
|
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||||
= render 'shared/issuable/user_dropdown_item',
|
= render 'shared/issuable/user_dropdown_item',
|
||||||
user: current_user
|
user: User.new(username: '{{username}}', name: '{{name}}'),
|
||||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
avatar: { lazy: true, url: '{{avatar_url}}' }
|
||||||
= render 'shared/issuable/user_dropdown_item',
|
#js-dropdown-reviewer.filtered-search-input-dropdown-menu.dropdown-menu
|
||||||
user: User.new(username: '{{username}}', name: '{{name}}'),
|
%ul{ data: { dropdown: true } }
|
||||||
avatar: { lazy: true, url: '{{avatar_url}}' }
|
%li.filter-dropdown-item{ data: { value: 'None' } }
|
||||||
#js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu
|
%button.btn.btn-link{ type: 'button' }
|
||||||
%ul{ data: { dropdown: true } }
|
= _('None')
|
||||||
%li.filter-dropdown-item{ data: { value: 'None' } }
|
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
||||||
%button.btn.btn-link{ type: 'button' }
|
%button.btn.btn-link{ type: 'button' }
|
||||||
= _('None')
|
= _('Any')
|
||||||
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
%li.divider.droplab-item-ignore
|
||||||
%button.btn.btn-link{ type: 'button' }
|
- if current_user
|
||||||
= _('Any')
|
= render 'shared/issuable/user_dropdown_item',
|
||||||
%li.divider.droplab-item-ignore
|
user: current_user
|
||||||
- if current_user
|
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||||
= render 'shared/issuable/user_dropdown_item',
|
= render 'shared/issuable/user_dropdown_item',
|
||||||
user: current_user
|
user: User.new(username: '{{username}}', name: '{{name}}'),
|
||||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
avatar: { lazy: true, url: '{{avatar_url}}' }
|
||||||
= render 'shared/issuable/user_dropdown_item',
|
= render_if_exists 'shared/issuable/approver_dropdown'
|
||||||
user: User.new(username: '{{username}}', name: '{{name}}'),
|
= render_if_exists 'shared/issuable/approved_by_dropdown'
|
||||||
avatar: { lazy: true, url: '{{avatar_url}}' }
|
#js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
|
||||||
#js-dropdown-reviewer.filtered-search-input-dropdown-menu.dropdown-menu
|
%ul{ data: { dropdown: true } }
|
||||||
%ul{ data: { dropdown: true } }
|
%li.filter-dropdown-item{ data: { value: 'None' } }
|
||||||
%li.filter-dropdown-item{ data: { value: 'None' } }
|
%button.btn.btn-link{ type: 'button' }
|
||||||
%button.btn.btn-link{ type: 'button' }
|
= _('None')
|
||||||
= _('None')
|
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
||||||
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
%button.btn.btn-link{ type: 'button' }
|
||||||
%button.btn.btn-link{ type: 'button' }
|
= _('Any')
|
||||||
= _('Any')
|
%li.filter-dropdown-item{ data: { value: 'Upcoming' } }
|
||||||
%li.divider.droplab-item-ignore
|
%button.btn.btn-link{ type: 'button' }
|
||||||
- if current_user
|
= _('Upcoming')
|
||||||
= render 'shared/issuable/user_dropdown_item',
|
%li.filter-dropdown-item{ data: { value: 'Started' } }
|
||||||
user: current_user
|
%button.btn.btn-link{ type: 'button' }
|
||||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
= _('Started')
|
||||||
= render 'shared/issuable/user_dropdown_item',
|
%li.divider.droplab-item-ignore
|
||||||
user: User.new(username: '{{username}}', name: '{{name}}'),
|
|
||||||
avatar: { lazy: true, url: '{{avatar_url}}' }
|
|
||||||
= render_if_exists 'shared/issuable/approver_dropdown'
|
|
||||||
= render_if_exists 'shared/issuable/approved_by_dropdown'
|
|
||||||
#js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
|
|
||||||
%ul{ data: { dropdown: true } }
|
|
||||||
%li.filter-dropdown-item{ data: { value: 'None' } }
|
|
||||||
%button.btn.btn-link{ type: 'button' }
|
|
||||||
= _('None')
|
|
||||||
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
|
||||||
%button.btn.btn-link{ type: 'button' }
|
|
||||||
= _('Any')
|
|
||||||
%li.filter-dropdown-item{ data: { value: 'Upcoming' } }
|
|
||||||
%button.btn.btn-link{ type: 'button' }
|
|
||||||
= _('Upcoming')
|
|
||||||
%li.filter-dropdown-item{ data: { value: 'Started' } }
|
|
||||||
%button.btn.btn-link{ type: 'button' }
|
|
||||||
= _('Started')
|
|
||||||
%li.divider.droplab-item-ignore
|
|
||||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
|
||||||
%li.filter-dropdown-item
|
|
||||||
%button.btn.btn-link.js-data-value{ type: 'button' }
|
|
||||||
{{title}}
|
|
||||||
= render_if_exists 'shared/issuable/filter_iteration', type: type
|
|
||||||
#js-dropdown-release.filtered-search-input-dropdown-menu.dropdown-menu
|
|
||||||
%ul{ data: { dropdown: true } }
|
|
||||||
%li.filter-dropdown-item{ data: { value: 'None' } }
|
|
||||||
%button.btn.btn-link{ type: 'button' }
|
|
||||||
= _('None')
|
|
||||||
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
|
||||||
%button.btn.btn-link{ type: 'button' }
|
|
||||||
= _('Any')
|
|
||||||
%li.divider.droplab-item-ignore
|
|
||||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
|
||||||
%li.filter-dropdown-item
|
|
||||||
%button.btn.btn-link.js-data-value{ type: 'button' }
|
|
||||||
{{title}}
|
|
||||||
#js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu
|
|
||||||
%ul{ data: { dropdown: true } }
|
|
||||||
%li.filter-dropdown-item{ data: { value: 'None' } }
|
|
||||||
%button.btn.btn-link{ type: 'button' }
|
|
||||||
= _('None')
|
|
||||||
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
|
||||||
%button.btn.btn-link{ type: 'button' }
|
|
||||||
= _('Any')
|
|
||||||
%li.divider.droplab-item-ignore
|
|
||||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
|
||||||
%li.filter-dropdown-item
|
|
||||||
%button.btn.btn-link{ type: 'button' }
|
|
||||||
%span.dropdown-label-box{ style: 'background: {{color}}' }
|
|
||||||
%span.label-title.js-data-value
|
|
||||||
{{title}}
|
|
||||||
#js-dropdown-my-reaction.filtered-search-input-dropdown-menu.dropdown-menu
|
|
||||||
%ul{ data: { dropdown: true } }
|
|
||||||
%li.filter-dropdown-item{ data: { value: 'None' } }
|
|
||||||
%button.btn.btn-link{ type: 'button' }
|
|
||||||
= _('None')
|
|
||||||
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
|
||||||
%button.btn.btn-link{ type: 'button' }
|
|
||||||
= _('Any')
|
|
||||||
%li.divider.droplab-item-ignore
|
|
||||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
|
||||||
%li.filter-dropdown-item
|
|
||||||
%button.btn.btn-link{ type: 'button' }
|
|
||||||
%gl-emoji
|
|
||||||
%span.js-data-value.gl-ml-3
|
|
||||||
{{name}}
|
|
||||||
#js-dropdown-wip.filtered-search-input-dropdown-menu.dropdown-menu
|
|
||||||
%ul.filter-dropdown{ data: { dropdown: true } }
|
|
||||||
%li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
|
|
||||||
%button.btn.btn-link{ type: 'button' }
|
|
||||||
= _('Yes')
|
|
||||||
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
|
|
||||||
%button.btn.btn-link{ type: 'button' }
|
|
||||||
= _('No')
|
|
||||||
#js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu
|
|
||||||
%ul.filter-dropdown{ data: { dropdown: true } }
|
|
||||||
%li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
|
|
||||||
%button.btn.btn-link{ type: 'button' }
|
|
||||||
= _('Yes')
|
|
||||||
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
|
|
||||||
%button.btn.btn-link{ type: 'button' }
|
|
||||||
= _('No')
|
|
||||||
- unless disable_target_branch
|
|
||||||
#js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu
|
|
||||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||||
%li.filter-dropdown-item
|
%li.filter-dropdown-item
|
||||||
%button.btn.btn-link.js-data-value.monospace
|
%button.btn.btn-link.js-data-value{ type: 'button' }
|
||||||
|
{{title}}
|
||||||
|
= render_if_exists 'shared/issuable/filter_iteration', type: type
|
||||||
|
#js-dropdown-release.filtered-search-input-dropdown-menu.dropdown-menu
|
||||||
|
%ul{ data: { dropdown: true } }
|
||||||
|
%li.filter-dropdown-item{ data: { value: 'None' } }
|
||||||
|
%button.btn.btn-link{ type: 'button' }
|
||||||
|
= _('None')
|
||||||
|
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
||||||
|
%button.btn.btn-link{ type: 'button' }
|
||||||
|
= _('Any')
|
||||||
|
%li.divider.droplab-item-ignore
|
||||||
|
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||||
|
%li.filter-dropdown-item
|
||||||
|
%button.btn.btn-link.js-data-value{ type: 'button' }
|
||||||
|
{{title}}
|
||||||
|
#js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu
|
||||||
|
%ul{ data: { dropdown: true } }
|
||||||
|
%li.filter-dropdown-item{ data: { value: 'None' } }
|
||||||
|
%button.btn.btn-link{ type: 'button' }
|
||||||
|
= _('None')
|
||||||
|
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
||||||
|
%button.btn.btn-link{ type: 'button' }
|
||||||
|
= _('Any')
|
||||||
|
%li.divider.droplab-item-ignore
|
||||||
|
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||||
|
%li.filter-dropdown-item
|
||||||
|
%button.btn.btn-link{ type: 'button' }
|
||||||
|
%span.dropdown-label-box{ style: 'background: {{color}}' }
|
||||||
|
%span.label-title.js-data-value
|
||||||
|
{{title}}
|
||||||
|
#js-dropdown-my-reaction.filtered-search-input-dropdown-menu.dropdown-menu
|
||||||
|
%ul{ data: { dropdown: true } }
|
||||||
|
%li.filter-dropdown-item{ data: { value: 'None' } }
|
||||||
|
%button.btn.btn-link{ type: 'button' }
|
||||||
|
= _('None')
|
||||||
|
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
||||||
|
%button.btn.btn-link{ type: 'button' }
|
||||||
|
= _('Any')
|
||||||
|
%li.divider.droplab-item-ignore
|
||||||
|
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||||
|
%li.filter-dropdown-item
|
||||||
|
%button.btn.btn-link{ type: 'button' }
|
||||||
|
%gl-emoji
|
||||||
|
%span.js-data-value.gl-ml-3
|
||||||
|
{{name}}
|
||||||
|
#js-dropdown-wip.filtered-search-input-dropdown-menu.dropdown-menu
|
||||||
|
%ul.filter-dropdown{ data: { dropdown: true } }
|
||||||
|
%li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
|
||||||
|
%button.btn.btn-link{ type: 'button' }
|
||||||
|
= _('Yes')
|
||||||
|
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
|
||||||
|
%button.btn.btn-link{ type: 'button' }
|
||||||
|
= _('No')
|
||||||
|
#js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu
|
||||||
|
%ul.filter-dropdown{ data: { dropdown: true } }
|
||||||
|
%li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
|
||||||
|
%button.btn.btn-link{ type: 'button' }
|
||||||
|
= _('Yes')
|
||||||
|
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
|
||||||
|
%button.btn.btn-link{ type: 'button' }
|
||||||
|
= _('No')
|
||||||
|
- unless disable_target_branch
|
||||||
|
#js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu
|
||||||
|
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||||
|
%li.filter-dropdown-item
|
||||||
|
%button.btn.btn-link.js-data-value.monospace
|
||||||
|
{{title}}
|
||||||
|
#js-dropdown-environment.filtered-search-input-dropdown-menu.dropdown-menu
|
||||||
|
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||||
|
%li.filter-dropdown-item
|
||||||
|
%button.btn.btn-link.js-data-value{ type: 'button' }
|
||||||
{{title}}
|
{{title}}
|
||||||
#js-dropdown-environment.filtered-search-input-dropdown-menu.dropdown-menu
|
|
||||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
|
||||||
%li.filter-dropdown-item
|
|
||||||
%button.btn.btn-link.js-data-value{ type: 'button' }
|
|
||||||
{{title}}
|
|
||||||
|
|
||||||
= render_if_exists 'shared/issuable/filter_weight', type: type
|
= render_if_exists 'shared/issuable/filter_weight', type: type
|
||||||
|
|
||||||
= render_if_exists 'shared/issuable/filter_epic', type: type
|
= render_if_exists 'shared/issuable/filter_epic', type: type
|
||||||
|
|
||||||
%button.clear-search.hidden{ type: 'button' }
|
%button.clear-search.hidden{ type: 'button' }
|
||||||
= sprite_icon('close', size: 16, css_class: 'clear-search-icon')
|
= sprite_icon('close', size: 16, css_class: 'clear-search-icon')
|
||||||
.filter-dropdown-container.d-flex.flex-column.flex-md-row
|
.filter-dropdown-container.d-flex.flex-column.flex-md-row
|
||||||
- if type == :boards
|
- if type == :boards
|
||||||
#js-board-labels-toggle
|
#js-board-labels-toggle
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Move from btn-success to btn-confirm in shared/deploy_tokens directory
|
||||||
|
merge_request: 55300
|
||||||
|
author: Yogi (@yo)
|
||||||
|
type: changed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Turn off native autocomplete for ref selector components
|
||||||
|
merge_request: 56128
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Improve styling of user access role badges
|
||||||
|
merge_request: 56061
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -15,8 +15,10 @@
|
||||||
- audit_reports
|
- audit_reports
|
||||||
- authentication_and_authorization
|
- authentication_and_authorization
|
||||||
- auto_devops
|
- auto_devops
|
||||||
|
- auto_portfolio_mgmt
|
||||||
- backup_restore
|
- backup_restore
|
||||||
- boards
|
- boards
|
||||||
|
- browser_performance
|
||||||
- chatops
|
- chatops
|
||||||
- cloud_native_installation
|
- cloud_native_installation
|
||||||
- cluster_cost_management
|
- cluster_cost_management
|
||||||
|
@ -32,17 +34,18 @@
|
||||||
- continuous_delivery
|
- continuous_delivery
|
||||||
- continuous_integration
|
- continuous_integration
|
||||||
- database
|
- database
|
||||||
|
- dataops
|
||||||
- dependency_firewall
|
- dependency_firewall
|
||||||
- dependency_proxy
|
- dependency_proxy
|
||||||
- dependency_scanning
|
- dependency_scanning
|
||||||
- design_management
|
- design_management
|
||||||
- devops_reports
|
- devops_reports
|
||||||
- disaster_recovery
|
- disaster_recovery
|
||||||
- dora_metrics
|
|
||||||
- dynamic_application_security_testing
|
- dynamic_application_security_testing
|
||||||
- editor_extension
|
- editor_extension
|
||||||
- epics
|
- epics
|
||||||
- error_tracking
|
- error_tracking
|
||||||
|
- experimentation
|
||||||
- feature_flags
|
- feature_flags
|
||||||
- five_minute_production_app
|
- five_minute_production_app
|
||||||
- foundations
|
- foundations
|
||||||
|
@ -61,6 +64,7 @@
|
||||||
- insider_threat
|
- insider_threat
|
||||||
- insights
|
- insights
|
||||||
- integrations
|
- integrations
|
||||||
|
- intel_code_security
|
||||||
- interactive_application_security_testing
|
- interactive_application_security_testing
|
||||||
- internationalization
|
- internationalization
|
||||||
- issue_tracking
|
- issue_tracking
|
||||||
|
@ -79,6 +83,7 @@
|
||||||
- mobile_signing_deployment
|
- mobile_signing_deployment
|
||||||
- navigation
|
- navigation
|
||||||
- omnibus_package
|
- omnibus_package
|
||||||
|
- onboarding
|
||||||
- package_registry
|
- package_registry
|
||||||
- pages
|
- pages
|
||||||
- pipeline_authoring
|
- pipeline_authoring
|
||||||
|
@ -120,5 +125,4 @@
|
||||||
- vulnerability_management
|
- vulnerability_management
|
||||||
- web_firewall
|
- web_firewall
|
||||||
- web_ide
|
- web_ide
|
||||||
- web_performance
|
|
||||||
- wiki
|
- wiki
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
name: saas_add_seats_button
|
name: boards_filtered_search
|
||||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49242
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54641
|
||||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/291060
|
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/322778
|
||||||
milestone: '13.7'
|
milestone: '13.10'
|
||||||
type: development
|
type: development
|
||||||
group: group::purchase
|
group: group::project management
|
||||||
default_enabled: false
|
default_enabled: false
|
|
@ -6,7 +6,7 @@
|
||||||
# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
|
# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
|
||||||
extends: substitution
|
extends: substitution
|
||||||
message: 'Verify this use of the word "admin". Can it be updated to "administration", "administrator", "administer", or "Admin Area"?'
|
message: 'Verify this use of the word "admin". Can it be updated to "administration", "administrator", "administer", or "Admin Area"?'
|
||||||
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html
|
link: https://docs.gitlab.com/ee/development/documentation/styleguide/index.html
|
||||||
level: suggestion
|
level: suggestion
|
||||||
ignorecase: true
|
ignorecase: true
|
||||||
swap:
|
swap:
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
|
# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
|
||||||
extends: existence
|
extends: existence
|
||||||
message: 'Alert box "%s" must use the formatting in the style guide.'
|
message: 'Alert box "%s" must use the formatting in the style guide.'
|
||||||
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#alert-boxes
|
link: https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#alert-boxes
|
||||||
level: error
|
level: error
|
||||||
nonword: true
|
nonword: true
|
||||||
scope: raw
|
scope: raw
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
# Error: gitlab.RelativeLinksDoubleSlashes
|
||||||
|
#
|
||||||
|
# Checks for the presence of double slashes in relative URLs.
|
||||||
|
#
|
||||||
|
# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
|
||||||
|
extends: existence
|
||||||
|
message: 'Relative links must not include a double slash.'
|
||||||
|
link: https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#links-to-internal-documentation
|
||||||
|
level: error
|
||||||
|
scope: raw
|
||||||
|
raw:
|
||||||
|
- '\.//'
|
|
@ -239,7 +239,7 @@ control over how the Pages daemon runs and serves content in your environment.
|
||||||
| `gitlab_secret` | The OAuth application secret. Leave blank to automatically fill when Pages authenticates with GitLab. |
|
| `gitlab_secret` | The OAuth application secret. Leave blank to automatically fill when Pages authenticates with GitLab. |
|
||||||
| `auth_scope` | The OAuth application scope to use for authentication. Must match GitLab Pages OAuth application settings. Leave blank to use `api` scope by default. |
|
| `auth_scope` | The OAuth application scope to use for authentication. Must match GitLab Pages OAuth application settings. Leave blank to use `api` scope by default. |
|
||||||
| `gitlab_server` | Server to use for authentication when access control is enabled; defaults to GitLab `external_url`. |
|
| `gitlab_server` | Server to use for authentication when access control is enabled; defaults to GitLab `external_url`. |
|
||||||
| `headers` | Specify any additional http headers that should be sent to the client with each response. |
|
| `headers` | Specify any additional http headers that should be sent to the client with each response. Multiple headers can be given as an array, header and value as one string, for example `['my-header: myvalue', 'my-other-header: my-other-value']` |
|
||||||
| `inplace_chroot` | On [systems that don't support bind-mounts](index.md#additional-configuration-for-docker-container), this instructs GitLab Pages to `chroot` into its `pages_path` directory. Some caveats exist when using in-place `chroot`; refer to the GitLab Pages [README](https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md#caveats) for more information. |
|
| `inplace_chroot` | On [systems that don't support bind-mounts](index.md#additional-configuration-for-docker-container), this instructs GitLab Pages to `chroot` into its `pages_path` directory. Some caveats exist when using in-place `chroot`; refer to the GitLab Pages [README](https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md#caveats) for more information. |
|
||||||
| `insecure_ciphers` | Use default list of cipher suites, may contain insecure ones like 3DES and RC4. |
|
| `insecure_ciphers` | Use default list of cipher suites, may contain insecure ones like 3DES and RC4. |
|
||||||
| `internal_gitlab_server` | Internal GitLab server address used exclusively for API requests. Useful if you want to send that traffic over an internal load balancer. Defaults to GitLab `external_url`. |
|
| `internal_gitlab_server` | Internal GitLab server address used exclusively for API requests. Useful if you want to send that traffic over an internal load balancer. Defaults to GitLab `external_url`. |
|
||||||
|
|
|
@ -36,7 +36,7 @@ The type of problem will determine what steps to take. The possible troubleshoot
|
||||||
- Indexing.
|
- Indexing.
|
||||||
- Integration.
|
- Integration.
|
||||||
- Performance.
|
- Performance.
|
||||||
- Background Migrations.
|
- Advanced Search Migrations.
|
||||||
|
|
||||||
### Search Results workflow
|
### Search Results workflow
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ graph TD;
|
||||||
F7(Escalate to<br>GitLab support.)
|
F7(Escalate to<br>GitLab support.)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Background Migrations workflow
|
### Advanced Search Migrations workflow
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD;
|
graph TD;
|
||||||
|
@ -180,7 +180,7 @@ Most Elasticsearch troubleshooting can be broken down into 4 categories:
|
||||||
- [Troubleshooting indexing](#troubleshooting-indexing)
|
- [Troubleshooting indexing](#troubleshooting-indexing)
|
||||||
- [Troubleshooting integration](#troubleshooting-integration)
|
- [Troubleshooting integration](#troubleshooting-integration)
|
||||||
- [Troubleshooting performance](#troubleshooting-performance)
|
- [Troubleshooting performance](#troubleshooting-performance)
|
||||||
- [Troubleshooting background migrations](#troubleshooting-background-migrations)
|
- [Troubleshooting Advanced Search migrations](#troubleshooting-advanced-search-migrations)
|
||||||
|
|
||||||
Generally speaking, if it does not fall into those four categories, it is either:
|
Generally speaking, if it does not fall into those four categories, it is either:
|
||||||
|
|
||||||
|
@ -356,15 +356,16 @@ dig further into these.
|
||||||
Feel free to reach out to GitLab support, but this is likely to be something a skilled
|
Feel free to reach out to GitLab support, but this is likely to be something a skilled
|
||||||
Elasticsearch administrator has more experience with.
|
Elasticsearch administrator has more experience with.
|
||||||
|
|
||||||
### Troubleshooting background migrations
|
### Troubleshooting Advanced Search migrations
|
||||||
|
|
||||||
Troubleshooting background migration failures can be difficult and may require contacting
|
Troubleshooting Advanced Search migration failures can be difficult and may
|
||||||
an Elasticsearch administrator or GitLab Support.
|
require contacting an Elasticsearch administrator or GitLab Support.
|
||||||
|
|
||||||
The best place to start while debugging issues with a background migration is the
|
The best place to start while debugging issues with an Advanced Search
|
||||||
[`elasticsearch.log` file](../logs.md#elasticsearchlog). Migrations will
|
migration is the [`elasticsearch.log` file](../logs.md#elasticsearchlog).
|
||||||
print information while a migration is in progress and any errors encountered.
|
Migrations will log information while a migration is in progress and any
|
||||||
Apply fixes for any errors found in the log and retry the migration.
|
errors encountered. Apply fixes for any errors found in the log and retry
|
||||||
|
the migration.
|
||||||
|
|
||||||
If you still encounter issues after retrying the migration, reach out to GitLab support.
|
If you still encounter issues after retrying the migration, reach out to GitLab support.
|
||||||
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: 'styleguide/index.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](styleguide/index.md).
|
|
||||||
|
|
||||||
<!-- This redirect file can be deleted after <2022-02-13>. -->
|
|
||||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
|
|
|
@ -249,12 +249,12 @@ end
|
||||||
|
|
||||||
### Multi-version compatibility
|
### Multi-version compatibility
|
||||||
|
|
||||||
These Elasticsearch migrations, like any other GitLab changes, need to support the case where
|
These Advanced Search migrations, like any other GitLab changes, need to support the case where
|
||||||
[multiple versions of the application are running at the same time](multi_version_compatibility.md).
|
[multiple versions of the application are running at the same time](multi_version_compatibility.md).
|
||||||
|
|
||||||
Depending on the order of deployment, it's possible that the migration
|
Depending on the order of deployment, it's possible that the migration
|
||||||
has started or finished and there's still a server running the application code from before the
|
has started or finished and there's still a server running the application code from before the
|
||||||
migration. We need to take this into consideration until we can [ensure all Elasticsearch migrations
|
migration. We need to take this into consideration until we can [ensure all Advanced Search migrations
|
||||||
start after the deployment has finished](https://gitlab.com/gitlab-org/gitlab/-/issues/321619).
|
start after the deployment has finished](https://gitlab.com/gitlab-org/gitlab/-/issues/321619).
|
||||||
|
|
||||||
### Reverting a migration
|
### Reverting a migration
|
||||||
|
@ -268,7 +268,7 @@ some data is moved) to a later merge request after the migrations have
|
||||||
completed successfully. To be safe, for self-managed customers we should also
|
completed successfully. To be safe, for self-managed customers we should also
|
||||||
defer it to another release if there is risk of important data loss.
|
defer it to another release if there is risk of important data loss.
|
||||||
|
|
||||||
### Best practices for Elasticsearch migrations
|
### Best practices for Advanced Search migrations
|
||||||
|
|
||||||
Follow these best practices for best results:
|
Follow these best practices for best results:
|
||||||
|
|
||||||
|
@ -286,6 +286,63 @@ Follow these best practices for best results:
|
||||||
- Consider adding a retry limit if there is potential for the migration to fail.
|
- Consider adding a retry limit if there is potential for the migration to fail.
|
||||||
This ensures that migrations can be halted if an issue occurs.
|
This ensures that migrations can be halted if an issue occurs.
|
||||||
|
|
||||||
|
## Deleting Advanced Search migrations in a major version upgrade
|
||||||
|
|
||||||
|
Since our Advanced Search migrations usually require us to support multiple
|
||||||
|
code paths for a long period of time, it's important to clean those up when we
|
||||||
|
safely can.
|
||||||
|
|
||||||
|
We choose to use GitLab major version upgrades as a safe time to remove
|
||||||
|
backwards compatibility for indices that have not been fully migrated. We
|
||||||
|
[document this in our upgrade
|
||||||
|
documentation](../update/index.md#upgrading-to-a-new-major-version). We also
|
||||||
|
choose to remove the migration code and tests so that:
|
||||||
|
|
||||||
|
- We don't need to maintain any code that is called from our Advanced Search
|
||||||
|
migrations.
|
||||||
|
- We don't waste CI time running tests for migrations that we don't support
|
||||||
|
anymore.
|
||||||
|
|
||||||
|
To be extra safe, we will not delete migrations that were created in the last
|
||||||
|
minor version before the major upgrade. So, if the we are upgrading to `%14.0`,
|
||||||
|
we should not delete migrations that were only added in `%13.11`. This is an
|
||||||
|
extra safety net as we expect there are migrations that get merged that may
|
||||||
|
take multiple weeks to finish on GitLab.com. It would be bad if we upgraded
|
||||||
|
GitLab.com to `%14.0` before the migrations in `%13.11` were finished. Since
|
||||||
|
our deployments to GitLab.com are automated and we currently don't have
|
||||||
|
automated checks to prevent this, the extra precaution is warranted.
|
||||||
|
Additionally, even if we did have automated checks to prevent it, we wouldn't
|
||||||
|
actually want to hold up GitLab.com deployments on Advanced Search migrations,
|
||||||
|
as they may still have another week to go, and that's too long to block
|
||||||
|
deployments.
|
||||||
|
|
||||||
|
### Process for removing migrations
|
||||||
|
|
||||||
|
For every migration that was created 2 minor versions before the major version
|
||||||
|
being upgraded to, we do the following:
|
||||||
|
|
||||||
|
1. Confirm the migration has actually completed successfully for GitLab.com.
|
||||||
|
1. Replace the content of `migrate` and `completed?` methods as follows:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
def migrate
|
||||||
|
log_raise "Migration has been deleted in the last major version upgrade." \
|
||||||
|
"Migrations are supposed to be finished before upgrading major version https://docs.gitlab.com/ee/update/#upgrading-to-a-new-major-version ." \
|
||||||
|
"In order to correct this issue you will need to reacreate your index from scratch https://docs.gitlab.com/ee/integration/elasticsearch.html#last-resort-to-recreate-an-index ."
|
||||||
|
end
|
||||||
|
|
||||||
|
def completed?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Delete any spec files to support this migration.
|
||||||
|
1. Remove any logic handling backwards compatibility for this migration. You
|
||||||
|
can find this by looking for
|
||||||
|
`Elastic::DataMigrationService.migration_has_finished?(:migration_name_in_lowercase)`.
|
||||||
|
1. Create a merge request with these changes. Noting that we should not
|
||||||
|
accidentally merge this before the major release is started.
|
||||||
|
|
||||||
## Performance Monitoring
|
## Performance Monitoring
|
||||||
|
|
||||||
### Prometheus
|
### Prometheus
|
||||||
|
|
|
@ -222,7 +222,7 @@ The following Elasticsearch settings are available:
|
||||||
| `Pause Elasticsearch indexing` | Enables or disables temporary indexing pause. This is useful for cluster migration/reindexing. All changes are still tracked, but they are not committed to the Elasticsearch index until resumed. |
|
| `Pause Elasticsearch indexing` | Enables or disables temporary indexing pause. This is useful for cluster migration/reindexing. All changes are still tracked, but they are not committed to the Elasticsearch index until resumed. |
|
||||||
| `Search with Elasticsearch enabled` | Enables or disables using Elasticsearch in search. |
|
| `Search with Elasticsearch enabled` | Enables or disables using Elasticsearch in search. |
|
||||||
| `URL` | The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., `http://host1, https://host2:9200`). If your Elasticsearch instance is password protected, pass the `username:password` in the URL (e.g., `http://<username>:<password>@<elastic_host>:9200/`). Special characters in the username or password should use [percentage encoding](https://en.wikipedia.org/wiki/Percent-encoding). |
|
| `URL` | The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., `http://host1, https://host2:9200`). If your Elasticsearch instance is password protected, pass the `username:password` in the URL (e.g., `http://<username>:<password>@<elastic_host>:9200/`). Special characters in the username or password should use [percentage encoding](https://en.wikipedia.org/wiki/Percent-encoding). |
|
||||||
| `Number of Elasticsearch shards` | Elasticsearch indexes are split into multiple shards for performance reasons. In general, larger indexes need to have more shards. Changes to this value do not take effect until the index is recreated. You can read more about tradeoffs in the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/scalability.html). |
|
| `Number of Elasticsearch shards` | Elasticsearch indexes are split into multiple shards for performance reasons. In general, you should use at least 5 shards, and indexes with tens of millions of documents need to have more shards ([see below](#guidance-on-choosing-optimal-cluster-configuration)). Changes to this value do not take effect until the index is recreated. You can read more about tradeoffs in the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/scalability.html). |
|
||||||
| `Number of Elasticsearch replicas` | Each Elasticsearch shard can have a number of replicas. These are a complete copy of the shard, and can provide increased query performance or resilience against hardware failure. Increasing this value will greatly increase total disk space required by the index. |
|
| `Number of Elasticsearch replicas` | Each Elasticsearch shard can have a number of replicas. These are a complete copy of the shard, and can provide increased query performance or resilience against hardware failure. Increasing this value will greatly increase total disk space required by the index. |
|
||||||
| `Limit namespaces and projects that can be indexed` | Enabling this will allow you to select namespaces and projects to index. All other namespaces and projects will use database search instead. Please note that if you enable this option but do not select any namespaces or projects, none will be indexed. [Read more below](#limiting-namespaces-and-projects).
|
| `Limit namespaces and projects that can be indexed` | Enabling this will allow you to select namespaces and projects to index. All other namespaces and projects will use database search instead. Please note that if you enable this option but do not select any namespaces or projects, none will be indexed. [Read more below](#limiting-namespaces-and-projects).
|
||||||
| `Using AWS hosted Elasticsearch with IAM credentials` | Sign your Elasticsearch requests using [AWS IAM authorization](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html), [AWS EC2 Instance Profile Credentials](https://docs.aws.amazon.com/codedeploy/latest/userguide/getting-started-create-iam-instance-profile.html#getting-started-create-iam-instance-profile-cli), or [AWS ECS Tasks Credentials](https://docs.aws.amazon.com/AmazonECS/latest/userguide/task-iam-roles.html). Please refer to [Identity and Access Management in Amazon Elasticsearch Service](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-ac.html) for details of AWS hosted Elasticsearch domain access policy configuration. |
|
| `Using AWS hosted Elasticsearch with IAM credentials` | Sign your Elasticsearch requests using [AWS IAM authorization](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html), [AWS EC2 Instance Profile Credentials](https://docs.aws.amazon.com/codedeploy/latest/userguide/getting-started-create-iam-instance-profile.html#getting-started-create-iam-instance-profile-cli), or [AWS ECS Tasks Credentials](https://docs.aws.amazon.com/AmazonECS/latest/userguide/task-iam-roles.html). Please refer to [Identity and Access Management in Amazon Elasticsearch Service](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-ac.html) for details of AWS hosted Elasticsearch domain access policy configuration. |
|
||||||
|
@ -344,7 +344,7 @@ Sometimes, you might want to abandon the unfinished reindex job and resume the i
|
||||||
|
|
||||||
1. Uncheck the "Pause Elasticsearch indexing" checkbox in **Admin Area > Settings > Advanced Search**.
|
1. Uncheck the "Pause Elasticsearch indexing" checkbox in **Admin Area > Settings > Advanced Search**.
|
||||||
|
|
||||||
## Background migrations
|
## Advanced Search migrations
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/234046) in GitLab 13.6.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/234046) in GitLab 13.6.
|
||||||
|
|
||||||
|
@ -352,7 +352,7 @@ 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
|
intervention. This usually happens in situations where new features are added to
|
||||||
Advanced Search, which means adding or changing the way content is indexed.
|
Advanced Search, which means adding or changing the way content is indexed.
|
||||||
|
|
||||||
To confirm that the background migrations ran, you can check with:
|
To confirm that the Advanced Search migrations ran, you can check with:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
curl "$CLUSTER_URL/gitlab-production-migrations/_search?q=*" | jq .
|
curl "$CLUSTER_URL/gitlab-production-migrations/_search?q=*" | jq .
|
||||||
|
@ -402,6 +402,21 @@ debug why the migration was halted and make any changes before retrying the migr
|
||||||
fixed the cause of the failure, click "Retry migration", and the migration will be scheduled to be retried
|
fixed the cause of the failure, click "Retry migration", and the migration will be scheduled to be retried
|
||||||
in the background.
|
in the background.
|
||||||
|
|
||||||
|
If you cannot get the migration to succeed, you may
|
||||||
|
consider the [last resort to recreate the index from
|
||||||
|
scratch](#last-resort-to-recreate-an-index). This may allow you to skip over
|
||||||
|
the problem because a newly created index will skip all migrations as the index
|
||||||
|
will be recreated with the correct up-to-date schema.
|
||||||
|
|
||||||
|
### All migrations must be finished before doing a major upgrade
|
||||||
|
|
||||||
|
Before doing a major version GitLab upgrade, you should have completed all
|
||||||
|
migrations that exist up until the latest minor version before that major
|
||||||
|
version. If you have halted migrations, these will need to be resolved and
|
||||||
|
[retried](#retry-a-halted-migration) before proceeding with a major version
|
||||||
|
upgrade. Read more about [upgrading to a new major
|
||||||
|
version](../update/index.md#upgrading-to-a-new-major-version).
|
||||||
|
|
||||||
## GitLab Advanced Search Rake tasks
|
## GitLab Advanced Search Rake tasks
|
||||||
|
|
||||||
Rake tasks are available to:
|
Rake tasks are available to:
|
||||||
|
@ -476,7 +491,12 @@ For basic guidance on choosing a cluster configuration you may refer to [Elastic
|
||||||
- `Heap size` should be set to no more than 50% of your physical RAM. Additionally, it shouldn't be set to more than the threshold for zero-based compressed oops. The exact threshold varies, but 26 GB is safe on most systems, but can also be as large as 30 GB on some systems. See [Heap size settings](https://www.elastic.co/guide/en/elasticsearch/reference/current/important-settings.html#heap-size-settings) and [Setting JVM options](https://www.elastic.co/guide/en/elasticsearch/reference/current/jvm-options.html) for more details.
|
- `Heap size` should be set to no more than 50% of your physical RAM. Additionally, it shouldn't be set to more than the threshold for zero-based compressed oops. The exact threshold varies, but 26 GB is safe on most systems, but can also be as large as 30 GB on some systems. See [Heap size settings](https://www.elastic.co/guide/en/elasticsearch/reference/current/important-settings.html#heap-size-settings) and [Setting JVM options](https://www.elastic.co/guide/en/elasticsearch/reference/current/jvm-options.html) for more details.
|
||||||
- Number of CPUs (CPU cores) per node usually corresponds to the `Number of Elasticsearch shards` setting described below.
|
- Number of CPUs (CPU cores) per node usually corresponds to the `Number of Elasticsearch shards` setting described below.
|
||||||
- A good guideline is to ensure you keep the number of shards per node below 20 per GB heap it has configured. A node with a 30GB heap should therefore have a maximum of 600 shards, but the further below this limit you can keep it the better. This will generally help the cluster stay in good health.
|
- A good guideline is to ensure you keep the number of shards per node below 20 per GB heap it has configured. A node with a 30GB heap should therefore have a maximum of 600 shards, but the further below this limit you can keep it the better. This will generally help the cluster stay in good health.
|
||||||
- Small shards result in small segments, which increases overhead. Aim to keep the average shard size between at least a few GB and a few tens of GB. Another consideration is the number of documents, you should aim for this simple formula for the number of shards: `number of expected documents / 5M +1`.
|
- Number of Elasticsearch shards:
|
||||||
|
- Small shards result in small segments, which increases overhead. Aim to keep the average shard size between at least a few GB and a few tens of GB.
|
||||||
|
- Another consideration is the number of documents. To determine the number of shards to use, sum the numbers in the **Admin Area > Dashboard > Statistics** pane (the number of documents to be indexed), divide by 5 million, and add 5. For example:
|
||||||
|
- If you have fewer than about 2,000,000 documents, use the default of 5 shards
|
||||||
|
- 10,000,000 documents: `10000000/5000000 + 5` = 7 shards
|
||||||
|
- 100,000,000 documents: `100000000/5000000 + 5` = 25 shards
|
||||||
- `refresh_interval` is a per index setting. You may want to adjust that from default `1s` to a bigger value if you don't need data in real-time. This will change how soon you will see fresh results. If that's important for you, you should leave it as close as possible to the default value.
|
- `refresh_interval` is a per index setting. You may want to adjust that from default `1s` to a bigger value if you don't need data in real-time. This will change how soon you will see fresh results. If that's important for you, you should leave it as close as possible to the default value.
|
||||||
- You might want to raise [`indices.memory.index_buffer_size`](https://www.elastic.co/guide/en/elasticsearch/reference/current/indexing-buffer.html) to 30% or 40% if you have a lot of heavy indexing operations.
|
- You might want to raise [`indices.memory.index_buffer_size`](https://www.elastic.co/guide/en/elasticsearch/reference/current/indexing-buffer.html) to 30% or 40% if you have a lot of heavy indexing operations.
|
||||||
|
|
||||||
|
@ -884,3 +904,35 @@ Improvements to the `code_analyzer` pattern and filters are being discussed in [
|
||||||
### Some binary files may not be searchable by name
|
### Some binary files may not be searchable by name
|
||||||
|
|
||||||
In GitLab 13.9, a change was made where [binary file names are being indexed](https://gitlab.com/gitlab-org/gitlab/-/issues/301083). However, without indexing all projects' data from scratch, only binary files that are added or updated after the GitLab 13.9 release are searchable.
|
In GitLab 13.9, a change was made where [binary file names are being indexed](https://gitlab.com/gitlab-org/gitlab/-/issues/301083). However, without indexing all projects' data from scratch, only binary files that are added or updated after the GitLab 13.9 release are searchable.
|
||||||
|
|
||||||
|
### Last resort to recreate an index
|
||||||
|
|
||||||
|
There may be cases where somehow data never got indexed and it's not in the
|
||||||
|
queue, or the index is somehow in a state where migrations just simply cannot
|
||||||
|
proceed. It is always best to try to troubleshoot the root cause of the problem
|
||||||
|
using the above [troubleshooting](#troubleshooting) steps.
|
||||||
|
|
||||||
|
If there are no other options, then you always have the option of recreating the
|
||||||
|
entire index from scratch. If you have a small GitLab installation, this can
|
||||||
|
sometimes be a quick way to resolve a problem, but if you have a large GitLab
|
||||||
|
installation, then this will likely take a very long time to complete. Until the
|
||||||
|
index is fully recreated, your index will not be serving correct search results,
|
||||||
|
so you may want to disable **Search with Elasticsearch** while it is running.
|
||||||
|
|
||||||
|
If you are sure you've read the above caveats and want to proceed, then you
|
||||||
|
should run the following Rake task to recreate the entire index from scratch:
|
||||||
|
|
||||||
|
**For Omnibus installations**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# WARNING: DO NOT RUN THIS UNTIL YOU READ THE DESCRIPTION ABOVE
|
||||||
|
sudo gitlab-rake gitlab:elastic:index
|
||||||
|
```
|
||||||
|
|
||||||
|
**For installations from source**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# WARNING: DO NOT RUN THIS UNTIL YOU READ THE DESCRIPTION ABOVE
|
||||||
|
cd /home/git/gitlab
|
||||||
|
sudo -u git -H bundle exec rake gitlab:elastic:index
|
||||||
|
```
|
||||||
|
|
|
@ -138,14 +138,16 @@ pending_job_classes = scheduled_queue.select { |job| job["class"] == "Background
|
||||||
pending_job_classes.each { |job_class| Gitlab::BackgroundMigration.steal(job_class) }
|
pending_job_classes.each { |job_class| Gitlab::BackgroundMigration.steal(job_class) }
|
||||||
```
|
```
|
||||||
|
|
||||||
## Checking for pending Elasticsearch migrations
|
## Checking for pending Advanced Search migrations
|
||||||
|
|
||||||
This section is only applicable if you have enabled the [Elasticsearch
|
This section is only applicable if you have enabled the [Elasticsearch
|
||||||
integration](../integration/elasticsearch.md).
|
integration](../integration/elasticsearch.md).
|
||||||
|
|
||||||
Certain major releases might require [Elasticsearch
|
Major releases require all [Advanced Search
|
||||||
migrations](../integration/elasticsearch.md#background-migrations) to be
|
migrations](../integration/elasticsearch.md#advanced-search-migrations) to
|
||||||
finished. You can find pending migrations by running the following command:
|
be finished from the most recent minor release in your current version
|
||||||
|
before the major version upgrade. You can find pending migrations by
|
||||||
|
running the following command:
|
||||||
|
|
||||||
**For Omnibus installations**
|
**For Omnibus installations**
|
||||||
|
|
||||||
|
@ -160,7 +162,7 @@ cd /home/git/gitlab
|
||||||
sudo -u git -H bundle exec rake gitlab:elastic:list_pending_migrations
|
sudo -u git -H bundle exec rake gitlab:elastic:list_pending_migrations
|
||||||
```
|
```
|
||||||
|
|
||||||
### What do I do if my Elasticsearch migrations are stuck?
|
### What do I do if my Advanced Search migrations are stuck?
|
||||||
|
|
||||||
See [how to retry a halted
|
See [how to retry a halted
|
||||||
migration](../integration/elasticsearch.md#retry-a-halted-migration).
|
migration](../integration/elasticsearch.md#retry-a-halted-migration).
|
||||||
|
@ -208,6 +210,13 @@ It's also important to ensure that any background migrations have been fully com
|
||||||
before upgrading to a new major version. To see the current size of the `background_migration` queue,
|
before upgrading to a new major version. To see the current size of the `background_migration` queue,
|
||||||
[Check for background migrations before upgrading](#checking-for-background-migrations-before-upgrading).
|
[Check for background migrations before upgrading](#checking-for-background-migrations-before-upgrading).
|
||||||
|
|
||||||
|
If you have enabled the [Elasticsearch
|
||||||
|
integration](../integration/elasticsearch.md), then you will also need to ensure
|
||||||
|
all Advanced Search migrations are completed in the last minor version within
|
||||||
|
your current version. Be sure to [check for pending Advanced Search
|
||||||
|
migrations](#checking-for-pending-advanced-search-migrations) before proceeding
|
||||||
|
with the major version upgrade.
|
||||||
|
|
||||||
If your GitLab instance has any runners associated with it, it is very
|
If your GitLab instance has any runners associated with it, it is very
|
||||||
important to upgrade GitLab Runner to match the GitLab minor version that was
|
important to upgrade GitLab Runner to match the GitLab minor version that was
|
||||||
upgraded to. This is to ensure [compatibility with GitLab versions](https://docs.gitlab.com/runner/#compatibility-with-gitlab-versions).
|
upgraded to. This is to ensure [compatibility with GitLab versions](https://docs.gitlab.com/runner/#compatibility-with-gitlab-versions).
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: 'usage_trends.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](usage_trends.md).
|
|
||||||
|
|
||||||
<!-- This redirect file can be deleted after <2022-03-20>. -->
|
|
||||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
|
|
|
@ -1322,7 +1322,7 @@ Examples:
|
||||||
<!--
|
<!--
|
||||||
The "2." and "4." in the example above are changed to "1." below, to match the style
|
The "2." and "4." in the example above are changed to "1." below, to match the style
|
||||||
standards on docs.gitlab.com.
|
standards on docs.gitlab.com.
|
||||||
See https://docs.gitlab.com/ee/development/documentation/styleguide.html#lists
|
See https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#lists
|
||||||
-->
|
-->
|
||||||
|
|
||||||
1. First ordered list item
|
1. First ordered list item
|
||||||
|
@ -1356,7 +1356,7 @@ They can even:
|
||||||
<!--
|
<!--
|
||||||
The "*" and "+" in the example above are changed to "-" below, to match the style
|
The "*" and "+" in the example above are changed to "-" below, to match the style
|
||||||
standards on docs.gitlab.com.
|
standards on docs.gitlab.com.
|
||||||
See https://docs.gitlab.com/ee/development/documentation/styleguide.html#lists
|
See https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#lists
|
||||||
-->
|
-->
|
||||||
|
|
||||||
Unordered lists can:
|
Unordered lists can:
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: advanced_search.md
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](advanced_search.md).
|
|
||||||
|
|
||||||
<!-- This redirect file can be deleted after <2021-02-12>. -->
|
|
||||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
|
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: advanced_search.md
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](advanced_search.md).
|
|
||||||
|
|
||||||
<!-- This redirect file can be deleted after <2021-02-12>. -->
|
|
||||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
|
|
|
@ -19,6 +19,10 @@ module Gitlab
|
||||||
Settings
|
Settings
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.host_with_port
|
||||||
|
"#{self.config.gitlab.host}:#{self.config.gitlab.port}"
|
||||||
|
end
|
||||||
|
|
||||||
def self.revision
|
def self.revision
|
||||||
@_revision ||= begin
|
@_revision ||= begin
|
||||||
if File.exist?(root.join("REVISION"))
|
if File.exist?(root.join("REVISION"))
|
||||||
|
|
|
@ -7,7 +7,7 @@ module Gitlab
|
||||||
module Registry
|
module Registry
|
||||||
class DependencyProxy < GitlabRegistry
|
class DependencyProxy < GitlabRegistry
|
||||||
def url
|
def url
|
||||||
"#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}"
|
Gitlab.host_with_port
|
||||||
end
|
end
|
||||||
|
|
||||||
def valid?
|
def valid?
|
||||||
|
|
|
@ -78,7 +78,7 @@ then
|
||||||
echo
|
echo
|
||||||
echo ' ✖ ERROR: The number of README.md file(s) has changed. Use index.md instead of README.md.' >&2
|
echo ' ✖ ERROR: The number of README.md file(s) has changed. Use index.md instead of README.md.' >&2
|
||||||
echo ' ✖ If removing a README.md file, update NUMBER_READMES in lint-doc.sh.' >&2
|
echo ' ✖ If removing a README.md file, update NUMBER_READMES in lint-doc.sh.' >&2
|
||||||
echo ' https://docs.gitlab.com/ee/development/documentation/styleguide.html#work-with-directories-and-files'
|
echo ' https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#work-with-directories-and-files'
|
||||||
echo
|
echo
|
||||||
((ERRORCODE++))
|
((ERRORCODE++))
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -42,7 +42,6 @@ describe('Board card component', () => {
|
||||||
GlLabel: true,
|
GlLabel: true,
|
||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
groupId: null,
|
|
||||||
rootPath: '/',
|
rootPath: '/',
|
||||||
scopedLabelsAvailable: false,
|
scopedLabelsAvailable: false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,7 +19,11 @@ const createStore = (state = defaultState) => {
|
||||||
return new Vuex.Store({
|
return new Vuex.Store({
|
||||||
state,
|
state,
|
||||||
actions,
|
actions,
|
||||||
getters: { isEpicBoard: () => false },
|
getters: {
|
||||||
|
isGroupBoard: () => false,
|
||||||
|
isProjectBoard: () => true,
|
||||||
|
isEpicBoard: () => false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import Vuex from 'vuex';
|
||||||
import boardNewIssue from '~/boards/components/board_new_issue_deprecated.vue';
|
import boardNewIssue from '~/boards/components/board_new_issue_deprecated.vue';
|
||||||
import boardsStore from '~/boards/stores/boards_store';
|
import boardsStore from '~/boards/stores/boards_store';
|
||||||
import axios from '~/lib/utils/axios_utils';
|
import axios from '~/lib/utils/axios_utils';
|
||||||
|
@ -10,6 +11,8 @@ import axios from '~/lib/utils/axios_utils';
|
||||||
import '~/boards/models/list';
|
import '~/boards/models/list';
|
||||||
import { listObj, boardsMockInterceptor } from './mock_data';
|
import { listObj, boardsMockInterceptor } from './mock_data';
|
||||||
|
|
||||||
|
Vue.use(Vuex);
|
||||||
|
|
||||||
describe('Issue boards new issue form', () => {
|
describe('Issue boards new issue form', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let vm;
|
let vm;
|
||||||
|
@ -43,11 +46,16 @@ describe('Issue boards new issue form', () => {
|
||||||
newIssueMock = Promise.resolve(promiseReturn);
|
newIssueMock = Promise.resolve(promiseReturn);
|
||||||
jest.spyOn(list, 'newIssue').mockImplementation(() => newIssueMock);
|
jest.spyOn(list, 'newIssue').mockImplementation(() => newIssueMock);
|
||||||
|
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
getters: { isGroupBoard: () => false },
|
||||||
|
});
|
||||||
|
|
||||||
wrapper = mount(BoardNewIssueComp, {
|
wrapper = mount(BoardNewIssueComp, {
|
||||||
propsData: {
|
propsData: {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
list,
|
list,
|
||||||
},
|
},
|
||||||
|
store,
|
||||||
provide: {
|
provide: {
|
||||||
groupId: null,
|
groupId: null,
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { formType } from '~/boards/constants';
|
||||||
import createBoardMutation from '~/boards/graphql/board_create.mutation.graphql';
|
import createBoardMutation from '~/boards/graphql/board_create.mutation.graphql';
|
||||||
import destroyBoardMutation from '~/boards/graphql/board_destroy.mutation.graphql';
|
import destroyBoardMutation from '~/boards/graphql/board_destroy.mutation.graphql';
|
||||||
import updateBoardMutation from '~/boards/graphql/board_update.mutation.graphql';
|
import updateBoardMutation from '~/boards/graphql/board_update.mutation.graphql';
|
||||||
|
import { createStore } from '~/boards/stores';
|
||||||
import { deprecatedCreateFlash as createFlash } from '~/flash';
|
import { deprecatedCreateFlash as createFlash } from '~/flash';
|
||||||
import { visitUrl } from '~/lib/utils/url_utility';
|
import { visitUrl } from '~/lib/utils/url_utility';
|
||||||
|
|
||||||
|
@ -48,6 +49,13 @@ describe('BoardForm', () => {
|
||||||
const findDeleteConfirmation = () => wrapper.find('[data-testid="delete-confirmation-message"]');
|
const findDeleteConfirmation = () => wrapper.find('[data-testid="delete-confirmation-message"]');
|
||||||
const findInput = () => wrapper.find('#board-new-name');
|
const findInput = () => wrapper.find('#board-new-name');
|
||||||
|
|
||||||
|
const store = createStore({
|
||||||
|
getters: {
|
||||||
|
isGroupBoard: () => true,
|
||||||
|
isProjectBoard: () => false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const createComponent = (props, data) => {
|
const createComponent = (props, data) => {
|
||||||
wrapper = shallowMount(BoardForm, {
|
wrapper = shallowMount(BoardForm, {
|
||||||
propsData: { ...defaultProps, ...props },
|
propsData: { ...defaultProps, ...props },
|
||||||
|
@ -64,6 +72,7 @@ describe('BoardForm', () => {
|
||||||
mutate,
|
mutate,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
store,
|
||||||
attachTo: document.body,
|
attachTo: document.body,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
import BoardNewIssue from '~/boards/components/board_new_issue.vue';
|
import BoardNewIssue from '~/boards/components/board_new_issue.vue';
|
||||||
|
|
||||||
import '~/boards/models/list';
|
|
||||||
import { mockList, mockGroupProjects } from '../mock_data';
|
import { mockList, mockGroupProjects } from '../mock_data';
|
||||||
|
|
||||||
const localVue = createLocalVue();
|
const localVue = createLocalVue();
|
||||||
|
@ -31,7 +30,7 @@ describe('Issue boards new issue form', () => {
|
||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
state: { selectedProject: mockGroupProjects[0] },
|
state: { selectedProject: mockGroupProjects[0] },
|
||||||
actions: { addListNewIssue: addListNewIssuesSpy },
|
actions: { addListNewIssue: addListNewIssuesSpy },
|
||||||
getters: {},
|
getters: { isGroupBoard: () => false, isProjectBoard: () => true },
|
||||||
});
|
});
|
||||||
|
|
||||||
wrapper = shallowMount(BoardNewIssue, {
|
wrapper = shallowMount(BoardNewIssue, {
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||||
|
import Vuex from 'vuex';
|
||||||
|
import FilteredSearch from '~/boards/components/filtered_search.vue';
|
||||||
|
import { createStore } from '~/boards/stores';
|
||||||
|
import * as commonUtils from '~/lib/utils/common_utils';
|
||||||
|
import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
|
||||||
|
|
||||||
|
const localVue = createLocalVue();
|
||||||
|
localVue.use(Vuex);
|
||||||
|
|
||||||
|
describe('FilteredSearch', () => {
|
||||||
|
let wrapper;
|
||||||
|
let store;
|
||||||
|
|
||||||
|
const createComponent = () => {
|
||||||
|
wrapper = shallowMount(FilteredSearch, {
|
||||||
|
localVue,
|
||||||
|
propsData: { search: '' },
|
||||||
|
store,
|
||||||
|
attachTo: document.body,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// this needed for actions call for performSearch
|
||||||
|
window.gon = { features: {} };
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('default', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store = createStore();
|
||||||
|
|
||||||
|
jest.spyOn(store, 'dispatch');
|
||||||
|
|
||||||
|
createComponent();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('finds FilteredSearch', () => {
|
||||||
|
expect(wrapper.find(FilteredSearchBarRoot).exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when onFilter is emitted', () => {
|
||||||
|
it('calls performSearch', () => {
|
||||||
|
wrapper.find(FilteredSearchBarRoot).vm.$emit('onFilter', [{ value: { data: '' } }]);
|
||||||
|
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith('performSearch');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls historyPushState', () => {
|
||||||
|
commonUtils.historyPushState = jest.fn();
|
||||||
|
wrapper
|
||||||
|
.find(FilteredSearchBarRoot)
|
||||||
|
.vm.$emit('onFilter', [{ value: { data: 'searchQuery' } }]);
|
||||||
|
|
||||||
|
expect(commonUtils.historyPushState).toHaveBeenCalledWith(
|
||||||
|
'http://test.host/?search=searchQuery',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -10,6 +10,42 @@ import {
|
||||||
} from '../mock_data';
|
} from '../mock_data';
|
||||||
|
|
||||||
describe('Boards - Getters', () => {
|
describe('Boards - Getters', () => {
|
||||||
|
describe('isGroupBoard', () => {
|
||||||
|
it('returns true when boardType on state is group', () => {
|
||||||
|
const state = {
|
||||||
|
boardType: 'group',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getters.isGroupBoard(state)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when boardType on state is not group', () => {
|
||||||
|
const state = {
|
||||||
|
boardType: 'project',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getters.isGroupBoard(state)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isProjectBoard', () => {
|
||||||
|
it('returns true when boardType on state is project', () => {
|
||||||
|
const state = {
|
||||||
|
boardType: 'project',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getters.isProjectBoard(state)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when boardType on state is not project', () => {
|
||||||
|
const state = {
|
||||||
|
boardType: 'group',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getters.isProjectBoard(state)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('isSidebarOpen', () => {
|
describe('isSidebarOpen', () => {
|
||||||
it('returns true when activeId is not equal to 0', () => {
|
it('returns true when activeId is not equal to 0', () => {
|
||||||
const state = {
|
const state = {
|
||||||
|
|
|
@ -16,6 +16,8 @@ RSpec.describe Projects::IssuesController, '(JavaScript fixtures)', type: :contr
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
stub_feature_flags(boards_filtered_search: false)
|
||||||
|
|
||||||
project.add_maintainer(user)
|
project.add_maintainer(user)
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
end
|
end
|
||||||
|
|
|
@ -188,7 +188,7 @@ describe('GroupItemComponent', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render component template correctly', () => {
|
it('should render component template correctly', () => {
|
||||||
const visibilityIconEl = vm.$el.querySelector('.item-visibility');
|
const visibilityIconEl = vm.$el.querySelector('[data-testid="group-visibility-icon"]');
|
||||||
|
|
||||||
expect(vm.$el.getAttribute('id')).toBe('group-55');
|
expect(vm.$el.getAttribute('id')).toBe('group-55');
|
||||||
expect(vm.$el.classList.contains('group-row')).toBeTruthy();
|
expect(vm.$el.classList.contains('group-row')).toBeTruthy();
|
||||||
|
@ -209,8 +209,7 @@ describe('GroupItemComponent', () => {
|
||||||
expect(vm.$el.querySelector('.title a.no-expand')).toBeDefined();
|
expect(vm.$el.querySelector('.title a.no-expand')).toBeDefined();
|
||||||
|
|
||||||
expect(visibilityIconEl).not.toBe(null);
|
expect(visibilityIconEl).not.toBe(null);
|
||||||
expect(visibilityIconEl.title).toBe(vm.visibilityTooltip);
|
expect(visibilityIconEl.getAttribute('title')).toBe(vm.visibilityTooltip);
|
||||||
expect(visibilityIconEl.querySelectorAll('svg').length).toBeGreaterThan(0);
|
|
||||||
|
|
||||||
expect(vm.$el.querySelector('.access-type')).toBeDefined();
|
expect(vm.$el.querySelector('.access-type')).toBeDefined();
|
||||||
expect(vm.$el.querySelector('.description')).toBeDefined();
|
expect(vm.$el.querySelector('.description')).toBeDefined();
|
||||||
|
|
|
@ -6,6 +6,7 @@ import axios from '~/lib/utils/axios_utils';
|
||||||
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
|
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
|
||||||
import noteActions from '~/notes/components/note_actions.vue';
|
import noteActions from '~/notes/components/note_actions.vue';
|
||||||
import createStore from '~/notes/stores';
|
import createStore from '~/notes/stores';
|
||||||
|
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
|
||||||
import { userDataMock } from '../mock_data';
|
import { userDataMock } from '../mock_data';
|
||||||
|
|
||||||
describe('noteActions', () => {
|
describe('noteActions', () => {
|
||||||
|
@ -15,6 +16,9 @@ describe('noteActions', () => {
|
||||||
let actions;
|
let actions;
|
||||||
let axiosMock;
|
let axiosMock;
|
||||||
|
|
||||||
|
const findUserAccessRoleBadge = (idx) => wrapper.findAll(UserAccessRoleBadge).at(idx);
|
||||||
|
const findUserAccessRoleBadgeText = (idx) => findUserAccessRoleBadge(idx).text().trim();
|
||||||
|
|
||||||
const mountNoteActions = (propsData, computed) => {
|
const mountNoteActions = (propsData, computed) => {
|
||||||
const localVue = createLocalVue();
|
const localVue = createLocalVue();
|
||||||
return mount(localVue.extend(noteActions), {
|
return mount(localVue.extend(noteActions), {
|
||||||
|
@ -66,11 +70,11 @@ describe('noteActions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render noteable author badge', () => {
|
it('should render noteable author badge', () => {
|
||||||
expect(wrapper.findAll('.note-role').at(0).text().trim()).toEqual('Author');
|
expect(findUserAccessRoleBadgeText(0)).toBe('Author');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render access level badge', () => {
|
it('should render access level badge', () => {
|
||||||
expect(wrapper.findAll('.note-role').at(1).text().trim()).toEqual(props.accessLevel);
|
expect(findUserAccessRoleBadgeText(1)).toBe(props.accessLevel);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render contributor badge', () => {
|
it('should render contributor badge', () => {
|
||||||
|
@ -80,7 +84,7 @@ describe('noteActions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return wrapper.vm.$nextTick().then(() => {
|
return wrapper.vm.$nextTick().then(() => {
|
||||||
expect(wrapper.findAll('.note-role').at(1).text().trim()).toBe('Contributor');
|
expect(findUserAccessRoleBadgeText(1)).toBe('Contributor');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { GlBadge } from '@gitlab/ui';
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
|
||||||
|
|
||||||
|
describe('UserAccessRoleBadge', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
const createComponent = ({ slots } = {}) => {
|
||||||
|
wrapper = shallowMount(UserAccessRoleBadge, {
|
||||||
|
slots,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
it('renders slot content inside GlBadge', () => {
|
||||||
|
createComponent({
|
||||||
|
slots: {
|
||||||
|
default: 'test slot content',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const badge = wrapper.find(GlBadge);
|
||||||
|
|
||||||
|
expect(badge.exists()).toBe(true);
|
||||||
|
expect(badge.html()).toContain('test slot content');
|
||||||
|
});
|
||||||
|
});
|
|
@ -166,7 +166,7 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
|
||||||
let(:ports) { Project::VALID_IMPORT_PORTS }
|
let(:ports) { Project::VALID_IMPORT_PORTS }
|
||||||
|
|
||||||
it 'allows imports from configured web host and port' do
|
it 'allows imports from configured web host and port' do
|
||||||
import_url = "http://#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}/t.git"
|
import_url = "http://#{Gitlab.host_with_port}/t.git"
|
||||||
expect(described_class.blocked_url?(import_url)).to be false
|
expect(described_class.blocked_url?(import_url)).to be false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns true for bad protocol on configured web/SSH host and ports' do
|
it 'returns true for bad protocol on configured web/SSH host and ports' do
|
||||||
web_url = "javascript://#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}/t.git%0aalert(1)"
|
web_url = "javascript://#{Gitlab.host_with_port}/t.git%0aalert(1)"
|
||||||
expect(described_class.blocked_url?(web_url)).to be true
|
expect(described_class.blocked_url?(web_url)).to be true
|
||||||
|
|
||||||
ssh_url = "javascript://#{Gitlab.config.gitlab_shell.ssh_host}:#{Gitlab.config.gitlab_shell.ssh_port}/t.git%0aalert(1)"
|
ssh_url = "javascript://#{Gitlab.config.gitlab_shell.ssh_host}:#{Gitlab.config.gitlab_shell.ssh_port}/t.git%0aalert(1)"
|
||||||
|
|
|
@ -2465,9 +2465,9 @@ RSpec.describe Ci::Build do
|
||||||
{ key: 'CI_CONFIG_PATH', value: project.ci_config_path_or_default, public: true, masked: false },
|
{ key: 'CI_CONFIG_PATH', value: project.ci_config_path_or_default, public: true, masked: false },
|
||||||
{ key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true, masked: false },
|
{ key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true, masked: false },
|
||||||
{ key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false },
|
{ key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false },
|
||||||
{ key: 'CI_DEPENDENCY_PROXY_SERVER', value: "#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}", public: true, masked: false },
|
{ key: 'CI_DEPENDENCY_PROXY_SERVER', value: Gitlab.host_with_port, public: true, masked: false },
|
||||||
{ key: 'CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX',
|
{ key: 'CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX',
|
||||||
value: "#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}/#{project.namespace.root_ancestor.path}#{DependencyProxy::URL_SUFFIX}",
|
value: "#{Gitlab.host_with_port}/#{project.namespace.root_ancestor.path}#{DependencyProxy::URL_SUFFIX}",
|
||||||
public: true,
|
public: true,
|
||||||
masked: false },
|
masked: false },
|
||||||
{ key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true, masked: false },
|
{ key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true, masked: false },
|
||||||
|
|
|
@ -246,6 +246,12 @@ RSpec.configure do |config|
|
||||||
|
|
||||||
stub_feature_flags(unified_diff_components: false)
|
stub_feature_flags(unified_diff_components: false)
|
||||||
|
|
||||||
|
# Disable this feature flag as we iterate and
|
||||||
|
# refactor filtered search to use gitlab ui
|
||||||
|
# components to meet feature parody. More details found
|
||||||
|
# https://gitlab.com/groups/gitlab-org/-/epics/5501
|
||||||
|
stub_feature_flags(boards_filtered_search: false)
|
||||||
|
|
||||||
allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(enable_rugged)
|
allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(enable_rugged)
|
||||||
else
|
else
|
||||||
unstub_all_feature_flags
|
unstub_all_feature_flags
|
||||||
|
|
Loading…
Reference in New Issue