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:
-rw-r--r--app/assets/javascripts/boards/components/board_app.vue3
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue4
-rw-r--r--app/assets/javascripts/boards/components/board_top_bar.vue54
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue34
-rw-r--r--app/assets/javascripts/boards/components/config_toggle.vue11
-rw-r--r--app/assets/javascripts/boards/components/issue_board_filtered_search.vue12
-rw-r--r--app/assets/javascripts/boards/components/toggle_focus.vue8
-rw-r--r--app/assets/javascripts/boards/index.js91
-rw-r--r--app/helpers/boards_helper.rb16
-rw-r--r--app/services/loose_foreign_keys/process_deleted_records_service.rb2
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml9
-rw-r--r--app/views/shared/boards/_show.html.haml2
-rw-r--r--config/feature_flags/development/admin_application_settings_service_usage_data_center.yml8
-rw-r--r--doc/api/groups.md58
-rw-r--r--doc/api/secure_files.md3
-rw-r--r--doc/ci/yaml/index.md6
-rw-r--r--doc/development/service_ping/index.md3
-rw-r--r--doc/user/permissions.md4
-rw-r--r--lib/tasks/dev.rake17
-rw-r--r--qa/qa/resource/project.rb7
-rw-r--r--qa/qa/resource/runner.rb14
-rw-r--r--qa/qa/specs/features/api/4_verify/remove_runner_spec.rb14
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb7
-rw-r--r--spec/frontend/boards/components/board_form_spec.js2
-rw-r--r--spec/frontend/boards/components/board_top_bar_spec.js88
-rw-r--r--spec/frontend/boards/components/boards_selector_spec.js8
-rw-r--r--spec/frontend/boards/components/issue_board_filtered_search_spec.js3
-rw-r--r--spec/helpers/boards_helper_spec.rb17
-rw-r--r--spec/tasks/dev_rake_spec.rb33
29 files changed, 350 insertions, 188 deletions
diff --git a/app/assets/javascripts/boards/components/board_app.vue b/app/assets/javascripts/boards/components/board_app.vue
index 28f4a267077..858aabb0f05 100644
--- a/app/assets/javascripts/boards/components/board_app.vue
+++ b/app/assets/javascripts/boards/components/board_app.vue
@@ -2,11 +2,13 @@
import { mapActions, mapGetters } from 'vuex';
import BoardContent from '~/boards/components/board_content.vue';
import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue';
+import BoardTopBar from '~/boards/components/board_top_bar.vue';
export default {
components: {
BoardContent,
BoardSettingsSidebar,
+ BoardTopBar,
},
inject: ['disabled'],
computed: {
@@ -23,6 +25,7 @@ export default {
<template>
<div class="boards-app gl-relative" :class="{ 'is-compact': isSidebarOpen }">
+ <board-top-bar />
<board-content :disabled="disabled" />
<board-settings-sidebar />
</div>
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 5fcf9514708..aba1f42ead5 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -48,7 +48,7 @@ export default {
fullPath: {
default: '',
},
- rootPath: {
+ boardBaseUrl: {
default: '',
},
},
@@ -209,7 +209,7 @@ export default {
if (this.isDeleteForm) {
try {
await this.deleteBoard();
- visitUrl(this.rootPath);
+ visitUrl(this.boardBaseUrl);
} catch {
this.setError({ message: this.$options.i18n.deleteErrorMessage });
} finally {
diff --git a/app/assets/javascripts/boards/components/board_top_bar.vue b/app/assets/javascripts/boards/components/board_top_bar.vue
new file mode 100644
index 00000000000..f90ac1e9079
--- /dev/null
+++ b/app/assets/javascripts/boards/components/board_top_bar.vue
@@ -0,0 +1,54 @@
+<script>
+import { mapGetters } from 'vuex';
+import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_trigger.vue';
+import BoardsSelector from 'ee_else_ce/boards/components/boards_selector.vue';
+import IssueBoardFilteredSearch from 'ee_else_ce/boards/components/issue_board_filtered_search.vue';
+import ConfigToggle from './config_toggle.vue';
+import NewBoardButton from './new_board_button.vue';
+import ToggleFocus from './toggle_focus.vue';
+
+export default {
+ components: {
+ BoardAddNewColumnTrigger,
+ BoardsSelector,
+ IssueBoardFilteredSearch,
+ ConfigToggle,
+ NewBoardButton,
+ ToggleFocus,
+ ToggleLabels: () => import('ee_component/boards/components/toggle_labels.vue'),
+ ToggleEpicsSwimlanes: () => import('ee_component/boards/components/toggle_epics_swimlanes.vue'),
+ EpicBoardFilteredSearch: () =>
+ import('ee_component/boards/components/epic_filtered_search.vue'),
+ },
+ inject: ['swimlanesFeatureAvailable', 'canAdminList', 'isSignedIn'],
+ computed: {
+ ...mapGetters(['isEpicBoard']),
+ },
+};
+</script>
+
+<template>
+ <div class="issues-filters">
+ <div
+ class="issues-details-filters filtered-search-block gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row row-content-block second-block"
+ >
+ <div
+ class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-flex-grow-1 gl-lg-mb-0! mb-md-2 mb-sm-0 gl-w-full"
+ >
+ <boards-selector />
+ <new-board-button />
+ <epic-board-filtered-search v-if="isEpicBoard" />
+ <issue-board-filtered-search v-else />
+ </div>
+ <div
+ class="filter-dropdown-container gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-align-items-flex-start"
+ >
+ <toggle-labels />
+ <toggle-epics-swimlanes v-if="swimlanesFeatureAvailable && isSignedIn" />
+ <config-toggle />
+ <board-add-new-column-trigger v-if="canAdminList" />
+ <toggle-focus />
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index 91fdfd668fc..8333856e996 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -40,37 +40,21 @@ export default {
directives: {
GlModalDirective,
},
- inject: ['fullPath'],
+ inject: [
+ 'boardBaseUrl',
+ 'fullPath',
+ 'canAdminBoard',
+ 'multipleIssueBoardsAvailable',
+ 'hasMissingBoards',
+ 'scopedIssueBoardFeatureEnabled',
+ 'weights',
+ ],
props: {
throttleDuration: {
type: Number,
default: 200,
required: false,
},
- boardBaseUrl: {
- type: String,
- required: true,
- },
- hasMissingBoards: {
- type: Boolean,
- required: true,
- },
- canAdminBoard: {
- type: Boolean,
- required: true,
- },
- multipleIssueBoardsAvailable: {
- type: Boolean,
- required: true,
- },
- scopedIssueBoardFeatureEnabled: {
- type: Boolean,
- required: true,
- },
- weights: {
- type: Array,
- required: true,
- },
},
data() {
return {
diff --git a/app/assets/javascripts/boards/components/config_toggle.vue b/app/assets/javascripts/boards/components/config_toggle.vue
index f39e4d90357..4746f598ab7 100644
--- a/app/assets/javascripts/boards/components/config_toggle.vue
+++ b/app/assets/javascripts/boards/components/config_toggle.vue
@@ -14,16 +14,7 @@ export default {
GlModalDirective,
},
mixins: [Tracking.mixin()],
- props: {
- canAdminList: {
- type: Boolean,
- required: true,
- },
- hasScope: {
- type: Boolean,
- required: true,
- },
- },
+ inject: ['canAdminList', 'hasScope'],
computed: {
buttonText() {
return this.canAdminList ? s__('Boards|Edit board') : s__('Boards|View scope');
diff --git a/app/assets/javascripts/boards/components/issue_board_filtered_search.vue b/app/assets/javascripts/boards/components/issue_board_filtered_search.vue
index 6bfdbb674a2..bab6fe26978 100644
--- a/app/assets/javascripts/boards/components/issue_board_filtered_search.vue
+++ b/app/assets/javascripts/boards/components/issue_board_filtered_search.vue
@@ -41,17 +41,7 @@ export default {
confidential: __('Confidential'),
},
components: { BoardFilteredSearch },
- inject: ['isSignedIn', 'releasesFetchPath'],
- props: {
- fullPath: {
- type: String,
- required: true,
- },
- boardType: {
- type: String,
- required: true,
- },
- },
+ inject: ['isSignedIn', 'releasesFetchPath', 'fullPath', 'boardType'],
computed: {
isGroupBoard() {
return this.boardType === BoardType.group;
diff --git a/app/assets/javascripts/boards/components/toggle_focus.vue b/app/assets/javascripts/boards/components/toggle_focus.vue
index 49f5e7d20a9..0169c9c72ed 100644
--- a/app/assets/javascripts/boards/components/toggle_focus.vue
+++ b/app/assets/javascripts/boards/components/toggle_focus.vue
@@ -10,12 +10,6 @@ export default {
directives: {
GlTooltip,
},
- props: {
- issueBoardsContentSelector: {
- type: String,
- required: true,
- },
- },
data() {
return {
isFullscreen: false,
@@ -25,7 +19,7 @@ export default {
toggleFocusMode() {
hide(this.$refs.toggleFocusModeButton);
- const issueBoardsContent = document.querySelector(this.issueBoardsContentSelector);
+ const issueBoardsContent = document.querySelector('.content-wrapper > .js-focus-mode-board');
issueBoardsContent.classList.toggle('is-focused');
this.isFullscreen = !this.isFullscreen;
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index b31b56e6839..2abc283c5b4 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -1,22 +1,20 @@
import PortalVue from 'portal-vue';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-
-import toggleEpicsSwimlanes from 'ee_else_ce/boards/toggle_epics_swimlanes';
-import toggleLabels from 'ee_else_ce/boards/toggle_labels';
-import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_trigger.vue';
import BoardApp from '~/boards/components/board_app.vue';
import '~/boards/filters/due_date_filters';
import { issuableTypes } from '~/boards/constants';
-import initBoardsFilteredSearch from '~/boards/mount_filtered_search_issue_boards';
import store from '~/boards/stores';
-import toggleFocusMode from '~/boards/toggle_focus';
-import { NavigationType, isLoggedIn, parseBoolean } from '~/lib/utils/common_utils';
+import {
+ NavigationType,
+ isLoggedIn,
+ parseBoolean,
+ convertObjectPropsToCamelCase,
+} from '~/lib/utils/common_utils';
+import { queryToObject } from '~/lib/utils/url_utility';
import { fullBoardId } from './boards_util';
-import boardConfigToggle from './config_toggle';
import initNewBoard from './new_board';
import { gqlClient } from './graphql';
-import mountMultipleBoardsSwitcher from './mount_multiple_boards_switcher';
Vue.use(VueApollo);
Vue.use(PortalVue);
@@ -28,6 +26,12 @@ const apolloProvider = new VueApollo({
function mountBoardApp(el) {
const { boardId, groupId, fullPath, rootPath } = el.dataset;
+ const rawFilterParams = queryToObject(window.location.search, { gatherArrays: true });
+
+ const initialFilterParams = {
+ ...convertObjectPropsToCamelCase(rawFilterParams),
+ };
+
store.dispatch('fetchBoard', {
fullPath,
fullBoardId: fullBoardId(boardId),
@@ -54,26 +58,41 @@ function mountBoardApp(el) {
boardId,
groupId: Number(groupId),
rootPath,
+ fullPath,
+ initialFilterParams,
+ boardBaseUrl: el.dataset.boardBaseUrl,
+ boardType: el.dataset.parent,
currentUserId: gon.current_user_id || null,
- canUpdate: parseBoolean(el.dataset.canUpdate),
- canAdminList: parseBoolean(el.dataset.canAdminList),
+ boardWeight: el.dataset.boardWeight ? parseInt(el.dataset.boardWeight, 10) : null,
labelsManagePath: el.dataset.labelsManagePath,
labelsFilterBasePath: el.dataset.labelsFilterBasePath,
+ releasesFetchPath: el.dataset.releasesFetchPath,
timeTrackingLimitToHours: parseBoolean(el.dataset.timeTrackingLimitToHours),
+ issuableType: issuableTypes.issue,
+ emailsDisabled: parseBoolean(el.dataset.emailsDisabled),
+ hasScope: parseBoolean(el.dataset.hasScope),
+ hasMissingBoards: parseBoolean(el.dataset.hasMissingBoards),
+ weights: el.dataset.weights ? JSON.parse(el.dataset.weights) : [],
+ // Permissions
+ canUpdate: parseBoolean(el.dataset.canUpdate),
+ canAdminList: parseBoolean(el.dataset.canAdminList),
+ canAdminBoard: parseBoolean(el.dataset.canAdminBoard),
+ allowLabelCreate: parseBoolean(el.dataset.canUpdate),
+ allowLabelEdit: parseBoolean(el.dataset.canUpdate),
+ isSignedIn: isLoggedIn(),
+ // Features
multipleAssigneesFeatureAvailable: parseBoolean(el.dataset.multipleAssigneesFeatureAvailable),
epicFeatureAvailable: parseBoolean(el.dataset.epicFeatureAvailable),
iterationFeatureAvailable: parseBoolean(el.dataset.iterationFeatureAvailable),
weightFeatureAvailable: parseBoolean(el.dataset.weightFeatureAvailable),
- boardWeight: el.dataset.boardWeight ? parseInt(el.dataset.boardWeight, 10) : null,
scopedLabelsAvailable: parseBoolean(el.dataset.scopedLabels),
milestoneListsAvailable: parseBoolean(el.dataset.milestoneListsAvailable),
assigneeListsAvailable: parseBoolean(el.dataset.assigneeListsAvailable),
iterationListsAvailable: parseBoolean(el.dataset.iterationListsAvailable),
- issuableType: issuableTypes.issue,
- emailsDisabled: parseBoolean(el.dataset.emailsDisabled),
- allowLabelCreate: parseBoolean(el.dataset.canUpdate),
- allowLabelEdit: parseBoolean(el.dataset.canUpdate),
allowScopedLabels: parseBoolean(el.dataset.scopedLabels),
+ swimlanesFeatureAvailable: gon.licensed_features?.swimlanes,
+ multipleIssueBoardsAvailable: parseBoolean(el.dataset.multipleBoardsAvailable),
+ scopedIssueBoardFeatureEnabled: parseBoolean(el.dataset.scopedIssueBoardFeatureEnabled),
},
render: (createComponent) => createComponent(BoardApp),
});
@@ -92,47 +111,7 @@ export default () => {
}
});
- const { releasesFetchPath, epicFeatureAvailable, iterationFeatureAvailable } = $boardApp.dataset;
- initBoardsFilteredSearch(
- apolloProvider,
- isLoggedIn(),
- releasesFetchPath,
- parseBoolean(epicFeatureAvailable),
- parseBoolean(iterationFeatureAvailable),
- );
-
mountBoardApp($boardApp);
- const createColumnTriggerEl = document.querySelector('.js-create-column-trigger');
- if (createColumnTriggerEl) {
- // eslint-disable-next-line no-new
- new Vue({
- el: createColumnTriggerEl,
- name: 'BoardAddNewColumnTriggerRoot',
- components: {
- BoardAddNewColumnTrigger,
- },
- store,
- render(createElement) {
- return createElement('board-add-new-column-trigger');
- },
- });
- }
-
- boardConfigToggle();
initNewBoard();
-
- toggleFocusMode();
- toggleLabels();
-
- if (gon.licensed_features?.swimlanes) {
- toggleEpicsSwimlanes();
- }
-
- mountMultipleBoardsSwitcher({
- fullPath: $boardApp.dataset.fullPath,
- rootPath: $boardApp.dataset.boardsEndpoint,
- allowScopedLabels: $boardApp.dataset.scopedLabels,
- labelsManagePath: $boardApp.dataset.labelsManagePath,
- });
};
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index 28cd61e10d9..7b4d87a77e4 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -16,6 +16,7 @@ module BoardsHelper
bulk_update_path: @bulk_issues_path,
can_update: can_update?.to_s,
can_admin_list: can_admin_list?.to_s,
+ can_admin_board: can_admin_board?.to_s,
time_tracking_limit_to_hours: Gitlab::CurrentSettings.time_tracking_limit_to_hours.to_s,
parent: current_board_parent.model_name.param_key,
group_id: group_id,
@@ -23,7 +24,11 @@ module BoardsHelper
labels_fetch_path: labels_fetch_path,
labels_manage_path: labels_manage_path,
releases_fetch_path: releases_fetch_path,
- board_type: board.to_type
+ board_type: board.to_type,
+ has_scope: board.scoped?.to_s,
+ has_missing_boards: has_missing_boards?.to_s,
+ multiple_boards_available: multiple_boards_available?.to_s,
+ board_base_url: board_base_url
}
end
@@ -85,6 +90,11 @@ module BoardsHelper
current_board_parent.multiple_issue_boards_available?
end
+ # Boards are hidden when extra boards were created but the license does not allow multiple boards
+ def has_missing_boards?
+ !multiple_boards_available? && current_board_parent.boards.size > 1
+ end
+
def current_board_path(board)
@current_board_path ||= if board.group_board?
group_board_path(current_board_parent, board)
@@ -109,6 +119,10 @@ module BoardsHelper
can?(current_user, :admin_issue_board_list, current_board_parent)
end
+ def can_admin_board?
+ can?(current_user, :admin_issue_board, current_board_parent)
+ end
+
def can_admin_issue?
can?(current_user, :admin_issue, current_board_parent)
end
diff --git a/app/services/loose_foreign_keys/process_deleted_records_service.rb b/app/services/loose_foreign_keys/process_deleted_records_service.rb
index 2826bdb4c3c..54f54d99afb 100644
--- a/app/services/loose_foreign_keys/process_deleted_records_service.rb
+++ b/app/services/loose_foreign_keys/process_deleted_records_service.rb
@@ -52,7 +52,7 @@ module LooseForeignKeys
end
def tracked_tables
- @tracked_tables ||= Gitlab::Database::LooseForeignKeys.definitions_by_table.keys
+ @tracked_tables ||= Gitlab::Database::LooseForeignKeys.definitions_by_table.keys.shuffle
end
end
end
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index 9bac0f04d23..94c708783e4 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -269,11 +269,10 @@
= link_to metrics_and_profiling_admin_application_settings_path, title: _('Metrics and profiling'), class: 'qa-admin-settings-metrics-and-profiling-item' do
%span
= _('Metrics and profiling')
- - if Feature.enabled?(:admin_application_settings_service_usage_data_center, default_enabled: :yaml)
- = nav_link(path: ['application_settings#service_usage_data']) do
- = link_to service_usage_data_admin_application_settings_path, title: _('Service usage data') do
- %span
- = _('Service usage data')
+ = nav_link(path: ['application_settings#service_usage_data']) do
+ = link_to service_usage_data_admin_application_settings_path, title: _('Service usage data') do
+ %span
+ = _('Service usage data')
= nav_link(path: 'application_settings#network') do
= link_to network_admin_application_settings_path, title: _('Network'), data: { qa_selector: 'admin_settings_network_item' } do
%span
diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index 98752345074..c070baf02b1 100644
--- a/app/views/shared/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -16,6 +16,4 @@
- page_title("#{board.name}", _("Boards"))
- add_page_specific_style 'page_bundles/boards'
-= render 'shared/issuable/search_bar', type: :boards, board: board
-
#js-issuable-board-app{ data: board_data }
diff --git a/config/feature_flags/development/admin_application_settings_service_usage_data_center.yml b/config/feature_flags/development/admin_application_settings_service_usage_data_center.yml
deleted file mode 100644
index 465f9061eb4..00000000000
--- a/config/feature_flags/development/admin_application_settings_service_usage_data_center.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: admin_application_settings_service_usage_data_center
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78747
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351268
-milestone: '14.8'
-type: development
-group: group::product intelligence
-default_enabled: true
diff --git a/doc/api/groups.md b/doc/api/groups.md
index a4aa2330c29..e381d679478 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -809,24 +809,24 @@ Parameters:
| ------------------------------------------------------- | ------- | -------- | ----------- |
| `name` | string | yes | The name of the group. |
| `path` | string | yes | The path of the group. |
-| `description` | string | no | The group's description. |
-| `membership_lock` **(PREMIUM)** | boolean | no | Prevent adding new members to projects within this group. |
-| `visibility` | string | no | The group's visibility. Can be `private`, `internal`, or `public`. |
-| `share_with_group_lock` | boolean | no | Prevent sharing a project with another group within this group. |
-| `require_two_factor_authentication` | boolean | no | Require all users in this group to setup Two-factor authentication. |
-| `two_factor_grace_period` | integer | no | Time before Two-factor authentication is enforced (in hours). |
-| `project_creation_level` | string | no | Determine if developers can create projects in the group. Can be `noone` (No one), `maintainer` (users with the Maintainer role), or `developer` (users with the Developer or Maintainer role). |
| `auto_devops_enabled` | boolean | no | Default to Auto DevOps pipeline for all projects within this group. |
-| `subgroup_creation_level` | string | no | Allowed to [create subgroups](../user/group/subgroups/index.md#create-a-subgroup). Can be `owner` (Owners), or `maintainer` (users with the Maintainer role). |
-| `emails_disabled` | boolean | no | Disable email notifications |
| `avatar` | mixed | no | Image file for avatar of the group. [Introduced in GitLab 12.9](https://gitlab.com/gitlab-org/gitlab/-/issues/36681) |
-| `mentions_disabled` | boolean | no | Disable the capability of a group from getting mentioned |
+| `default_branch_protection` | integer | no | See [Options for `default_branch_protection`](#options-for-default_branch_protection). Default to the global level default branch protection setting. |
+| `description` | string | no | The group's description. |
+| `emails_disabled` | boolean | no | Disable email notifications. |
| `lfs_enabled` | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group. |
-| `request_access_enabled` | boolean | no | Allow users to request member access. |
+| `mentions_disabled` | boolean | no | Disable the capability of a group from getting mentioned. |
| `parent_id` | integer | no | The parent group ID for creating nested group. |
-| `default_branch_protection` | integer | no | See [Options for `default_branch_protection`](#options-for-default_branch_protection). Default to the global level default branch protection setting. |
-| `shared_runners_minutes_limit` **(PREMIUM)** | integer | no | Can be set by administrators only. Maximum number of monthly CI/CD minutes for this group. Can be `nil` (default; inherit system default), `0` (unlimited), or `> 0`. |
+| `project_creation_level` | string | no | Determine if developers can create projects in the group. Can be `noone` (No one), `maintainer` (users with the Maintainer role), or `developer` (users with the Developer or Maintainer role). |
+| `request_access_enabled` | boolean | no | Allow users to request member access. |
+| `require_two_factor_authentication` | boolean | no | Require all users in this group to setup Two-factor authentication. |
+| `share_with_group_lock` | boolean | no | Prevent sharing a project with another group within this group. |
+| `subgroup_creation_level` | string | no | Allowed to [create subgroups](../user/group/subgroups/index.md#create-a-subgroup). Can be `owner` (Owners), or `maintainer` (users with the Maintainer role). |
+| `two_factor_grace_period` | integer | no | Time before Two-factor authentication is enforced (in hours). |
+| `visibility` | string | no | The group's visibility. Can be `private`, `internal`, or `public`. |
+| `membership_lock` **(PREMIUM)** | boolean | no | Prevent adding new members to projects within this group. |
| `extra_shared_runners_minutes_limit` **(PREMIUM)** | integer | no | Can be set by administrators only. Additional CI/CD minutes for this group. |
+| `shared_runners_minutes_limit` **(PREMIUM)** | integer | no | Can be set by administrators only. Maximum number of monthly CI/CD minutes for this group. Can be `nil` (default; inherit system default), `0` (unlimited), or `> 0`. |
### Options for `default_branch_protection`
@@ -912,27 +912,27 @@ PUT /groups/:id
| `id` | integer | yes | The ID of the group. |
| `name` | string | no | The name of the group. |
| `path` | string | no | The path of the group. |
-| `description` | string | no | The description of the group. |
-| `membership_lock` **(PREMIUM)** | boolean | no | Prevent adding new members to projects within this group. |
-| `share_with_group_lock` | boolean | no | Prevent sharing a project with another group within this group. |
-| `visibility` | string | no | The visibility level of the group. Can be `private`, `internal`, or `public`. |
-| `require_two_factor_authentication` | boolean | no | Require all users in this group to setup Two-factor authentication. |
-| `two_factor_grace_period` | integer | no | Time before Two-factor authentication is enforced (in hours). |
-| `project_creation_level` | string | no | Determine if developers can create projects in the group. Can be `noone` (No one), `maintainer` (users with the Maintainer role), or `developer` (users with the Developer or Maintainer role). |
| `auto_devops_enabled` | boolean | no | Default to Auto DevOps pipeline for all projects within this group. |
-| `subgroup_creation_level` | string | no | Allowed to [create subgroups](../user/group/subgroups/index.md#create-a-subgroup). Can be `owner` (Owners), or `maintainer` (users with the Maintainer role). |
-| `emails_disabled` | boolean | no | Disable email notifications |
| `avatar` | mixed | no | Image file for avatar of the group. [Introduced in GitLab 12.9](https://gitlab.com/gitlab-org/gitlab/-/issues/36681) |
-| `mentions_disabled` | boolean | no | Disable the capability of a group from getting mentioned |
-| `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group. |
-| `request_access_enabled` | boolean | no | Allow users to request member access. |
| `default_branch_protection` | integer | no | See [Options for `default_branch_protection`](#options-for-default_branch_protection). |
+| `description` | string | no | The description of the group. |
+| `emails_disabled` | boolean | no | Disable email notifications. |
+| `lfs_enabled` | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group. |
+| `mentions_disabled` | boolean | no | Disable the capability of a group from getting mentioned. |
+| `prevent_sharing_groups_outside_hierarchy` | boolean | no | See [Prevent group sharing outside the group hierarchy](../user/group/index.md#prevent-group-sharing-outside-the-group-hierarchy). This attribute is only available on top-level groups. [Introduced in GitLab 14.1](https://gitlab.com/gitlab-org/gitlab/-/issues/333721) |
+| `project_creation_level` | string | no | Determine if developers can create projects in the group. Can be `noone` (No one), `maintainer` (users with the Maintainer role), or `developer` (users with the Developer or Maintainer role). |
+| `request_access_enabled` | boolean | no | Allow users to request member access. |
+| `require_two_factor_authentication` | boolean | no | Require all users in this group to setup Two-factor authentication. |
+| `shared_runners_setting` | string | no | See [Options for `shared_runners_setting`](#options-for-shared_runners_setting). Enable or disable shared runners for a group's subgroups and projects. |
+| `share_with_group_lock` | boolean | no | Prevent sharing a project with another group within this group. |
+| `subgroup_creation_level` | string | no | Allowed to [create subgroups](../user/group/subgroups/index.md#create-a-subgroup). Can be `owner` (Owners), or `maintainer` (users with the Maintainer role). |
+| `two_factor_grace_period` | integer | no | Time before Two-factor authentication is enforced (in hours). |
+| `visibility` | string | no | The visibility level of the group. Can be `private`, `internal`, or `public`. |
+| `extra_shared_runners_minutes_limit` **(PREMIUM)** | integer | no | Can be set by administrators only. Additional CI/CD minutes for this group. |
| `file_template_project_id` **(PREMIUM)** | integer | no | The ID of a project to load custom file templates from. |
+| `membership_lock` **(PREMIUM)** | boolean | no | Prevent adding new members to projects within this group. |
+| `prevent_forking_outside_group` **(PREMIUM)** | boolean | no | When enabled, users can **not** fork projects from this group to external namespaces. |
| `shared_runners_minutes_limit` **(PREMIUM)** | integer | no | Can be set by administrators only. Maximum number of monthly CI/CD minutes for this group. Can be `nil` (default; inherit system default), `0` (unlimited), or `> 0`. |
-| `extra_shared_runners_minutes_limit` **(PREMIUM)** | integer | no | Can be set by administrators only. Additional CI/CD minutes for this group. |
-| `prevent_forking_outside_group` **(PREMIUM)** | boolean | no | When enabled, users can **not** fork projects from this group to external namespaces
-| `shared_runners_setting` | string | no | See [Options for `shared_runners_setting`](#options-for-shared_runners_setting). Enable or disable shared runners for a group's subgroups and projects. |
-| `prevent_sharing_groups_outside_hierarchy` | boolean | no | See [Prevent group sharing outside the group hierarchy](../user/group/index.md#prevent-group-sharing-outside-the-group-hierarchy). This attribute is only available on top-level groups. [Introduced in GitLab 14.1](https://gitlab.com/gitlab-org/gitlab/-/issues/333721) |
NOTE:
The `projects` and `shared_projects` attributes in the response are deprecated and [scheduled for removal in API v5](https://gitlab.com/gitlab-org/gitlab/-/issues/213797).
diff --git a/doc/api/secure_files.md b/doc/api/secure_files.md
index 826a5de3489..aa3e80a895d 100644
--- a/doc/api/secure_files.md
+++ b/doc/api/secure_files.md
@@ -11,8 +11,7 @@ type: reference, api
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available,
-ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `ci_secure_files`.
-The feature is not ready for production use.
+ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `ci_secure_files`. Limited to 100 secure files per project. The feature is not ready for production use.
## List project secure files
diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md
index a4ea2b38834..fdf0d8f939c 100644
--- a/doc/ci/yaml/index.md
+++ b/doc/ci/yaml/index.md
@@ -2620,9 +2620,11 @@ Multiple runners must exist, or a single runner must be configured to run multip
**Keyword type**: Job keyword. You can use it only as part of a job.
-**Possible inputs**:
+**Possible inputs**: An array of hashes of variables:
-- A numeric value from `2` to `50`.
+- The variable names can use only numbers, letters, and underscores (`_`).
+- The values must be either a string, or an array of strings.
+- The number of permutations cannot exceed 50.
**Example of `parallel:matrix`**:
diff --git a/doc/development/service_ping/index.md b/doc/development/service_ping/index.md
index 974e938da4d..14bb90537e7 100644
--- a/doc/development/service_ping/index.md
+++ b/doc/development/service_ping/index.md
@@ -582,7 +582,8 @@ ServicePing::SubmitService.new(skip_db_write: true).execute
## Manually upload Service Ping payload
-> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/7388) in GitLab 14.8 with a flag named `admin_application_settings_service_usage_data_center`. Disabled by default.
+> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/7388) in GitLab 14.8 with a flag named `admin_application_settings_service_usage_data_center`. Disabled by default.
+> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83265) in GitLab 14.10.
Service Ping payload can be uploaded to GitLab even if your application instance doesn't have access to the internet,
or you don't have Service Ping [cron job](#how-service-ping-works) enabled.
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index ee081bb0fa8..d47b0fcbb75 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -161,7 +161,7 @@ The following table lists project permissions available for each role:
| [Projects](project/index.md):<br>Rename project | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Share (invite) projects with groups | | | | ✓ (*7*) | ✓ (*7*) |
| [Projects](project/index.md):<br>View 2FA status of members | | | | ✓ | ✓ |
-| [Projects](project/index.md):<br>Administer project compliance frameworks | | | | | ✓ |
+| [Projects](project/index.md):<br>Assign project to a compliance framework | | | | | ✓ |
| [Projects](project/index.md):<br>Archive project | | | | | ✓ |
| [Projects](project/index.md):<br>Change project visibility level | | | | | ✓ |
| [Projects](project/index.md):<br>Delete project | | | | | ✓ |
@@ -407,7 +407,7 @@ The following table lists group permissions available for each role:
| List group deploy tokens | | | | ✓ | ✓ |
| Manage [group push rules](group/index.md#group-push-rules) | | | | ✓ | ✓ |
| View/manage group-level Kubernetes cluster | | | | ✓ | ✓ |
-| Administer project compliance frameworks | | | | | ✓ |
+| Create and manage compliance frameworks | | | | | ✓ |
| Create/Delete group deploy tokens | | | | | ✓ |
| Change group visibility level | | | | | ✓ |
| Delete group | | | | | ✓ |
diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake
index 9ac817b9aa0..a9af2afa068 100644
--- a/lib/tasks/dev.rake
+++ b/lib/tasks/dev.rake
@@ -26,4 +26,21 @@ namespace :dev do
Rails.configuration.eager_load = true
Rails.application.eager_load!
end
+
+ databases = ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml
+
+ namespace :copy_db do
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
+ next if name == 'main'
+
+ desc "Copies the #{name} database from the main database"
+ task name => :environment do
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name)
+
+ ApplicationRecord.connection.create_database(db_config.database, template: ApplicationRecord.connection_db_config.database)
+ rescue ActiveRecord::DatabaseAlreadyExists
+ warn "Database '#{db_config.database}' already exists"
+ end
+ end
+ end
end
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 740a8920cf2..1a09a3e8e59 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -295,13 +295,6 @@ module QA
merge_requests.find { |mr| mr[:title] == title }
end
- def runners(tag_list: nil)
- url = tag_list ? "#{api_runners_path}?tag_list=#{tag_list.compact.join(',')}" : api_runners_path
- response = get(request_url(url, per_page: '100'))
-
- parse_body(response)
- end
-
def registry_repositories
response = get(request_url(api_registry_repositories_path))
parse_body(response)
diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb
index 9c5c9992442..2075f302370 100644
--- a/qa/qa/resource/runner.rb
+++ b/qa/qa/resource/runner.rb
@@ -46,11 +46,12 @@ module QA
end
def remove_via_api!
- runners = project.runners(tag_list: @tags)
+ runners = list_of_runners(tag_list: @tags)
return if runners.blank?
this_runner = runners.find { |runner| runner[:description] == name }
+
unless this_runner
raise "Project #{project.path_with_namespace} does not have a runner with a description matching #{name} #{"or tags #{@tags}" if @tags&.any?}. Runners available: #{runners}"
end
@@ -62,6 +63,16 @@ module QA
Service::DockerRun::GitlabRunner.new(name).remove!
end
+ def list_of_runners(tag_list: nil)
+ url = tag_list ? "#{api_post_path}?tag_list=#{tag_list.compact.join(',')}" : api_post_path
+ response = get(request_url(url, per_page: '100'))
+
+ # Capturing 500 error code responses to log this issue better. We can consider cleaning it up once https://gitlab.com/gitlab-org/gitlab/-/issues/331753 is addressed.
+ raise "Response returned a #{response.code} error code. #{response.body}" if response.code == Support::API::HTTP_STATUS_SERVER_ERROR
+
+ parse_body(response)
+ end
+
def api_delete_path
"/runners/#{id}"
end
@@ -70,6 +81,7 @@ module QA
end
def api_post_path
+ "/runners"
end
def api_post_body
diff --git a/qa/qa/specs/features/api/4_verify/remove_runner_spec.rb b/qa/qa/specs/features/api/4_verify/remove_runner_spec.rb
index 55744332d8d..64b15c6ff74 100644
--- a/qa/qa/specs/features/api/4_verify/remove_runner_spec.rb
+++ b/qa/qa/specs/features/api/4_verify/remove_runner_spec.rb
@@ -7,7 +7,7 @@ module QA
let(:api_client) { Runtime::API::Client.new(:gitlab) }
let(:executor) { "qa-runner-#{Time.now.to_i}" }
- let(:runner_tags) { ['runner-registration-e2e-test'] }
+ let(:runner_tags) { ["runner-registration-e2e-test-#{Faker::Alphanumeric.alphanumeric(number: 8)}"] }
let!(:runner) do
Resource::Runner.fabricate! do |runner|
runner.name = executor
@@ -20,16 +20,18 @@ module QA
end
# Removing a runner via the UI is covered by `spec/features/runners_spec.rb``
- it 'removes the runner', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/355302', type: :investigating }, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/354828' do
- expect(runner.project.runners.size).to eq(1)
- expect(runner.project.runners.first[:description]).to eq(executor)
+ it 'removes the runner' do
+ runners = runner.list_of_runners(tag_list: runner_tags)
- request = Runtime::API::Request.new(api_client, "runners/#{runner.project.runners.first[:id]}")
+ expect(runners.size).to eq(1)
+ expect(runners.first[:description]).to eq(executor)
+
+ request = Runtime::API::Request.new(api_client, "runners/#{runners.first[:id]}")
response = delete(request.url)
expect(response.code).to eq(Support::API::HTTP_STATUS_NO_CONTENT)
expect(response.body).to be_empty
- expect(runner.project.runners).to be_empty
+ expect(runner.list_of_runners(tag_list: runner_tags)).to be_empty
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb
index 653c0657c81..e9871a70560 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb
@@ -2,10 +2,13 @@
module QA
RSpec.describe 'Create' do
- describe 'Open a fork in Web IDE', quarantine: {
+ describe 'Open a fork in Web IDE',
+ # TODO: remove limitation to only run on main when the test is fixed
+ only: { pipeline: :main },
+ quarantine: {
issue: "https://gitlab.com/gitlab-org/gitlab/-/issues/351696",
type: :flaky
- } do
+ } do
let(:parent_project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'parent-project'
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index c976ba7525b..6a659623b53 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -62,7 +62,7 @@ describe('BoardForm', () => {
};
},
provide: {
- rootPath: 'root',
+ boardBaseUrl: 'root',
},
mocks: {
$apollo: {
diff --git a/spec/frontend/boards/components/board_top_bar_spec.js b/spec/frontend/boards/components/board_top_bar_spec.js
new file mode 100644
index 00000000000..997768a0cc7
--- /dev/null
+++ b/spec/frontend/boards/components/board_top_bar_spec.js
@@ -0,0 +1,88 @@
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import Vuex from 'vuex';
+
+import BoardTopBar from '~/boards/components/board_top_bar.vue';
+import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_trigger.vue';
+import BoardsSelector from '~/boards/components/boards_selector.vue';
+import ConfigToggle from '~/boards/components/config_toggle.vue';
+import IssueBoardFilteredSearch from '~/boards/components/issue_board_filtered_search.vue';
+import NewBoardButton from '~/boards/components/new_board_button.vue';
+import ToggleFocus from '~/boards/components/toggle_focus.vue';
+
+describe('BoardTopBar', () => {
+ let wrapper;
+
+ Vue.use(Vuex);
+
+ const createStore = ({ mockGetters = {} } = {}) => {
+ return new Vuex.Store({
+ state: {},
+ getters: {
+ isEpicBoard: () => false,
+ ...mockGetters,
+ },
+ });
+ };
+
+ const createComponent = ({ provide = {}, mockGetters = {} } = {}) => {
+ const store = createStore({ mockGetters });
+ wrapper = shallowMount(BoardTopBar, {
+ store,
+ provide: {
+ swimlanesFeatureAvailable: false,
+ canAdminList: false,
+ isSignedIn: false,
+ fullPath: 'gitlab-org',
+ boardType: 'group',
+ releasesFetchPath: '/releases',
+ ...provide,
+ },
+ stubs: { IssueBoardFilteredSearch },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('base template', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders BoardsSelector component', () => {
+ expect(wrapper.findComponent(BoardsSelector).exists()).toBe(true);
+ });
+
+ it('renders IssueBoardFilteredSearch component', () => {
+ expect(wrapper.findComponent(IssueBoardFilteredSearch).exists()).toBe(true);
+ });
+
+ it('renders NewBoardButton component', () => {
+ expect(wrapper.findComponent(NewBoardButton).exists()).toBe(true);
+ });
+
+ it('renders ConfigToggle component', () => {
+ expect(wrapper.findComponent(ConfigToggle).exists()).toBe(true);
+ });
+
+ it('renders ToggleFocus component', () => {
+ expect(wrapper.findComponent(ToggleFocus).exists()).toBe(true);
+ });
+
+ it('does not render BoardAddNewColumnTrigger component', () => {
+ expect(wrapper.findComponent(BoardAddNewColumnTrigger).exists()).toBe(false);
+ });
+ });
+
+ describe('when user can admin list', () => {
+ beforeEach(() => {
+ createComponent({ provide: { canAdminList: true } });
+ });
+
+ it('renders BoardAddNewColumnTrigger component', () => {
+ expect(wrapper.findComponent(BoardAddNewColumnTrigger).exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/boards/components/boards_selector_spec.js b/spec/frontend/boards/components/boards_selector_spec.js
index 0c044deb78c..717216a9882 100644
--- a/spec/frontend/boards/components/boards_selector_spec.js
+++ b/spec/frontend/boards/components/boards_selector_spec.js
@@ -105,6 +105,10 @@ describe('BoardsSelector', () => {
apolloProvider: fakeApollo,
propsData: {
throttleDuration,
+ },
+ attachTo: document.body,
+ provide: {
+ fullPath: '',
boardBaseUrl: `${TEST_HOST}/board/base/url`,
hasMissingBoards: false,
canAdminBoard: true,
@@ -112,10 +116,6 @@ describe('BoardsSelector', () => {
scopedIssueBoardFeatureEnabled: true,
weights: [],
},
- attachTo: document.body,
- provide: {
- fullPath: '',
- },
});
};
diff --git a/spec/frontend/boards/components/issue_board_filtered_search_spec.js b/spec/frontend/boards/components/issue_board_filtered_search_spec.js
index 76e8b84d8ef..e4a6a2b8b76 100644
--- a/spec/frontend/boards/components/issue_board_filtered_search_spec.js
+++ b/spec/frontend/boards/components/issue_board_filtered_search_spec.js
@@ -14,10 +14,11 @@ describe('IssueBoardFilter', () => {
const createComponent = ({ isSignedIn = false } = {}) => {
wrapper = shallowMount(IssueBoardFilteredSpec, {
- propsData: { fullPath: 'gitlab-org', boardType: 'group' },
provide: {
isSignedIn,
releasesFetchPath: '/releases',
+ fullPath: 'gitlab-org',
+ boardType: 'group',
},
});
};
diff --git a/spec/helpers/boards_helper_spec.rb b/spec/helpers/boards_helper_spec.rb
index ec949fde30e..8d5dc3fb4be 100644
--- a/spec/helpers/boards_helper_spec.rb
+++ b/spec/helpers/boards_helper_spec.rb
@@ -102,6 +102,7 @@ RSpec.describe BoardsHelper do
allow(helper).to receive(:can?).with(user, :create_non_backlog_issues, project_board).and_return(true)
allow(helper).to receive(:can?).with(user, :admin_issue, project_board).and_return(true)
allow(helper).to receive(:can?).with(user, :admin_issue_board_list, project).and_return(false)
+ allow(helper).to receive(:can?).with(user, :admin_issue_board, project).and_return(false)
end
it 'returns a board_lists_path as lists_endpoint' do
@@ -129,12 +130,23 @@ RSpec.describe BoardsHelper do
it 'returns can_admin_list as false by default' do
expect(helper.board_data[:can_admin_list]).to eq('false')
end
- it 'returns can_admin_list as true when user can admin the board' do
+ it 'returns can_admin_list as true when user can admin the board lists' do
allow(helper).to receive(:can?).with(user, :admin_issue_board_list, project).and_return(true)
expect(helper.board_data[:can_admin_list]).to eq('true')
end
end
+
+ context 'can_admin_board' do
+ it 'returns can_admin_board as false by default' do
+ expect(helper.board_data[:can_admin_board]).to eq('false')
+ end
+ it 'returns can_admin_board as true when user can admin the board' do
+ allow(helper).to receive(:can?).with(user, :admin_issue_board, project).and_return(true)
+
+ expect(helper.board_data[:can_admin_board]).to eq('true')
+ end
+ end
end
context 'group board' do
@@ -146,6 +158,7 @@ RSpec.describe BoardsHelper do
allow(helper).to receive(:can?).with(user, :create_non_backlog_issues, group_board).and_return(true)
allow(helper).to receive(:can?).with(user, :admin_issue, group_board).and_return(true)
allow(helper).to receive(:can?).with(user, :admin_issue_board_list, base_group).and_return(false)
+ allow(helper).to receive(:can?).with(user, :admin_issue_board, base_group).and_return(false)
end
it 'returns correct path for base group' do
@@ -165,7 +178,7 @@ RSpec.describe BoardsHelper do
it 'returns can_admin_list as false by default' do
expect(helper.board_data[:can_admin_list]).to eq('false')
end
- it 'returns can_admin_list as true when user can admin the board' do
+ it 'returns can_admin_list as true when user can admin the board lists' do
allow(helper).to receive(:can?).with(user, :admin_issue_board_list, base_group).and_return(true)
expect(helper.board_data[:can_admin_list]).to eq('true')
diff --git a/spec/tasks/dev_rake_spec.rb b/spec/tasks/dev_rake_spec.rb
index ebcea338291..7c03b4f417c 100644
--- a/spec/tasks/dev_rake_spec.rb
+++ b/spec/tasks/dev_rake_spec.rb
@@ -7,6 +7,8 @@ RSpec.describe 'dev rake tasks' do
Rake.application.rake_require 'tasks/gitlab/setup'
Rake.application.rake_require 'tasks/gitlab/shell'
Rake.application.rake_require 'tasks/dev'
+ Rake.application.rake_require 'active_record/railties/databases'
+ Rake.application.rake_require 'tasks/gitlab/db'
end
describe 'setup' do
@@ -37,4 +39,35 @@ RSpec.describe 'dev rake tasks' do
load_task
end
end
+
+ describe 'copy_db:ci' do
+ before do
+ skip_if_multiple_databases_not_setup
+
+ configurations = instance_double(ActiveRecord::DatabaseConfigurations)
+ allow(ActiveRecord::Base).to receive(:configurations).and_return(configurations)
+ allow(configurations).to receive(:configs_for).with(env_name: Rails.env, name: 'ci').and_return(ci_configuration)
+ end
+
+ subject(:load_task) { run_rake_task('dev:setup_ci_db') }
+
+ let(:ci_configuration) { instance_double(ActiveRecord::DatabaseConfigurations::HashConfig, name: 'ci', database: '__test_db_ci') }
+
+ it 'creates the database from main' do
+ expect(ApplicationRecord.connection).to receive(:create_database).with(
+ ci_configuration.database,
+ template: ApplicationRecord.connection_db_config.database
+ )
+
+ run_rake_task('dev:copy_db:ci')
+ end
+
+ context 'when the database already exists' do
+ it 'prints out a warning' do
+ expect(ApplicationRecord.connection).to receive(:create_database).and_raise(ActiveRecord::DatabaseAlreadyExists)
+
+ expect { run_rake_task('dev:copy_db:ci') }.to output(/Database '#{ci_configuration.database}' already exists/).to_stderr
+ end
+ end
+ end
end