Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-03-10 12:09:29 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-03-10 12:09:29 +0300
commit921173681c207356914a35ea3dca1afffeac8b05 (patch)
tree45f0b49531b549dd7e28dd22744f822a214390a4
parente0df70a614e6381d1ccb9547f23c797063ba6dfc (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml4
-rw-r--r--app/assets/javascripts/boards/components/board_card_inner.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue6
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue7
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue_deprecated.vue6
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue7
-rw-r--r--app/assets/javascripts/boards/components/boards_selector_deprecated.vue7
-rw-r--r--app/assets/javascripts/boards/components/filtered_search.vue54
-rw-r--r--app/assets/javascripts/boards/filtered_search.js25
-rw-r--r--app/assets/javascripts/boards/index.js20
-rw-r--r--app/assets/javascripts/boards/stores/getters.js4
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue43
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue26
-rw-r--r--app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue20
-rw-r--r--app/assets/javascripts/ref/components/ref_selector.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/user_access_role_badge.vue22
-rw-r--r--app/assets/stylesheets/pages/groups.scss4
-rw-r--r--app/controllers/groups/boards_controller.rb1
-rw-r--r--app/models/project.rb4
-rw-r--r--app/views/shared/deploy_tokens/_form.html.haml2
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml327
-rw-r--r--changelogs/unreleased/btn-confirm-shared-deploy-tokens.yml5
-rw-r--r--changelogs/unreleased/nfriend-turn-off-ref-selector-autocomplete.yml5
-rw-r--r--changelogs/unreleased/user-access-role-badge-component.yml5
-rw-r--r--config/feature_categories.yml8
-rw-r--r--config/feature_flags/development/boards_filtered_search.yml (renamed from config/feature_flags/development/saas_add_seats_button.yml)10
-rw-r--r--doc/.vale/gitlab/Admin.yml2
-rw-r--r--doc/.vale/gitlab/AlertBoxStyle.yml2
-rw-r--r--doc/.vale/gitlab/RelativeLinksDoubleSlashes.yml13
-rw-r--r--doc/administration/pages/index.md2
-rw-r--r--doc/administration/troubleshooting/elasticsearch.md21
-rw-r--r--doc/development/documentation/styleguide.md8
-rw-r--r--doc/development/elasticsearch.md63
-rw-r--r--doc/integration/elasticsearch.md60
-rw-r--r--doc/update/index.md19
-rw-r--r--doc/user/admin_area/analytics/instance_statistics.md8
-rw-r--r--doc/user/markdown.md4
-rw-r--r--doc/user/search/advanced_global_search.md8
-rw-r--r--doc/user/search/advanced_search_syntax.md8
-rw-r--r--lib/gitlab.rb4
-rw-r--r--lib/gitlab/ci/build/credentials/registry/dependency_proxy.rb2
-rwxr-xr-xscripts/lint-doc.sh2
-rw-r--r--spec/frontend/boards/board_card_inner_spec.js1
-rw-r--r--spec/frontend/boards/board_list_spec.js6
-rw-r--r--spec/frontend/boards/board_new_issue_deprecated_spec.js8
-rw-r--r--spec/frontend/boards/components/board_form_spec.js9
-rw-r--r--spec/frontend/boards/components/board_new_issue_spec.js3
-rw-r--r--spec/frontend/boards/components/filtered_search_spec.js65
-rw-r--r--spec/frontend/boards/stores/getters_spec.js36
-rw-r--r--spec/frontend/fixtures/issues.rb2
-rw-r--r--spec/frontend/groups/components/group_item_spec.js5
-rw-r--r--spec/frontend/notes/components/note_actions_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/user_access_role_badge_spec.js26
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb4
-rw-r--r--spec/models/ci/build_spec.rb4
-rw-r--r--spec/spec_helper.rb6
56 files changed, 733 insertions, 303 deletions
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 5de8a6bc250..274629f9c60 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -102,7 +102,7 @@
- name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:5.0-alpine
- - name: elasticsearch:7.10.1
+ - name: elasticsearch:7.11.1
command: ["elasticsearch", "-E", "discovery.type=single-node"]
variables:
POSTGRES_HOST_AUTH_METHOD: trust
@@ -113,7 +113,7 @@
- name: postgres:12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:5.0-alpine
- - name: elasticsearch:7.10.1
+ - name: elasticsearch:7.11.1
command: ["elasticsearch", "-E", "discovery.type=single-node"]
variables:
POSTGRES_HOST_AUTH_METHOD: trust
diff --git a/app/assets/javascripts/boards/components/board_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue
index e9ba66cf86a..d4d6b17a589 100644
--- a/app/assets/javascripts/boards/components/board_card_inner.vue
+++ b/app/assets/javascripts/boards/components/board_card_inner.vue
@@ -27,7 +27,7 @@ export default {
GlTooltip: GlTooltipDirective,
},
mixins: [boardCardInner],
- inject: ['groupId', 'rootPath', 'scopedLabelsAvailable'],
+ inject: ['rootPath', 'scopedLabelsAvailable'],
props: {
item: {
type: Object,
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 0d392686393..d8504dcfb0f 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -107,7 +107,7 @@ export default {
};
},
computed: {
- ...mapGetters(['isEpicBoard']),
+ ...mapGetters(['isEpicBoard', 'isGroupBoard', 'isProjectBoard']),
isNewForm() {
return this.currentPage === formType.new;
},
@@ -178,8 +178,8 @@ export default {
}
: {
...variables,
- projectPath: this.projectId ? this.fullPath : undefined,
- groupPath: this.groupId ? this.fullPath : undefined,
+ projectPath: this.isProjectBoard ? this.fullPath : undefined,
+ groupPath: this.isGroupBoard ? this.fullPath : undefined,
};
},
boardScopeMutationVariables() {
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index 1df154688c8..a81c28733cd 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -1,6 +1,6 @@
<script>
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 { __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -32,8 +32,9 @@ export default {
},
computed: {
...mapState(['selectedProject']),
+ ...mapGetters(['isGroupBoard']),
disabled() {
- if (this.groupId) {
+ if (this.isGroupBoard) {
return this.title === '' || !this.selectedProject.name;
}
return this.title === '';
@@ -98,7 +99,7 @@ export default {
name="issue_title"
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">
<gl-button
ref="submitButton"
diff --git a/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue b/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue
index eff87ff110e..16f23dfff0e 100644
--- a/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue
@@ -1,5 +1,6 @@
<script>
import { GlButton } from '@gitlab/ui';
+import { mapGetters } from 'vuex';
import { getMilestone } from 'ee_else_ce/boards/boards_util';
import ListIssue from 'ee_else_ce/boards/models/issue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -31,8 +32,9 @@ export default {
};
},
computed: {
+ ...mapGetters(['isGroupBoard']),
disabled() {
- if (this.groupId) {
+ if (this.isGroupBoard) {
return this.title === '' || !this.selectedProject.name;
}
return this.title === '';
@@ -110,7 +112,7 @@ export default {
name="issue_title"
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">
<gl-button
ref="submitButton"
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index 3ffd28665f9..5124467136e 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -9,6 +9,7 @@ import {
GlModalDirective,
} from '@gitlab/ui';
import { throttle } from 'lodash';
+import { mapGetters, mapState } from 'vuex';
import BoardForm from 'ee_else_ce/boards/components/board_form.vue';
@@ -109,8 +110,10 @@ export default {
};
},
computed: {
+ ...mapState(['boardType']),
+ ...mapGetters(['isGroupBoard']),
parentType() {
- return this.groupId ? 'group' : 'project';
+ return this.boardType;
},
loading() {
return this.loadingRecentBoards || Boolean(this.loadingBoards);
@@ -171,7 +174,7 @@ export default {
}));
},
boardQuery() {
- return this.groupId ? groupQuery : projectQuery;
+ return this.isGroupBoard ? groupQuery : projectQuery;
},
loadBoards(toggleDropdown = true) {
if (toggleDropdown && this.boards.length > 0) {
diff --git a/app/assets/javascripts/boards/components/boards_selector_deprecated.vue b/app/assets/javascripts/boards/components/boards_selector_deprecated.vue
index 33ad46a0d29..85c7b27336b 100644
--- a/app/assets/javascripts/boards/components/boards_selector_deprecated.vue
+++ b/app/assets/javascripts/boards/components/boards_selector_deprecated.vue
@@ -9,6 +9,7 @@ import {
GlModalDirective,
} from '@gitlab/ui';
import { throttle } from 'lodash';
+import { mapGetters, mapState } from 'vuex';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import httpStatusCodes from '~/lib/utils/http_status';
@@ -108,8 +109,10 @@ export default {
};
},
computed: {
+ ...mapState(['boardType']),
+ ...mapGetters(['isGroupBoard']),
parentType() {
- return this.groupId ? 'group' : 'project';
+ return this.boardType;
},
loading() {
return this.loadingRecentBoards || Boolean(this.loadingBoards);
@@ -167,7 +170,7 @@ export default {
return { fullPath: this.state.endpoints.fullPath };
},
query() {
- return this.groupId ? groupQuery : projectQuery;
+ return this.isGroupBoard ? groupQuery : projectQuery;
},
loadingKey: 'loadingBoards',
update(data) {
diff --git a/app/assets/javascripts/boards/components/filtered_search.vue b/app/assets/javascripts/boards/components/filtered_search.vue
new file mode 100644
index 00000000000..8505ea39a6b
--- /dev/null
+++ b/app/assets/javascripts/boards/components/filtered_search.vue
@@ -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>
diff --git a/app/assets/javascripts/boards/filtered_search.js b/app/assets/javascripts/boards/filtered_search.js
new file mode 100644
index 00000000000..182a2cf3724
--- /dev/null
+++ b/app/assets/javascripts/boards/filtered_search.js
@@ -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 },
+ }),
+ });
+};
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 0b9694a253c..4efcb1cd49c 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -53,7 +53,6 @@ let issueBoardsApp;
export default () => {
const $boardApp = document.getElementById('board-app');
-
// check for browser back and trigger a hard reload to circumvent browser caching.
window.addEventListener('pageshow', (event) => {
const isNavTypeBackForward =
@@ -73,6 +72,14 @@ export default () => {
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
issueBoardsApp = new Vue({
el: $boardApp,
@@ -164,8 +171,15 @@ export default () => {
eventHub.$off('initialBoardLoad', this.initialBoardLoad);
},
mounted() {
- this.filterManager = new FilteredSearchBoards(boardsStore.filter, true, boardsStore.cantEdit);
- this.filterManager.setup();
+ if (!gon.features?.boardsFilteredSearch) {
+ this.filterManager = new FilteredSearchBoards(
+ boardsStore.filter,
+ true,
+ boardsStore.cantEdit,
+ );
+
+ this.filterManager.setup();
+ }
this.performSearch();
diff --git a/app/assets/javascripts/boards/stores/getters.js b/app/assets/javascripts/boards/stores/getters.js
index ff8054ee557..c96f92106cc 100644
--- a/app/assets/javascripts/boards/stores/getters.js
+++ b/app/assets/javascripts/boards/stores/getters.js
@@ -1,7 +1,9 @@
import { find } from 'lodash';
-import { inactiveId } from '../constants';
+import { BoardType, inactiveId } from '../constants';
export default {
+ isGroupBoard: (state) => state.boardType === BoardType.group,
+ isProjectBoard: (state) => state.boardType === BoardType.project,
isSidebarOpen: (state) => state.activeId !== inactiveId,
isSwimlanesOn: () => false,
getBoardItemById: (state) => (id) => {
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index d41fa0b2410..9d46fcec09b 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -1,29 +1,36 @@
<script>
-/* eslint-disable vue/no-v-html */
-import { GlLoadingIcon, GlBadge, GlTooltipDirective } from '@gitlab/ui';
-import { visitUrl } from '../../lib/utils/url_utility';
-import identicon from '../../vue_shared/components/identicon.vue';
+import {
+ GlLoadingIcon,
+ GlBadge,
+ 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 eventHub from '../event_hub';
import itemActions from './item_actions.vue';
import itemCaret from './item_caret.vue';
import itemStats from './item_stats.vue';
-import itemStatsValue from './item_stats_value.vue';
import itemTypeIcon from './item_type_icon.vue';
export default {
directives: {
GlTooltip: GlTooltipDirective,
+ SafeHtml: GlSafeHtmlDirective,
},
components: {
GlBadge,
GlLoadingIcon,
+ GlIcon,
+ UserAccessRoleBadge,
identicon,
itemCaret,
itemTypeIcon,
itemStats,
- itemStatsValue,
itemActions,
},
props: {
@@ -91,6 +98,7 @@ export default {
}
},
},
+ safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
};
</script>
@@ -140,28 +148,31 @@ export default {
data-testid="group-name"
:href="group.relativePath"
: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"
- >{{
+ >
+ {{
// ending bracket must be by closing tag to prevent
// link hover text-decoration from over-extending
group.name
- }}</a
- >
- <item-stats-value
- :icon-name="visibilityIcon"
+ }}
+ </a>
+ <gl-icon
+ 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"
- 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 }}
- </span>
+ </user-access-role-badge>
</div>
<div v-if="group.description" class="description">
<span
+ v-safe-html:[$options.safeHtmlConfig]="group.description"
:itemprop="microdata.descriptionItemprop"
data-testid="group-description"
- v-html="group.description"
>
</span>
</div>
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index e1f3c964ed8..0b2c7611f8e 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -7,6 +7,7 @@ import { deprecatedCreateFlash as flash } from '~/flash';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { __, sprintf } from '~/locale';
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 ReplyButton from './note_actions/reply_button.vue';
@@ -17,6 +18,7 @@ export default {
ReplyButton,
GlButton,
GlDropdownItem,
+ UserAccessRoleBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -226,24 +228,30 @@ export default {
<template>
<div class="note-actions">
- <span
+ <user-access-role-badge
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"
- >{{ __('Author') }}</span
>
- <span
+ {{ __('Author') }}
+ </user-access-role-badge>
+ <user-access-role-badge
v-if="accessLevel"
- class="note-role user-access-role has-tooltip"
+ v-gl-tooltip
+ class="gl-mx-3"
:title="displayMemberBadgeText"
- >{{ accessLevel }}</span
>
- <span
+ {{ accessLevel }}
+ </user-access-role-badge>
+ <user-access-role-badge
v-else-if="isContributor"
- class="note-role user-access-role has-tooltip"
+ v-gl-tooltip
+ class="gl-mx-3"
:title="displayContributorBadgeText"
- >{{ __('Contributor') }}</span
>
+ {{ __('Contributor') }}
+ </user-access-role-badge>
<gl-button
v-if="canResolve"
ref="resolveButton"
diff --git a/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue b/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue
index 46d1696b88b..88f4bba5e2a 100644
--- a/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue
+++ b/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue
@@ -11,6 +11,7 @@ import {
} from '@gitlab/ui';
import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '~/groups/constants';
import csrf from '~/lib/utils/csrf';
+import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
export default {
components: {
@@ -20,6 +21,7 @@ export default {
GlButton,
GlTooltip,
GlLink,
+ UserAccessRoleBadge,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -72,7 +74,9 @@ export default {
<template>
<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="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" />
</div>
<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-flex-grow-1 flex-shrink-1">
<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!">{{
- group.full_name
- }}</gl-link>
+ <gl-link :href="group.relative_path" class="gl-mt-3 gl-mr-3 gl-text-gray-900!">
+ {{ group.full_name }}
+ </gl-link>
<gl-icon
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"
:title="visibilityTooltip"
/>
@@ -99,11 +103,11 @@ export default {
class="gl-display-none gl-sm-display-flex gl-mt-3 gl-mr-1"
>{{ __('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 }}
- </span>
+ </user-access-role-badge>
</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>
</div>
</div>
diff --git a/app/assets/javascripts/ref/components/ref_selector.vue b/app/assets/javascripts/ref/components/ref_selector.vue
index 7f30e7c57d7..6481af2fd80 100644
--- a/app/assets/javascripts/ref/components/ref_selector.vue
+++ b/app/assets/javascripts/ref/components/ref_selector.vue
@@ -208,6 +208,7 @@ export default {
ref="searchBox"
v-model.trim="query"
:placeholder="i18n.searchPlaceholder"
+ autocomplete="off"
@input="onSearchBoxInput"
@keydown.enter.prevent="onSearchBoxEnter"
/>
diff --git a/app/assets/javascripts/vue_shared/components/user_access_role_badge.vue b/app/assets/javascripts/vue_shared/components/user_access_role_badge.vue
new file mode 100644
index 00000000000..e5558c038b3
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/user_access_role_badge.vue
@@ -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>
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 87307fd682e..ee1385d36df 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -519,10 +519,6 @@ table.pipeline-project-metrics tr td {
margin-left: 25px;
}
- .item-visibility {
- margin-right: 0;
- }
-
.last-updated {
position: relative;
min-width: 250px;
diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb
index ff5be986f16..3354c0a5c9f 100644
--- a/app/controllers/groups/boards_controller.rb
+++ b/app/controllers/groups/boards_controller.rb
@@ -9,6 +9,7 @@ class Groups::BoardsController < Groups::ApplicationController
before_action :assign_endpoint_vars
before_action do
push_frontend_feature_flag(:graphql_board_lists, group, default_enabled: false)
+ push_frontend_feature_flag(:boards_filtered_search, group)
end
feature_category :boards
diff --git a/app/models/project.rb b/app/models/project.rb
index 7f61b83def5..d960732b16f 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -2026,10 +2026,10 @@ class Project < ApplicationRecord
Gitlab::Ci::Variables::Collection.new.tap do |variables|
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(
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
diff --git a/app/views/shared/deploy_tokens/_form.html.haml b/app/views/shared/deploy_tokens/_form.html.haml
index 052d68baf71..2ddfcf43756 100644
--- a/app/views/shared/deploy_tokens/_form.html.haml
+++ b/app/views/shared/deploy_tokens/_form.html.haml
@@ -48,4 +48,4 @@
.text-secondary= s_('DeployTokens|Allows write access to the package registry.')
.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'
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index cb8a93827a5..f5b2868aa6c 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -21,178 +21,181 @@
- if @can_bulk_update
.check-all-holder.d-none.d-sm-block.hidden
= 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
- .filtered-search-box
- - if type != :boards_modal && type != :boards
- - text = tag.span(sprite_icon('history'), class: "d-md-none") + tag.span(_('Recent searches'), class: "d-none d-md-inline")
- = dropdown_tag(text,
- options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
- toggle_class: "btn filtered-search-history-dropdown-toggle-button",
- dropdown_class: "filtered-search-history-dropdown",
- content_class: "filtered-search-history-dropdown-content" }) do
- .js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } }
- .filtered-search-box-input-container.droplab-dropdown
- .scroll-container
- %ul.tokens-container.list-unstyled
- %li.input-token
- %input.form-control.filtered-search{ search_filter_input_options(type, placeholder) }
- #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item{ data: {hint: "#{'{{hint}}'}", tag: "#{'{{tag}}'}", action: "#{'{{hint === \'search\' ? \'submit\' : \'\' }}'}" } }
- %button.btn.btn-link{ type: 'button' }
- -# Encapsulate static class name `{{icon}}` inside #{} to bypass
- -# haml lint's ClassAttributeWithStaticValue
- %svg
- %use{ 'xlink:href': "#{'{{icon}}'}" }
- %span.js-filter-hint
- {{formattedKey}}
- #js-dropdown-operator.filtered-search-input-dropdown-menu.dropdown-menu
- %ul.filter-dropdown{ data: { dropdown: true, dynamic: true } }
- %li.filter-dropdown-item{ data: { value: "{{ title }}" } }
- %button.btn.btn-link{ type: 'button' }
- {{ title }}
- %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 } }
- %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 Feature.enabled?(:boards_filtered_search, @group)
+ #js-board-filtered-search
+ - else
+ .issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
+ .filtered-search-box
+ - if type != :boards_modal && type != :boards
+ - text = tag.span(sprite_icon('history'), class: "d-md-none") + tag.span(_('Recent searches'), class: "d-none d-md-inline")
+ = dropdown_tag(text,
+ options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
+ toggle_class: "btn filtered-search-history-dropdown-toggle-button",
+ dropdown_class: "filtered-search-history-dropdown",
+ content_class: "filtered-search-history-dropdown-content" }) do
+ .js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } }
+ .filtered-search-box-input-container.droplab-dropdown
+ .scroll-container
+ %ul.tokens-container.list-unstyled
+ %li.input-token
+ %input.form-control.filtered-search{ search_filter_input_options(type, placeholder) }
+ #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item{ data: {hint: "#{'{{hint}}'}", tag: "#{'{{tag}}'}", action: "#{'{{hint === \'search\' ? \'submit\' : \'\' }}'}" } }
+ %button.btn.btn-link{ type: 'button' }
+ -# Encapsulate static class name `{{icon}}` inside #{} to bypass
+ -# haml lint's ClassAttributeWithStaticValue
+ %svg
+ %use{ 'xlink:href': "#{'{{icon}}'}" }
+ %span.js-filter-hint
+ {{formattedKey}}
+ #js-dropdown-operator.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul.filter-dropdown{ data: { dropdown: true, dynamic: true } }
+ %li.filter-dropdown-item{ data: { value: "{{ title }}" } }
+ %button.btn.btn-link{ type: 'button' }
+ {{ title }}
+ %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: 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-reviewer.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
- - if current_user
+ 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 } }
+ %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',
+ user: User.new(username: '{{username}}', name: '{{name}}'),
+ avatar: { lazy: true, url: '{{avatar_url}}' }
+ #js-dropdown-reviewer.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
+ - 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',
- 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}}' }
- = 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
+ 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-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
+ #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.monospace
+ %button.btn.btn-link.js-data-value{ type: 'button' }
{{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' }
- = sprite_icon('close', size: 16, css_class: 'clear-search-icon')
+ %button.clear-search.hidden{ type: 'button' }
+ = sprite_icon('close', size: 16, css_class: 'clear-search-icon')
.filter-dropdown-container.d-flex.flex-column.flex-md-row
- if type == :boards
#js-board-labels-toggle
diff --git a/changelogs/unreleased/btn-confirm-shared-deploy-tokens.yml b/changelogs/unreleased/btn-confirm-shared-deploy-tokens.yml
new file mode 100644
index 00000000000..37016c27eb6
--- /dev/null
+++ b/changelogs/unreleased/btn-confirm-shared-deploy-tokens.yml
@@ -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
diff --git a/changelogs/unreleased/nfriend-turn-off-ref-selector-autocomplete.yml b/changelogs/unreleased/nfriend-turn-off-ref-selector-autocomplete.yml
new file mode 100644
index 00000000000..8e67251f06d
--- /dev/null
+++ b/changelogs/unreleased/nfriend-turn-off-ref-selector-autocomplete.yml
@@ -0,0 +1,5 @@
+---
+title: Turn off native autocomplete for ref selector components
+merge_request: 56128
+author:
+type: fixed
diff --git a/changelogs/unreleased/user-access-role-badge-component.yml b/changelogs/unreleased/user-access-role-badge-component.yml
new file mode 100644
index 00000000000..bad431dd9fb
--- /dev/null
+++ b/changelogs/unreleased/user-access-role-badge-component.yml
@@ -0,0 +1,5 @@
+---
+title: Improve styling of user access role badges
+merge_request: 56061
+author:
+type: changed
diff --git a/config/feature_categories.yml b/config/feature_categories.yml
index 0bcd28014ca..915c40fdbbc 100644
--- a/config/feature_categories.yml
+++ b/config/feature_categories.yml
@@ -15,8 +15,10 @@
- audit_reports
- authentication_and_authorization
- auto_devops
+- auto_portfolio_mgmt
- backup_restore
- boards
+- browser_performance
- chatops
- cloud_native_installation
- cluster_cost_management
@@ -32,17 +34,18 @@
- continuous_delivery
- continuous_integration
- database
+- dataops
- dependency_firewall
- dependency_proxy
- dependency_scanning
- design_management
- devops_reports
- disaster_recovery
-- dora_metrics
- dynamic_application_security_testing
- editor_extension
- epics
- error_tracking
+- experimentation
- feature_flags
- five_minute_production_app
- foundations
@@ -61,6 +64,7 @@
- insider_threat
- insights
- integrations
+- intel_code_security
- interactive_application_security_testing
- internationalization
- issue_tracking
@@ -79,6 +83,7 @@
- mobile_signing_deployment
- navigation
- omnibus_package
+- onboarding
- package_registry
- pages
- pipeline_authoring
@@ -120,5 +125,4 @@
- vulnerability_management
- web_firewall
- web_ide
-- web_performance
- wiki
diff --git a/config/feature_flags/development/saas_add_seats_button.yml b/config/feature_flags/development/boards_filtered_search.yml
index 1817481439a..bfb4ae46414 100644
--- a/config/feature_flags/development/saas_add_seats_button.yml
+++ b/config/feature_flags/development/boards_filtered_search.yml
@@ -1,8 +1,8 @@
---
-name: saas_add_seats_button
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49242
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/291060
-milestone: '13.7'
+name: boards_filtered_search
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54641
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/322778
+milestone: '13.10'
type: development
-group: group::purchase
+group: group::project management
default_enabled: false
diff --git a/doc/.vale/gitlab/Admin.yml b/doc/.vale/gitlab/Admin.yml
index d74621bde8e..560752594d5 100644
--- a/doc/.vale/gitlab/Admin.yml
+++ b/doc/.vale/gitlab/Admin.yml
@@ -6,7 +6,7 @@
# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: substitution
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
ignorecase: true
swap:
diff --git a/doc/.vale/gitlab/AlertBoxStyle.yml b/doc/.vale/gitlab/AlertBoxStyle.yml
index 92c3a16cab8..5912c4707fd 100644
--- a/doc/.vale/gitlab/AlertBoxStyle.yml
+++ b/doc/.vale/gitlab/AlertBoxStyle.yml
@@ -10,7 +10,7 @@
# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: existence
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
nonword: true
scope: raw
diff --git a/doc/.vale/gitlab/RelativeLinksDoubleSlashes.yml b/doc/.vale/gitlab/RelativeLinksDoubleSlashes.yml
new file mode 100644
index 00000000000..ce6ce8b5691
--- /dev/null
+++ b/doc/.vale/gitlab/RelativeLinksDoubleSlashes.yml
@@ -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:
+ - '\.//'
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index e7b61af8761..0fa5164bcb6 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -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. |
| `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`. |
-| `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. |
| `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`. |
diff --git a/doc/administration/troubleshooting/elasticsearch.md b/doc/administration/troubleshooting/elasticsearch.md
index 2fe3e5ee654..606697b5247 100644
--- a/doc/administration/troubleshooting/elasticsearch.md
+++ b/doc/administration/troubleshooting/elasticsearch.md
@@ -36,7 +36,7 @@ The type of problem will determine what steps to take. The possible troubleshoot
- Indexing.
- Integration.
- Performance.
-- Background Migrations.
+- Advanced Search Migrations.
### Search Results workflow
@@ -148,7 +148,7 @@ graph TD;
F7(Escalate to<br>GitLab support.)
```
-### Background Migrations workflow
+### Advanced Search Migrations workflow
```mermaid
graph TD;
@@ -180,7 +180,7 @@ Most Elasticsearch troubleshooting can be broken down into 4 categories:
- [Troubleshooting indexing](#troubleshooting-indexing)
- [Troubleshooting integration](#troubleshooting-integration)
- [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:
@@ -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
Elasticsearch administrator has more experience with.
-### Troubleshooting background migrations
+### Troubleshooting Advanced Search migrations
-Troubleshooting background migration failures can be difficult and may require contacting
-an Elasticsearch administrator or GitLab Support.
+Troubleshooting Advanced Search migration failures can be difficult and may
+require contacting an Elasticsearch administrator or GitLab Support.
-The best place to start while debugging issues with a background migration is the
-[`elasticsearch.log` file](../logs.md#elasticsearchlog). Migrations will
-print information while a migration is in progress and any errors encountered.
-Apply fixes for any errors found in the log and retry the migration.
+The best place to start while debugging issues with an Advanced Search
+migration is the [`elasticsearch.log` file](../logs.md#elasticsearchlog).
+Migrations will log information while a migration is in progress and any
+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.
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
deleted file mode 100644
index ade0b89a92c..00000000000
--- a/doc/development/documentation/styleguide.md
+++ /dev/null
@@ -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 -->
diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md
index e076d305251..3f14ca454fe 100644
--- a/doc/development/elasticsearch.md
+++ b/doc/development/elasticsearch.md
@@ -249,12 +249,12 @@ end
### 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).
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
-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).
### 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
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:
@@ -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.
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
### Prometheus
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index 01477c33011..5c9149ef49b 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -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. |
| `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). |
-| `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. |
| `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. |
@@ -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**.
-## Background migrations
+## Advanced Search migrations
> [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
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
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
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
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.
- 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.
-- 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.
- 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
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
+```
diff --git a/doc/update/index.md b/doc/update/index.md
index 9871d0aba2c..555c30136de 100644
--- a/doc/update/index.md
+++ b/doc/update/index.md
@@ -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) }
```
-## Checking for pending Elasticsearch migrations
+## Checking for pending Advanced Search migrations
This section is only applicable if you have enabled the [Elasticsearch
integration](../integration/elasticsearch.md).
-Certain major releases might require [Elasticsearch
-migrations](../integration/elasticsearch.md#background-migrations) to be
-finished. You can find pending migrations by running the following command:
+Major releases require all [Advanced Search
+migrations](../integration/elasticsearch.md#advanced-search-migrations) to
+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**
@@ -160,7 +162,7 @@ cd /home/git/gitlab
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
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,
[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
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).
diff --git a/doc/user/admin_area/analytics/instance_statistics.md b/doc/user/admin_area/analytics/instance_statistics.md
deleted file mode 100644
index e44dd891029..00000000000
--- a/doc/user/admin_area/analytics/instance_statistics.md
+++ /dev/null
@@ -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 -->
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 8e34fb17c8e..9a50dcb6abd 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -1322,7 +1322,7 @@ Examples:
<!--
The "2." and "4." in the example above are changed to "1." below, to match the style
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
@@ -1356,7 +1356,7 @@ They can even:
<!--
The "*" and "+" in the example above are changed to "-" below, to match the style
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:
diff --git a/doc/user/search/advanced_global_search.md b/doc/user/search/advanced_global_search.md
deleted file mode 100644
index 44af6f90cd9..00000000000
--- a/doc/user/search/advanced_global_search.md
+++ /dev/null
@@ -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 -->
diff --git a/doc/user/search/advanced_search_syntax.md b/doc/user/search/advanced_search_syntax.md
deleted file mode 100644
index 44af6f90cd9..00000000000
--- a/doc/user/search/advanced_search_syntax.md
+++ /dev/null
@@ -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 -->
diff --git a/lib/gitlab.rb b/lib/gitlab.rb
index ebf2cdd875a..11ecfb951aa 100644
--- a/lib/gitlab.rb
+++ b/lib/gitlab.rb
@@ -19,6 +19,10 @@ module Gitlab
Settings
end
+ def self.host_with_port
+ "#{self.config.gitlab.host}:#{self.config.gitlab.port}"
+ end
+
def self.revision
@_revision ||= begin
if File.exist?(root.join("REVISION"))
diff --git a/lib/gitlab/ci/build/credentials/registry/dependency_proxy.rb b/lib/gitlab/ci/build/credentials/registry/dependency_proxy.rb
index b6ac06cfb53..76eec2172b1 100644
--- a/lib/gitlab/ci/build/credentials/registry/dependency_proxy.rb
+++ b/lib/gitlab/ci/build/credentials/registry/dependency_proxy.rb
@@ -7,7 +7,7 @@ module Gitlab
module Registry
class DependencyProxy < GitlabRegistry
def url
- "#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}"
+ Gitlab.host_with_port
end
def valid?
diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh
index 17c8cdaabec..c94faa8f6f4 100755
--- a/scripts/lint-doc.sh
+++ b/scripts/lint-doc.sh
@@ -78,7 +78,7 @@ then
echo
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 ' 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
((ERRORCODE++))
fi
diff --git a/spec/frontend/boards/board_card_inner_spec.js b/spec/frontend/boards/board_card_inner_spec.js
index ae5bbfd825f..4487fc15de6 100644
--- a/spec/frontend/boards/board_card_inner_spec.js
+++ b/spec/frontend/boards/board_card_inner_spec.js
@@ -42,7 +42,6 @@ describe('Board card component', () => {
GlLabel: true,
},
provide: {
- groupId: null,
rootPath: '/',
scopedLabelsAvailable: false,
},
diff --git a/spec/frontend/boards/board_list_spec.js b/spec/frontend/boards/board_list_spec.js
index d8fc69206f6..005df76b4f1 100644
--- a/spec/frontend/boards/board_list_spec.js
+++ b/spec/frontend/boards/board_list_spec.js
@@ -19,7 +19,11 @@ const createStore = (state = defaultState) => {
return new Vuex.Store({
state,
actions,
- getters: { isEpicBoard: () => false },
+ getters: {
+ isGroupBoard: () => false,
+ isProjectBoard: () => true,
+ isEpicBoard: () => false,
+ },
});
};
diff --git a/spec/frontend/boards/board_new_issue_deprecated_spec.js b/spec/frontend/boards/board_new_issue_deprecated_spec.js
index 1a29f680166..3903ad201b2 100644
--- a/spec/frontend/boards/board_new_issue_deprecated_spec.js
+++ b/spec/frontend/boards/board_new_issue_deprecated_spec.js
@@ -3,6 +3,7 @@
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
+import Vuex from 'vuex';
import boardNewIssue from '~/boards/components/board_new_issue_deprecated.vue';
import boardsStore from '~/boards/stores/boards_store';
import axios from '~/lib/utils/axios_utils';
@@ -10,6 +11,8 @@ import axios from '~/lib/utils/axios_utils';
import '~/boards/models/list';
import { listObj, boardsMockInterceptor } from './mock_data';
+Vue.use(Vuex);
+
describe('Issue boards new issue form', () => {
let wrapper;
let vm;
@@ -43,11 +46,16 @@ describe('Issue boards new issue form', () => {
newIssueMock = Promise.resolve(promiseReturn);
jest.spyOn(list, 'newIssue').mockImplementation(() => newIssueMock);
+ const store = new Vuex.Store({
+ getters: { isGroupBoard: () => false },
+ });
+
wrapper = mount(BoardNewIssueComp, {
propsData: {
disabled: false,
list,
},
+ store,
provide: {
groupId: null,
},
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index 858efea99ad..32499bd5480 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -8,6 +8,7 @@ import { formType } from '~/boards/constants';
import createBoardMutation from '~/boards/graphql/board_create.mutation.graphql';
import destroyBoardMutation from '~/boards/graphql/board_destroy.mutation.graphql';
import updateBoardMutation from '~/boards/graphql/board_update.mutation.graphql';
+import { createStore } from '~/boards/stores';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { visitUrl } from '~/lib/utils/url_utility';
@@ -48,6 +49,13 @@ describe('BoardForm', () => {
const findDeleteConfirmation = () => wrapper.find('[data-testid="delete-confirmation-message"]');
const findInput = () => wrapper.find('#board-new-name');
+ const store = createStore({
+ getters: {
+ isGroupBoard: () => true,
+ isProjectBoard: () => false,
+ },
+ });
+
const createComponent = (props, data) => {
wrapper = shallowMount(BoardForm, {
propsData: { ...defaultProps, ...props },
@@ -64,6 +72,7 @@ describe('BoardForm', () => {
mutate,
},
},
+ store,
attachTo: document.body,
});
};
diff --git a/spec/frontend/boards/components/board_new_issue_spec.js b/spec/frontend/boards/components/board_new_issue_spec.js
index ce8c95527e9..737a18294bc 100644
--- a/spec/frontend/boards/components/board_new_issue_spec.js
+++ b/spec/frontend/boards/components/board_new_issue_spec.js
@@ -2,7 +2,6 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import BoardNewIssue from '~/boards/components/board_new_issue.vue';
-import '~/boards/models/list';
import { mockList, mockGroupProjects } from '../mock_data';
const localVue = createLocalVue();
@@ -31,7 +30,7 @@ describe('Issue boards new issue form', () => {
const store = new Vuex.Store({
state: { selectedProject: mockGroupProjects[0] },
actions: { addListNewIssue: addListNewIssuesSpy },
- getters: {},
+ getters: { isGroupBoard: () => false, isProjectBoard: () => true },
});
wrapper = shallowMount(BoardNewIssue, {
diff --git a/spec/frontend/boards/components/filtered_search_spec.js b/spec/frontend/boards/components/filtered_search_spec.js
new file mode 100644
index 00000000000..7f238aa671f
--- /dev/null
+++ b/spec/frontend/boards/components/filtered_search_spec.js
@@ -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',
+ );
+ });
+ });
+ });
+});
diff --git a/spec/frontend/boards/stores/getters_spec.js b/spec/frontend/boards/stores/getters_spec.js
index d030b34ef80..c0c19e9d797 100644
--- a/spec/frontend/boards/stores/getters_spec.js
+++ b/spec/frontend/boards/stores/getters_spec.js
@@ -10,6 +10,42 @@ import {
} from '../mock_data';
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', () => {
it('returns true when activeId is not equal to 0', () => {
const state = {
diff --git a/spec/frontend/fixtures/issues.rb b/spec/frontend/fixtures/issues.rb
index a027247bd0d..d6f6ed97626 100644
--- a/spec/frontend/fixtures/issues.rb
+++ b/spec/frontend/fixtures/issues.rb
@@ -16,6 +16,8 @@ RSpec.describe Projects::IssuesController, '(JavaScript fixtures)', type: :contr
end
before do
+ stub_feature_flags(boards_filtered_search: false)
+
project.add_maintainer(user)
sign_in(user)
end
diff --git a/spec/frontend/groups/components/group_item_spec.js b/spec/frontend/groups/components/group_item_spec.js
index 4fcc9bafa46..5a9f640392f 100644
--- a/spec/frontend/groups/components/group_item_spec.js
+++ b/spec/frontend/groups/components/group_item_spec.js
@@ -188,7 +188,7 @@ describe('GroupItemComponent', () => {
});
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.classList.contains('group-row')).toBeTruthy();
@@ -209,8 +209,7 @@ describe('GroupItemComponent', () => {
expect(vm.$el.querySelector('.title a.no-expand')).toBeDefined();
expect(visibilityIconEl).not.toBe(null);
- expect(visibilityIconEl.title).toBe(vm.visibilityTooltip);
- expect(visibilityIconEl.querySelectorAll('svg').length).toBeGreaterThan(0);
+ expect(visibilityIconEl.getAttribute('title')).toBe(vm.visibilityTooltip);
expect(vm.$el.querySelector('.access-type')).toBeDefined();
expect(vm.$el.querySelector('.description')).toBeDefined();
diff --git a/spec/frontend/notes/components/note_actions_spec.js b/spec/frontend/notes/components/note_actions_spec.js
index 17717ebd09a..f56be1a9af7 100644
--- a/spec/frontend/notes/components/note_actions_spec.js
+++ b/spec/frontend/notes/components/note_actions_spec.js
@@ -6,6 +6,7 @@ import axios from '~/lib/utils/axios_utils';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import noteActions from '~/notes/components/note_actions.vue';
import createStore from '~/notes/stores';
+import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
import { userDataMock } from '../mock_data';
describe('noteActions', () => {
@@ -15,6 +16,9 @@ describe('noteActions', () => {
let actions;
let axiosMock;
+ const findUserAccessRoleBadge = (idx) => wrapper.findAll(UserAccessRoleBadge).at(idx);
+ const findUserAccessRoleBadgeText = (idx) => findUserAccessRoleBadge(idx).text().trim();
+
const mountNoteActions = (propsData, computed) => {
const localVue = createLocalVue();
return mount(localVue.extend(noteActions), {
@@ -66,11 +70,11 @@ describe('noteActions', () => {
});
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', () => {
- expect(wrapper.findAll('.note-role').at(1).text().trim()).toEqual(props.accessLevel);
+ expect(findUserAccessRoleBadgeText(1)).toBe(props.accessLevel);
});
it('should render contributor badge', () => {
@@ -80,7 +84,7 @@ describe('noteActions', () => {
});
return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.findAll('.note-role').at(1).text().trim()).toBe('Contributor');
+ expect(findUserAccessRoleBadgeText(1)).toBe('Contributor');
});
});
diff --git a/spec/frontend/vue_shared/components/user_access_role_badge_spec.js b/spec/frontend/vue_shared/components/user_access_role_badge_spec.js
new file mode 100644
index 00000000000..7f25f7c08e7
--- /dev/null
+++ b/spec/frontend/vue_shared/components/user_access_role_badge_spec.js
@@ -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');
+ });
+});
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index fa01d4e48df..e076815c4f6 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -166,7 +166,7 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
let(:ports) { Project::VALID_IMPORT_PORTS }
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
end
@@ -190,7 +190,7 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
end
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
ssh_url = "javascript://#{Gitlab.config.gitlab_shell.ssh_host}:#{Gitlab.config.gitlab_shell.ssh_port}/t.git%0aalert(1)"
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index e18edd1ee8f..15f537f9055 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -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_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_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',
- 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,
masked: false },
{ key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true, masked: false },
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index ddda3ede083..87bb3ea842c 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -246,6 +246,12 @@ RSpec.configure do |config|
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)
else
unstub_all_feature_flags