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-29 03:09:36 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-03-29 03:09:36 +0300
commit4ca7380c7cd644ab63a801b593e9a581e48a7b0e (patch)
treed643e787573dc19f1e60e685365ba5c113013446
parenta6f15de9f648591f704625ed0debb7178ff71459 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/boards/components/board_extra_actions.vue57
-rw-r--r--app/assets/javascripts/boards/components/modal/empty_state.vue84
-rw-r--r--app/assets/javascripts/boards/components/modal/filters.js27
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.vue80
-rw-r--r--app/assets/javascripts/boards/components/modal/header.vue80
-rw-r--r--app/assets/javascripts/boards/components/modal/index.vue151
-rw-r--r--app/assets/javascripts/boards/components/modal/list.vue141
-rw-r--r--app/assets/javascripts/boards/components/modal/lists_dropdown.vue49
-rw-r--r--app/assets/javascripts/boards/components/modal/tabs.vue42
-rw-r--r--app/assets/javascripts/boards/components/toggle_focus.vue2
-rw-r--r--app/assets/javascripts/boards/ee_functions.js1
-rw-r--r--app/assets/javascripts/boards/index.js50
-rw-r--r--app/assets/javascripts/boards/mixins/modal_footer.js1
-rw-r--r--app/assets/javascripts/boards/mixins/modal_mixins.js12
-rw-r--r--app/assets/javascripts/boards/stores/modal_store.js95
-rw-r--r--app/assets/javascripts/labels_select.js19
-rw-r--r--app/assets/javascripts/milestone_select.js12
-rw-r--r--app/assets/javascripts/users_select/index.js5
-rw-r--r--app/assets/stylesheets/page_bundles/boards.scss93
-rw-r--r--app/controllers/projects/boards_controller.rb1
-rw-r--r--app/helpers/timeboxes_helper.rb11
-rw-r--r--app/models/service.rb4
-rw-r--r--app/views/projects/services/mattermost_slash_commands/_help.html.haml4
-rw-r--r--app/views/projects/services/slack_slash_commands/_help.html.haml2
-rw-r--r--app/views/shared/boards/_show.html.haml10
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml13
-rw-r--r--changelogs/unreleased/298854-gitlab-returns-error-500-when-visiting-admin-area-settings-integra.yml5
-rw-r--r--changelogs/unreleased/remove-disabled-add-issues-modal.yml5
-rw-r--r--config/feature_flags/development/add_issues_button.yml8
-rw-r--r--doc/user/project/issue_board.md49
-rw-r--r--locale/gitlab.pot38
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb271
-rw-r--r--spec/features/boards/focus_mode_spec.rb4
-rw-r--r--spec/features/boards/modal_filter_spec.rb230
-rw-r--r--spec/frontend/boards/modal_store_spec.js134
-rw-r--r--spec/helpers/timeboxes_helper_spec.rb36
-rw-r--r--spec/models/service_spec.rb10
37 files changed, 50 insertions, 1786 deletions
diff --git a/app/assets/javascripts/boards/components/board_extra_actions.vue b/app/assets/javascripts/boards/components/board_extra_actions.vue
deleted file mode 100644
index b802ccc7882..00000000000
--- a/app/assets/javascripts/boards/components/board_extra_actions.vue
+++ /dev/null
@@ -1,57 +0,0 @@
-<script>
-import { GlTooltip, GlButton } from '@gitlab/ui';
-import { __ } from '~/locale';
-
-export default {
- name: 'BoardExtraActions',
- components: {
- GlTooltip,
- GlButton,
- },
- props: {
- canAdminList: {
- type: Boolean,
- required: true,
- },
- disabled: {
- type: Boolean,
- required: true,
- },
- openModal: {
- type: Function,
- required: true,
- },
- },
- computed: {
- tooltipTitle() {
- if (this.disabled) {
- return __('Please add a list to your board first');
- }
-
- return '';
- },
- },
-};
-</script>
-
-<template>
- <div class="board-extra-actions">
- <span ref="addIssuesButtonTooltip" class="gl-ml-3">
- <gl-button
- v-if="canAdminList"
- type="button"
- data-placement="bottom"
- data-track-event="click_button"
- data-track-label="board_add_issues"
- :disabled="disabled"
- :aria-disabled="disabled"
- @click="openModal"
- >
- {{ __('Add issues') }}
- </gl-button>
- </span>
- <gl-tooltip v-if="disabled" :target="() => $refs.addIssuesButtonTooltip" placement="bottom">
- {{ tooltipTitle }}
- </gl-tooltip>
- </div>
-</template>
diff --git a/app/assets/javascripts/boards/components/modal/empty_state.vue b/app/assets/javascripts/boards/components/modal/empty_state.vue
deleted file mode 100644
index 486b012e3d2..00000000000
--- a/app/assets/javascripts/boards/components/modal/empty_state.vue
+++ /dev/null
@@ -1,84 +0,0 @@
-<script>
-import { GlButton, GlSprintf } from '@gitlab/ui';
-import { __ } from '~/locale';
-import modalMixin from '../../mixins/modal_mixins';
-import ModalStore from '../../stores/modal_store';
-
-export default {
- components: {
- GlButton,
- GlSprintf,
- },
- mixins: [modalMixin],
- props: {
- newIssuePath: {
- type: String,
- required: true,
- },
- emptyStateSvg: {
- type: String,
- required: true,
- },
- },
- data() {
- return ModalStore.store;
- },
- computed: {
- contents() {
- const obj = {
- title: __("You haven't added any issues to your project yet"),
- content: __(
- 'An issue can be a bug, a todo or a feature request that needs to be discussed in a project. Besides, issues are searchable and filterable.',
- ),
- };
-
- if (this.activeTab === 'selected') {
- obj.title = __("You haven't selected any issues yet");
- obj.content = __(
- 'Go back to %{tagStart}Open issues%{tagEnd} and select some issues to add to your board.',
- );
- }
-
- return obj;
- },
- },
-};
-</script>
-
-<template>
- <section class="empty-state d-flex mt-0 h-100">
- <div class="row w-100 my-auto mx-0">
- <div class="col-12 col-md-6 order-md-last">
- <aside class="svg-content d-none d-md-block"><img :src="emptyStateSvg" /></aside>
- </div>
- <div class="col-12 col-md-6 order-md-first">
- <div class="text-content">
- <h4>{{ contents.title }}</h4>
- <p>
- <gl-sprintf :message="contents.content">
- <template #tag="{ content }">
- <strong>{{ content }}</strong>
- </template>
- </gl-sprintf>
- </p>
- <gl-button
- v-if="activeTab === 'all'"
- :href="newIssuePath"
- category="secondary"
- variant="success"
- >
- {{ __('New issue') }}
- </gl-button>
- <gl-button
- v-if="activeTab === 'selected'"
- category="primary"
- variant="default"
- @click="changeTab('all')"
- >
- {{ __('Open issues') }}
- </gl-button>
- </div>
- </div>
- </div>
- </section>
-</template>
diff --git a/app/assets/javascripts/boards/components/modal/filters.js b/app/assets/javascripts/boards/components/modal/filters.js
deleted file mode 100644
index 2fb38a549f3..00000000000
--- a/app/assets/javascripts/boards/components/modal/filters.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import FilteredSearchContainer from '../../../filtered_search/container';
-import FilteredSearchBoards from '../../filtered_search_boards';
-
-export default {
- name: 'modal-filters',
- props: {
- store: {
- type: Object,
- required: true,
- },
- },
- mounted() {
- FilteredSearchContainer.container = this.$el;
-
- this.filteredSearch = new FilteredSearchBoards(this.store);
- this.filteredSearch.setup();
- this.filteredSearch.removeTokens();
- this.filteredSearch.handleInputPlaceholder();
- this.filteredSearch.toggleClearSearchButton();
- },
- destroyed() {
- this.filteredSearch.cleanup();
- FilteredSearchContainer.container = document;
- this.store.path = '';
- },
- template: '#js-board-modal-filter',
-};
diff --git a/app/assets/javascripts/boards/components/modal/footer.vue b/app/assets/javascripts/boards/components/modal/footer.vue
deleted file mode 100644
index 05e1219bc70..00000000000
--- a/app/assets/javascripts/boards/components/modal/footer.vue
+++ /dev/null
@@ -1,80 +0,0 @@
-<script>
-import { GlButton } from '@gitlab/ui';
-import footerEEMixin from 'ee_else_ce/boards/mixins/modal_footer';
-import { deprecatedCreateFlash as Flash } from '../../../flash';
-import { __, n__ } from '../../../locale';
-import modalMixin from '../../mixins/modal_mixins';
-import boardsStore from '../../stores/boards_store';
-import ModalStore from '../../stores/modal_store';
-import ListsDropdown from './lists_dropdown.vue';
-
-export default {
- components: {
- ListsDropdown,
- GlButton,
- },
- mixins: [modalMixin, footerEEMixin],
- data() {
- return {
- modal: ModalStore.store,
- state: boardsStore.state,
- };
- },
- computed: {
- submitDisabled() {
- return !ModalStore.selectedCount();
- },
- submitText() {
- const count = ModalStore.selectedCount();
- if (!count) return __('Add issues');
- return n__(`Add %d issue`, `Add %d issues`, count);
- },
- },
- methods: {
- buildUpdateRequest(list) {
- return {
- add_label_ids: [list.label.id],
- };
- },
- addIssues() {
- const firstListIndex = 1;
- const list = this.modal.selectedList || this.state.lists[firstListIndex];
- const selectedIssues = ModalStore.getSelectedIssues();
- const issueIds = selectedIssues.map((issue) => issue.id);
- const req = this.buildUpdateRequest(list);
-
- // Post the data to the backend
- boardsStore.bulkUpdate(issueIds, req).catch(() => {
- Flash(__('Failed to update issues, please try again.'));
-
- selectedIssues.forEach((issue) => {
- list.removeIssue(issue);
- list.issuesSize -= 1;
- });
- });
-
- // Add the issues on the frontend
- selectedIssues.forEach((issue) => {
- list.addIssue(issue);
- list.issuesSize += 1;
- });
-
- this.toggleModal(false);
- },
- },
-};
-</script>
-<template>
- <footer class="form-actions add-issues-footer">
- <div class="float-left">
- <gl-button :disabled="submitDisabled" category="primary" variant="success" @click="addIssues">
- {{ submitText }}
- </gl-button>
- <span class="inline add-issues-footer-to-list">{{ __('to list') }}</span>
- <lists-dropdown />
- </div>
- <gl-button class="float-right" @click="toggleModal(false)">
- {{ __('Cancel') }}
- </gl-button>
- </footer>
-</template>
diff --git a/app/assets/javascripts/boards/components/modal/header.vue b/app/assets/javascripts/boards/components/modal/header.vue
deleted file mode 100644
index c3a71e7177a..00000000000
--- a/app/assets/javascripts/boards/components/modal/header.vue
+++ /dev/null
@@ -1,80 +0,0 @@
-<script>
-/* eslint-disable @gitlab/vue-require-i18n-strings */
-import { GlButton } from '@gitlab/ui';
-import { __ } from '~/locale';
-import modalMixin from '../../mixins/modal_mixins';
-import ModalStore from '../../stores/modal_store';
-import ModalFilters from './filters';
-import ModalTabs from './tabs.vue';
-
-export default {
- components: {
- ModalTabs,
- ModalFilters,
- GlButton,
- },
- mixins: [modalMixin],
- props: {
- projectId: {
- type: Number,
- required: true,
- },
- labelPath: {
- type: String,
- required: true,
- },
- },
- data() {
- return ModalStore.store;
- },
- computed: {
- selectAllText() {
- if (ModalStore.selectedCount() !== this.issues.length || this.issues.length === 0) {
- return __('Select all');
- }
-
- return __('Deselect all');
- },
- showSearch() {
- return this.activeTab === 'all' && !this.loading && this.issuesCount > 0;
- },
- },
- methods: {
- toggleAll() {
- this.$refs.selectAllBtn.$el.blur();
-
- ModalStore.toggleAll();
- },
- },
-};
-</script>
-<template>
- <div>
- <header class="add-issues-header border-top-0 form-actions">
- <h2 class="m-0">
- Add issues
- <gl-button
- category="tertiary"
- icon="close"
- class="close"
- data-dismiss="modal"
- :aria-label="__('Close')"
- @click="toggleModal(false)"
- />
- </h2>
- </header>
- <modal-tabs v-if="!loading && issuesCount > 0" />
- <div v-if="showSearch" class="d-flex gl-mb-3">
- <modal-filters :store="filter" />
- <gl-button
- ref="selectAllBtn"
- category="secondary"
- variant="success"
- class="gl-ml-3"
- @click="toggleAll"
- >
- {{ selectAllText }}
- </gl-button>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/boards/components/modal/index.vue b/app/assets/javascripts/boards/components/modal/index.vue
deleted file mode 100644
index 5af90c1ee66..00000000000
--- a/app/assets/javascripts/boards/components/modal/index.vue
+++ /dev/null
@@ -1,151 +0,0 @@
-<script>
-/* global ListIssue */
-import { GlLoadingIcon } from '@gitlab/ui';
-import boardsStore from '~/boards/stores/boards_store';
-import { urlParamsToObject } from '~/lib/utils/common_utils';
-import ModalStore from '../../stores/modal_store';
-import EmptyState from './empty_state.vue';
-import ModalFooter from './footer.vue';
-import ModalHeader from './header.vue';
-import ModalList from './list.vue';
-
-export default {
- components: {
- EmptyState,
- ModalHeader,
- ModalList,
- ModalFooter,
- GlLoadingIcon,
- },
- props: {
- newIssuePath: {
- type: String,
- required: true,
- },
- emptyStateSvg: {
- type: String,
- required: true,
- },
- projectId: {
- type: Number,
- required: true,
- },
- labelPath: {
- type: String,
- required: true,
- },
- },
- data() {
- return ModalStore.store;
- },
- computed: {
- showList() {
- if (this.activeTab === 'selected') {
- return this.selectedIssues.length > 0;
- }
-
- return this.issuesCount > 0;
- },
- showEmptyState() {
- if (!this.loading && this.issuesCount === 0) {
- return true;
- }
-
- return this.activeTab === 'selected' && this.selectedIssues.length === 0;
- },
- },
- watch: {
- page() {
- this.loadIssues();
- },
- showAddIssuesModal() {
- if (this.showAddIssuesModal && !this.issues.length) {
- this.loading = true;
- const loadingDone = () => {
- this.loading = false;
- };
-
- this.loadIssues().then(loadingDone).catch(loadingDone);
- } else if (!this.showAddIssuesModal) {
- this.issues = [];
- this.selectedIssues = [];
- this.issuesCount = false;
- }
- },
- filter: {
- handler() {
- if (this.$el.tagName) {
- this.page = 1;
- this.filterLoading = true;
- const loadingDone = () => {
- this.filterLoading = false;
- };
-
- this.loadIssues(true).then(loadingDone).catch(loadingDone);
- }
- },
- deep: true,
- },
- },
- created() {
- this.page = 1;
- },
- methods: {
- loadIssues(clearIssues = false) {
- if (!this.showAddIssuesModal) return false;
-
- return boardsStore
- .getBacklog({
- ...urlParamsToObject(this.filter.path),
- page: this.page,
- per: this.perPage,
- })
- .then((res) => res.data)
- .then((data) => {
- if (clearIssues) {
- this.issues = [];
- }
-
- data.issues.forEach((issueObj) => {
- const issue = new ListIssue(issueObj);
- const foundSelectedIssue = ModalStore.findSelectedIssue(issue);
- issue.selected = Boolean(foundSelectedIssue);
-
- this.issues.push(issue);
- });
-
- this.loadingNewPage = false;
-
- if (!this.issuesCount) {
- this.issuesCount = data.size;
- }
- })
- .catch(() => {
- // TODO: handle request error
- });
- },
- },
-};
-</script>
-<template>
- <div
- v-if="showAddIssuesModal"
- class="add-issues-modal d-flex position-fixed position-top-0 position-bottom-0 position-left-0 position-right-0 h-100"
- >
- <div class="add-issues-container d-flex flex-column m-auto rounded">
- <modal-header :project-id="projectId" :label-path="labelPath" />
- <modal-list v-if="!loading && showList && !filterLoading" :empty-state-svg="emptyStateSvg" />
- <empty-state
- v-if="showEmptyState"
- :new-issue-path="newIssuePath"
- :empty-state-svg="emptyStateSvg"
- />
- <section v-if="loading || filterLoading" class="add-issues-list d-flex h-100 text-center">
- <div class="add-issues-list-loading w-100 align-self-center">
- <gl-loading-icon size="md" />
- </div>
- </section>
- <modal-footer />
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/boards/components/modal/list.vue b/app/assets/javascripts/boards/components/modal/list.vue
deleted file mode 100644
index e66cae0ce18..00000000000
--- a/app/assets/javascripts/boards/components/modal/list.vue
+++ /dev/null
@@ -1,141 +0,0 @@
-<script>
-import { GlIcon } from '@gitlab/ui';
-import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
-import ModalStore from '../../stores/modal_store';
-import BoardCardInner from '../board_card_inner.vue';
-
-export default {
- components: {
- BoardCardInner,
- GlIcon,
- },
- props: {
- emptyStateSvg: {
- type: String,
- required: true,
- },
- },
- data() {
- return ModalStore.store;
- },
- computed: {
- loopIssues() {
- if (this.activeTab === 'all') {
- return this.issues;
- }
-
- return this.selectedIssues;
- },
- groupedIssues() {
- const groups = [];
- this.loopIssues.forEach((issue, i) => {
- const index = i % this.columns;
-
- if (!groups[index]) {
- groups.push([]);
- }
-
- groups[index].push(issue);
- });
-
- return groups;
- },
- },
- watch: {
- activeTab() {
- if (this.activeTab === 'all') {
- ModalStore.purgeUnselectedIssues();
- }
- },
- },
- mounted() {
- this.scrollHandlerWrapper = this.scrollHandler.bind(this);
- this.setColumnCountWrapper = this.setColumnCount.bind(this);
- this.setColumnCount();
-
- this.$refs.list.addEventListener('scroll', this.scrollHandlerWrapper);
- window.addEventListener('resize', this.setColumnCountWrapper);
- },
- beforeDestroy() {
- this.$refs.list.removeEventListener('scroll', this.scrollHandlerWrapper);
- window.removeEventListener('resize', this.setColumnCountWrapper);
- },
- methods: {
- scrollHandler() {
- const currentPage = Math.floor(this.issues.length / this.perPage);
-
- if (
- this.scrollTop() > this.scrollHeight() - 100 &&
- !this.loadingNewPage &&
- currentPage === this.page
- ) {
- this.loadingNewPage = true;
- this.page += 1;
- }
- },
- toggleIssue(e, issue) {
- if (e.target.tagName !== 'A') {
- ModalStore.toggleIssue(issue);
- }
- },
- listHeight() {
- return this.$refs.list.getBoundingClientRect().height;
- },
- scrollHeight() {
- return this.$refs.list.scrollHeight;
- },
- scrollTop() {
- return this.$refs.list.scrollTop + this.listHeight();
- },
- showIssue(issue) {
- if (this.activeTab === 'all') return true;
-
- const index = ModalStore.selectedIssueIndex(issue);
-
- return index !== -1;
- },
- setColumnCount() {
- const breakpoint = bp.getBreakpointSize();
-
- if (breakpoint === 'xl' || breakpoint === 'lg') {
- this.columns = 3;
- } else if (breakpoint === 'md') {
- this.columns = 2;
- } else {
- this.columns = 1;
- }
- },
- },
-};
-</script>
-<template>
- <section ref="list" class="add-issues-list add-issues-list-columns d-flex h-100">
- <div
- v-if="issuesCount > 0 && issues.length === 0"
- class="empty-state add-issues-empty-state-filter text-center"
- >
- <div class="svg-content"><img :src="emptyStateSvg" /></div>
- <div class="text-content">
- <h4>{{ __('There are no issues to show.') }}</h4>
- </div>
- </div>
- <div v-for="(group, index) in groupedIssues" :key="index" class="add-issues-list-column">
- <div v-for="issue in group" v-if="showIssue(issue)" :key="issue.id" class="board-card-parent">
- <div
- :class="{ 'is-active': issue.selected }"
- class="board-card position-relative p-3 rounded"
- @click="toggleIssue($event, issue)"
- >
- <board-card-inner :item="issue" />
- <gl-icon
- v-if="issue.selected"
- :aria-label="'Issue #' + issue.id + ' selected'"
- name="mobile-issue-close"
- aria-checked="true"
- class="issue-card-selected text-center"
- />
- </div>
- </div>
- </div>
- </section>
-</template>
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
deleted file mode 100644
index 2065568d275..00000000000
--- a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
+++ /dev/null
@@ -1,49 +0,0 @@
-<script>
-import { GlLink, GlIcon } from '@gitlab/ui';
-import boardsStore from '../../stores/boards_store';
-import ModalStore from '../../stores/modal_store';
-
-export default {
- components: {
- GlLink,
- GlIcon,
- },
- data() {
- return {
- modal: ModalStore.store,
- state: boardsStore.state,
- };
- },
- computed: {
- selected() {
- return this.modal.selectedList || this.state.lists[1];
- },
- },
- destroyed() {
- this.modal.selectedList = null;
- },
-};
-</script>
-<template>
- <div class="dropdown inline">
- <button class="dropdown-menu-toggle" type="button" data-toggle="dropdown" aria-expanded="false">
- <span :style="{ backgroundColor: selected.label.color }" class="dropdown-label-box"> </span>
- {{ selected.title }} <gl-icon name="chevron-down" class="dropdown-menu-toggle-icon" />
- </button>
- <div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
- <ul>
- <li v-for="(list, i) in state.lists" v-if="list.type == 'label'" :key="i">
- <gl-link
- :class="{ 'is-active': list.id == selected.id }"
- href="#"
- role="button"
- @click.prevent="modal.selectedList = list"
- >
- <span :style="{ backgroundColor: list.label.color }" class="dropdown-label-box"> </span>
- {{ list.title }}
- </gl-link>
- </li>
- </ul>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/boards/components/modal/tabs.vue b/app/assets/javascripts/boards/components/modal/tabs.vue
deleted file mode 100644
index 0b717f516db..00000000000
--- a/app/assets/javascripts/boards/components/modal/tabs.vue
+++ /dev/null
@@ -1,42 +0,0 @@
-<script>
-/* eslint-disable @gitlab/vue-require-i18n-strings */
-import { GlTabs, GlTab, GlBadge } from '@gitlab/ui';
-import modalMixin from '../../mixins/modal_mixins';
-import ModalStore from '../../stores/modal_store';
-
-export default {
- components: {
- GlTabs,
- GlTab,
- GlBadge,
- },
- mixins: [modalMixin],
- data() {
- return ModalStore.store;
- },
- computed: {
- selectedCount() {
- return ModalStore.selectedCount();
- },
- },
- destroyed() {
- this.activeTab = 'all';
- },
-};
-</script>
-<template>
- <gl-tabs class="gl-mt-3">
- <gl-tab @click.prevent="changeTab('all')">
- <template slot="title">
- <span>Open issues</span>
- <gl-badge size="sm" class="gl-tab-counter-badge">{{ issuesCount }}</gl-badge>
- </template>
- </gl-tab>
- <gl-tab @click.prevent="changeTab('selected')">
- <template slot="title">
- <span>Selected issues</span>
- <gl-badge size="sm" class="gl-tab-counter-badge">{{ selectedCount }}</gl-badge>
- </template>
- </gl-tab>
- </gl-tabs>
-</template>
diff --git a/app/assets/javascripts/boards/components/toggle_focus.vue b/app/assets/javascripts/boards/components/toggle_focus.vue
index 87a1d517111..49f5e7d20a9 100644
--- a/app/assets/javascripts/boards/components/toggle_focus.vue
+++ b/app/assets/javascripts/boards/components/toggle_focus.vue
@@ -38,7 +38,7 @@ export default {
</script>
<template>
- <div class="board-extra-actions gl-ml-3 gl-display-none gl-md-display-flex gl-align-items-center">
+ <div class="gl-ml-3 gl-display-none gl-md-display-flex gl-align-items-center">
<gl-button
ref="toggleFocusModeButton"
v-gl-tooltip
diff --git a/app/assets/javascripts/boards/ee_functions.js b/app/assets/javascripts/boards/ee_functions.js
index b6b34556663..62a0d930ec0 100644
--- a/app/assets/javascripts/boards/ee_functions.js
+++ b/app/assets/javascripts/boards/ee_functions.js
@@ -2,4 +2,3 @@ export const setWeightFetchingState = () => {};
export const setEpicFetchingState = () => {};
export const getMilestoneTitle = () => ({});
-export const getBoardsModalData = () => ({});
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index f0c39d9cf74..7635da8487b 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -10,26 +10,21 @@ import {
setWeightFetchingState,
setEpicFetchingState,
getMilestoneTitle,
- getBoardsModalData,
} from 'ee_else_ce/boards/ee_functions';
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 BoardContent from '~/boards/components/board_content.vue';
-import BoardExtraActions from '~/boards/components/board_extra_actions.vue';
import './models/label';
import './models/assignee';
import '~/boards/models/milestone';
import '~/boards/models/project';
import '~/boards/filters/due_date_filters';
-import BoardAddIssuesModal from '~/boards/components/modal/index.vue';
import { issuableTypes } from '~/boards/constants';
import eventHub from '~/boards/eventhub';
import FilteredSearchBoards from '~/boards/filtered_search_boards';
-import modalMixin from '~/boards/mixins/modal_mixins';
import store from '~/boards/stores';
import boardsStore from '~/boards/stores/boards_store';
-import ModalStore from '~/boards/stores/modal_store';
import toggleFocusMode from '~/boards/toggle_focus';
import { deprecatedCreateFlash as Flash } from '~/flash';
import createDefaultClient from '~/lib/graphql';
@@ -78,7 +73,6 @@ export default () => {
components: {
BoardContent,
BoardSidebar,
- BoardAddIssuesModal,
BoardSettingsSidebar: () => import('~/boards/components/board_settings_sidebar.vue'),
},
provide: {
@@ -316,49 +310,7 @@ export default () => {
boardConfigToggle(boardsStore);
- const issueBoardsModal = document.getElementById('js-add-issues-btn');
-
- if (issueBoardsModal && gon.features.addIssuesButton) {
- // eslint-disable-next-line no-new
- new Vue({
- el: issueBoardsModal,
- mixins: [modalMixin],
- data() {
- return {
- modal: ModalStore.store,
- store: boardsStore.state,
- ...getBoardsModalData(),
- canAdminList: this.$options.el.hasAttribute('data-can-admin-list'),
- };
- },
- computed: {
- disabled() {
- if (!this.store || !this.store.lists) {
- return true;
- }
- return !this.store.lists.filter((list) => !list.preset).length;
- },
- },
- methods: {
- openModal() {
- if (!this.disabled) {
- this.toggleModal(true);
- }
- },
- },
- render(createElement) {
- return createElement(BoardExtraActions, {
- props: {
- canAdminList: this.$options.el.hasAttribute('data-can-admin-list'),
- openModal: this.openModal,
- disabled: this.disabled,
- },
- });
- },
- });
- }
-
- toggleFocusMode(ModalStore, boardsStore);
+ toggleFocusMode();
toggleLabels();
if (gon.licensed_features?.swimlanes) {
diff --git a/app/assets/javascripts/boards/mixins/modal_footer.js b/app/assets/javascripts/boards/mixins/modal_footer.js
deleted file mode 100644
index ff8b4c56321..00000000000
--- a/app/assets/javascripts/boards/mixins/modal_footer.js
+++ /dev/null
@@ -1 +0,0 @@
-export default {};
diff --git a/app/assets/javascripts/boards/mixins/modal_mixins.js b/app/assets/javascripts/boards/mixins/modal_mixins.js
deleted file mode 100644
index 6c97e1629bf..00000000000
--- a/app/assets/javascripts/boards/mixins/modal_mixins.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import ModalStore from '../stores/modal_store';
-
-export default {
- methods: {
- toggleModal(toggle) {
- ModalStore.store.showAddIssuesModal = toggle;
- },
- changeTab(tab) {
- ModalStore.store.activeTab = tab;
- },
- },
-};
diff --git a/app/assets/javascripts/boards/stores/modal_store.js b/app/assets/javascripts/boards/stores/modal_store.js
deleted file mode 100644
index 8a8fa61361c..00000000000
--- a/app/assets/javascripts/boards/stores/modal_store.js
+++ /dev/null
@@ -1,95 +0,0 @@
-class ModalStore {
- constructor() {
- this.store = {
- columns: 3,
- issues: [],
- issuesCount: false,
- selectedIssues: [],
- showAddIssuesModal: false,
- activeTab: 'all',
- selectedList: null,
- searchTerm: '',
- loading: false,
- loadingNewPage: false,
- filterLoading: false,
- page: 1,
- perPage: 50,
- filter: {
- path: '',
- },
- };
- }
-
- selectedCount() {
- return this.getSelectedIssues().length;
- }
-
- toggleIssue(issueObj) {
- const issue = issueObj;
- const { selected } = issue;
-
- issue.selected = !selected;
-
- if (!selected) {
- this.addSelectedIssue(issue);
- } else {
- this.removeSelectedIssue(issue);
- }
- }
-
- toggleAll() {
- const select = this.selectedCount() !== this.store.issues.length;
-
- this.store.issues.forEach((issue) => {
- const issueUpdate = issue;
-
- if (issueUpdate.selected !== select) {
- issueUpdate.selected = select;
-
- if (select) {
- this.addSelectedIssue(issue);
- } else {
- this.removeSelectedIssue(issue);
- }
- }
- });
- }
-
- getSelectedIssues() {
- return this.store.selectedIssues.filter((issue) => issue.selected);
- }
-
- addSelectedIssue(issue) {
- const index = this.selectedIssueIndex(issue);
-
- if (index === -1) {
- this.store.selectedIssues.push(issue);
- }
- }
-
- removeSelectedIssue(issue, forcePurge = false) {
- if (this.store.activeTab === 'all' || forcePurge) {
- this.store.selectedIssues = this.store.selectedIssues.filter(
- (fIssue) => fIssue.id !== issue.id,
- );
- }
- }
-
- purgeUnselectedIssues() {
- this.store.selectedIssues.forEach((issue) => {
- if (!issue.selected) {
- this.removeSelectedIssue(issue, true);
- }
- });
- }
-
- selectedIssueIndex(issue) {
- return this.store.selectedIssues.indexOf(issue);
- }
-
- findSelectedIssue(issue) {
- return this.store.selectedIssues.filter((filteredIssue) => filteredIssue.id === issue.id)[0];
- }
-}
-
-export default new ModalStore();
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 2503648e6f5..fb88e48c9a6 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -1,4 +1,4 @@
-/* eslint-disable no-useless-return, func-names, no-underscore-dangle, no-new, consistent-return, no-shadow, no-param-reassign, no-lonely-if, dot-notation, no-empty */
+/* eslint-disable func-names, no-underscore-dangle, no-new, consistent-return, no-shadow, no-param-reassign, no-lonely-if, no-empty */
/* global Issuable */
/* global ListLabel */
@@ -7,7 +7,6 @@ import { difference, isEqual, escape, sortBy, template, union } from 'lodash';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
import { isScopedLabel } from '~/lib/utils/common_utils';
import boardsStore from './boards/stores/boards_store';
-import ModalStore from './boards/stores/modal_store';
import CreateLabelDropdown from './create_label';
import { deprecatedCreateFlash as flash } from './flash';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
@@ -361,21 +360,7 @@ export default class LabelsSelect {
return;
}
- let boardsModel;
- if ($dropdown.closest('.add-issues-modal').length) {
- boardsModel = ModalStore.store.filter;
- }
-
- if (boardsModel) {
- if (label.isAny) {
- boardsModel['label_name'] = [];
- } else if ($el.hasClass('is-active')) {
- boardsModel['label_name'].push(label.title);
- }
-
- e.preventDefault();
- return;
- } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
+ if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
if (!$dropdown.hasClass('js-multiselect')) {
selectedLabel = label.title;
return Issuable.filterResults($dropdown.closest('form'));
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index f4b60fc0961..b992eaff779 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -11,7 +11,6 @@ import boardsStore, {
boardStoreIssueSet,
boardStoreIssueDelete,
} from './boards/stores/boards_store';
-import ModalStore from './boards/stores/modal_store';
import axios from './lib/utils/axios_utils';
import { timeFor, parsePikadayDate, dateInWords } from './lib/utils/datetime_utility';
@@ -211,7 +210,7 @@ export default class MilestoneSelect {
const { e } = clickEvent;
let selected = clickEvent.selectedObj;
- let data, modalStoreFilter;
+ let data;
if (!selected) return;
if (options.handleClick) {
@@ -234,14 +233,7 @@ export default class MilestoneSelect {
return;
}
- if ($dropdown.closest('.add-issues-modal').length) {
- modalStoreFilter = ModalStore.store.filter;
- }
-
- if (modalStoreFilter) {
- modalStoreFilter[$dropdown.data('fieldName')] = selected.name;
- e.preventDefault();
- } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
+ if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
return Issuable.filterResults($dropdown.closest('form'));
} else if ($dropdown.hasClass('js-filter-submit')) {
return $dropdown.closest('form').submit();
diff --git a/app/assets/javascripts/users_select/index.js b/app/assets/javascripts/users_select/index.js
index e1a4a74b982..498d7b74093 100644
--- a/app/assets/javascripts/users_select/index.js
+++ b/app/assets/javascripts/users_select/index.js
@@ -11,7 +11,6 @@ import {
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
import { isUserBusy } from '~/set_status_modal/utils';
import { fixTitle, dispose } from '~/tooltips';
-import ModalStore from '../boards/stores/modal_store';
import axios from '../lib/utils/axios_utils';
import { parseBoolean, spriteIcon } from '../lib/utils/common_utils';
import { loadCSSFile } from '../lib/utils/css_utils';
@@ -504,9 +503,7 @@ function UsersSelect(currentUser, els, options = {}) {
}
return;
}
- if ($el.closest('.add-issues-modal').length) {
- ModalStore.store.filter[$dropdown.data('fieldName')] = user.id;
- } else if (handleClick) {
+ if (handleClick) {
e.preventDefault();
handleClick(user, isMarking);
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
diff --git a/app/assets/stylesheets/page_bundles/boards.scss b/app/assets/stylesheets/page_bundles/boards.scss
index 61cbfec4597..4ec9e414749 100644
--- a/app/assets/stylesheets/page_bundles/boards.scss
+++ b/app/assets/stylesheets/page_bundles/boards.scss
@@ -401,99 +401,6 @@
}
}
-.add-issues-modal {
- background-color: rgba($black, 0.3);
- z-index: 9999;
-}
-
-.add-issues-container {
- width: 90vw;
- height: 85vh;
- max-width: 1100px;
- min-height: 500px;
- padding: 25px 15px 0;
- background-color: var(--white, $white);
- box-shadow: 0 2px 12px rgba(var(--black, $black), 0.5);
-
- .empty-state {
- &.add-issues-empty-state-filter {
- flex-direction: column;
- justify-content: center;
- }
-
- .svg-content {
- margin-top: -40px;
- }
- }
-}
-
-.add-issues-header {
- margin: -25px -15px -5px;
- border-bottom: 1px solid $border-color;
- border-top-right-radius: $border-radius-default;
- border-top-left-radius: $border-radius-default;
-
- > h2 {
- font-size: 18px;
- }
-}
-
-.add-issues-list-column {
- width: 100%;
-
- @include media-breakpoint-up(sm) {
- width: 50%;
- }
-
- @include media-breakpoint-up(md) {
- width: (100% / 3);
- }
-}
-
-.add-issues-list {
- padding-top: 3px;
- margin-left: -$gl-vert-padding;
- margin-right: -$gl-vert-padding;
- overflow-y: scroll;
-
- .board-card-parent {
- padding: 0 5px 5px;
- }
-
- .board-card {
- border: 1px solid var(--gray-900, $gray-900);
- box-shadow: 0 1px 2px rgba(var(--black, $black), 0.4);
- cursor: pointer;
- }
-}
-
-.add-issues-footer {
- margin: auto -15px 0;
- padding-left: 15px;
- padding-right: 15px;
- border-bottom-right-radius: $border-radius-default;
- border-bottom-left-radius: $border-radius-default;
-}
-
-.add-issues-footer-to-list {
- padding-left: $gl-vert-padding;
- padding-right: $gl-vert-padding;
- line-height: $input-height;
-}
-
-.issue-card-selected {
- position: absolute;
- right: -3px;
- top: -3px;
- width: 17px;
- background-color: var(--blue-500, $blue-500);
- color: $white;
- border: 1px solid var(--blue-600, $blue-600);
- font-size: 9px;
- line-height: 15px;
- border-radius: 50%;
-}
-
.board-card-info {
color: var(--gray-500, $gray-500);
white-space: nowrap;
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 5a8090fc4bd..349649c7b35 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -8,7 +8,6 @@ class Projects::BoardsController < Projects::ApplicationController
before_action :authorize_read_board!, only: [:index, :show]
before_action :assign_endpoint_vars
before_action do
- push_frontend_feature_flag(:add_issues_button)
push_frontend_feature_flag(:swimlanes_buffered_rendering, project, default_enabled: :yaml)
push_frontend_feature_flag(:graphql_board_lists, project, default_enabled: :yaml)
end
diff --git a/app/helpers/timeboxes_helper.rb b/app/helpers/timeboxes_helper.rb
index bbf8cf7dac3..e034a985b50 100644
--- a/app/helpers/timeboxes_helper.rb
+++ b/app/helpers/timeboxes_helper.rb
@@ -115,17 +115,6 @@ module TimeboxesHelper
end
end
- def milestones_filter_dropdown_path
- project = @target_project || @project
- if project
- project_milestones_path(project, :json)
- elsif @group
- group_milestones_path(@group, :json)
- else
- dashboard_milestones_path(:json)
- end
- end
-
def milestone_time_for(date, date_type)
title = date_type == :start ? "Start date" : "End date"
diff --git a/app/models/service.rb b/app/models/service.rb
index c49e0869b21..b0bad81d584 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -413,6 +413,10 @@ class Service < ApplicationRecord
!instance? && !group_id
end
+ def project_level?
+ project_id.present?
+ end
+
def parent
project || group
end
diff --git a/app/views/projects/services/mattermost_slash_commands/_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_help.html.haml
index 1005d9f7990..ae0f6c9ef18 100644
--- a/app/views/projects/services/mattermost_slash_commands/_help.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_help.html.haml
@@ -10,8 +10,8 @@
%p.inline
= s_("MattermostService|See list of available commands in Mattermost after setting up this service, by entering")
%kbd.inline /&lt;trigger&gt; help
- - unless enabled || @service.template?
+ - if !enabled && @service.project_level?
= render 'projects/services/mattermost_slash_commands/detailed_help', subject: @service
-- if enabled && !@service.template?
+- if enabled && @service.project_level?
= render 'projects/services/mattermost_slash_commands/installation_info', subject: @service
diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml
index 67c43bd2f33..a8fb0d55c0d 100644
--- a/app/views/projects/services/slack_slash_commands/_help.html.haml
+++ b/app/views/projects/services/slack_slash_commands/_help.html.haml
@@ -11,7 +11,7 @@
%p.inline
= s_("SlackService|See list of available commands in Slack after setting up this service, by entering")
%kbd.inline /&lt;command&gt; help
- - unless @service.template?
+ - if @service.project_level?
%p= _("To set up this service:")
%ul.list-unstyled.indent-list
%li
diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index 8c0893adaaa..dfb1aded461 100644
--- a/app/views/shared/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -13,10 +13,6 @@
- page_title("#{board.name}", _("Boards"))
- add_page_specific_style 'page_bundles/boards'
-- content_for :page_specific_javascripts do
-
- %script#js-board-modal-filter{ type: "text/x-template" }= render "shared/issuable/search_bar", type: :boards_modal, show_sorting_dropdown: false
-
= render 'shared/issuable/search_bar', type: :boards, board: board
#board-app.boards-app.position-relative{ "v-cloak" => "true", data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" }
%board-content{ "v-cloak" => "true",
@@ -27,9 +23,3 @@
data: { qa_selector: "boards_list" } }
= render "shared/boards/components/sidebar", group: group
%board-settings-sidebar{ ":can-admin-list" => can_admin_list }
- - if @project
- %board-add-issues-modal{ "new-issue-path" => new_project_issue_path(@project),
- "milestone-path" => milestones_filter_dropdown_path,
- "label-path" => labels_filter_path_with_defaults,
- "empty-state-svg" => image_path('illustrations/issues.svg'),
- ":project-id" => @project.id }
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 4233dfec698..84b091b6ca3 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -3,16 +3,15 @@
- show_sorting_dropdown = local_assigns.fetch(:show_sorting_dropdown, true)
- disable_target_branch = local_assigns.fetch(:disable_target_branch, false)
- placeholder = local_assigns[:placeholder] || _('Search or filter results...')
-- is_not_boards_modal_or_productivity_analytics = type != :boards_modal && type != :productivity_analytics
-- block_css_class = is_not_boards_modal_or_productivity_analytics ? 'row-content-block second-block' : ''
+- block_css_class = type != :productivity_analytics ? 'row-content-block second-block' : ''
- is_epic_board = board&.to_type == "EpicBoard"
- if is_epic_board
- user_can_admin_list = can?(current_user, :admin_epic_board_list, board.resource_parent)
- elsif board
- user_can_admin_list = can?(current_user, :admin_issue_board_list, board.resource_parent)
-.issues-filters{ class: ("w-100" if type == :boards_modal) }
- .issues-details-filters.filtered-search-block.d-flex.flex-column.flex-lg-row{ class: block_css_class, "v-pre" => type == :boards_modal }
+.issues-filters
+ .issues-details-filters.filtered-search-block.d-flex.flex-column.flex-lg-row{ class: block_css_class }
.d-flex.flex-column.flex-md-row.flex-grow-1.mb-lg-0.mb-md-2.mb-sm-0.w-100
- if type == :boards
= render "shared/boards/switcher", board: board
@@ -27,7 +26,7 @@
- else
.issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
.filtered-search-box
- - if type != :boards_modal && type != :boards
+ - if 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",
@@ -208,8 +207,6 @@
.js-create-column-trigger{ data: board_list_data }
- else
= render 'shared/issuable/board_create_list_dropdown', board: board
- - if @project
- #js-add-issues-btn{ data: { can_admin_list: can?(current_user, :admin_issue_board_list, @project) } }
#js-toggle-focus-btn
- - elsif is_not_boards_modal_or_productivity_analytics && show_sorting_dropdown
+ - elsif type != :productivity_analytics && show_sorting_dropdown
= render 'shared/issuable/sort_dropdown'
diff --git a/changelogs/unreleased/298854-gitlab-returns-error-500-when-visiting-admin-area-settings-integra.yml b/changelogs/unreleased/298854-gitlab-returns-error-500-when-visiting-admin-area-settings-integra.yml
new file mode 100644
index 00000000000..a49eb6f1c3e
--- /dev/null
+++ b/changelogs/unreleased/298854-gitlab-returns-error-500-when-visiting-admin-area-settings-integra.yml
@@ -0,0 +1,5 @@
+---
+title: Hide project-specific views on group / instance level integrations
+merge_request: 57381
+author:
+type: fixed
diff --git a/changelogs/unreleased/remove-disabled-add-issues-modal.yml b/changelogs/unreleased/remove-disabled-add-issues-modal.yml
new file mode 100644
index 00000000000..a74466d25f1
--- /dev/null
+++ b/changelogs/unreleased/remove-disabled-add-issues-modal.yml
@@ -0,0 +1,5 @@
+---
+title: Remove add issues modal from issue boards (this has been disabled since 13.6)
+merge_request: 57329
+author:
+type: removed
diff --git a/config/feature_flags/development/add_issues_button.yml b/config/feature_flags/development/add_issues_button.yml
deleted file mode 100644
index 12a6ef61bba..00000000000
--- a/config/feature_flags/development/add_issues_button.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: add_issues_button
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47898
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/292803
-milestone: '13.6'
-type: development
-group: group::project management
-default_enabled: false
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index 898857b61c0..e21ab54267c 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -450,7 +450,6 @@ The feature is enabled by default when you use group issue boards with epic swim
- [Create a new list](#create-a-new-list).
- [Remove an existing list](#remove-a-list).
-- [Add issues to a list](#add-issues-to-a-list).
- [Remove an issue from a list](#remove-an-issue-from-a-list).
- [Filter issues](#filter-issues) that appear across your issue board.
- [Create workflows](#create-workflows).
@@ -489,31 +488,19 @@ To remove a list from an issue board:
1. Select **Remove list**. A confirmation dialog appears.
1. Select **OK**.
-### Add issues to a list **(FREE SELF)**
+### Add issues to a list
-> - Feature flag [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47898) in GitLab 13.7.
-> - It's [deployed behind a feature flag](../feature_flags.md), disabled by default.
-> - It's disabled on GitLab.com.
-> - It's recommended for production use.
-> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-adding-issues-to-the-list). **(FREE SELF)**
+> The **Add issues** button was [removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57329) in GitLab 13.11.
-You can add issues to a list in a project issue board by clicking the **Add issues** button
-in the top right corner of the issue board. This opens up a modal
-window where you can see all the issues that do not belong to any list.
+If your board is scoped to one or more attributes, go to the issues you want to add and apply the
+same attributes as your board scope.
-Select one or more issues by clicking the cards and then click **Add issues**
-to add them to the selected list. You can limit the issues you want to add to
-the list by filtering by the following:
+For example, to add an issue to a list scoped to the `Doing` label, in a group issue board:
-- Assignee
-- Author
-- Epic
-- Iteration ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118742) in GitLab 13.6)
-- Label
-- Milestone
-- My Reaction
-- Release
-- Weight
+1. Go to an issue in the group or one of the subgroups or projects.
+1. Add the `Doing` label.
+
+The issue should now show in the `Doing` list on your issue board.
### Remove an issue from a list
@@ -657,24 +644,6 @@ To disable it:
Feature.disable(:graphql_board_lists)
```
-## Enable or disable adding issues to the list **(FREE SELF)**
-
-Adding issues to the list is deployed behind a feature flag that is **disabled by default**.
-[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
-can enable it.
-
-To enable it:
-
-```ruby
-Feature.enable(:add_issues_button)
-```
-
-To disable it:
-
-```ruby
-Feature.disable(:add_issues_button)
-```
-
### Enable or disable new add list form **(FREE SELF)**
The new form for adding lists is under development and not ready for production use. It is
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2b62445f182..f58c1811735 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1773,11 +1773,6 @@ msgstr ""
msgid "Add \"%{value}\""
msgstr ""
-msgid "Add %d issue"
-msgid_plural "Add %d issues"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "Add %{linkStart}assets%{linkEnd} to your Release. GitLab automatically includes read-only assets, like source code and release evidence."
msgstr ""
@@ -1928,9 +1923,6 @@ msgstr ""
msgid "Add image comment"
msgstr ""
-msgid "Add issues"
-msgstr ""
-
msgid "Add italic text"
msgstr ""
@@ -3676,9 +3668,6 @@ msgstr ""
msgid "An issue already exists"
msgstr ""
-msgid "An issue can be a bug, a todo or a feature request that needs to be discussed in a project. Besides, issues are searchable and filterable."
-msgstr ""
-
msgid "An issue title is required"
msgstr ""
@@ -10581,9 +10570,6 @@ msgstr ""
msgid "Descriptive label"
msgstr ""
-msgid "Deselect all"
-msgstr ""
-
msgid "Design Management files and data"
msgstr ""
@@ -12944,9 +12930,6 @@ msgstr ""
msgid "Failed to update issue status"
msgstr ""
-msgid "Failed to update issues, please try again."
-msgstr ""
-
msgid "Failed to update the Canary Ingress."
msgstr ""
@@ -14452,9 +14435,6 @@ msgstr ""
msgid "Go back (while searching for files)"
msgstr ""
-msgid "Go back to %{tagStart}Open issues%{tagEnd} and select some issues to add to your board."
-msgstr ""
-
msgid "Go full screen"
msgstr ""
@@ -21670,9 +21650,6 @@ msgstr ""
msgid "Open in your IDE"
msgstr ""
-msgid "Open issues"
-msgstr ""
-
msgid "Open raw"
msgstr ""
@@ -22912,9 +22889,6 @@ msgstr ""
msgid "Please add a comment in the text area above"
msgstr ""
-msgid "Please add a list to your board first"
-msgstr ""
-
msgid "Please check the configuration file for this chart"
msgstr ""
@@ -30602,9 +30576,6 @@ msgstr ""
msgid "There are no issues to show"
msgstr ""
-msgid "There are no issues to show."
-msgstr ""
-
msgid "There are no issues with the selected labels"
msgstr ""
@@ -34997,12 +34968,6 @@ msgstr ""
msgid "You have successfully purchased a %{plan} plan subscription for %{seats}. You’ll receive a receipt via email."
msgstr ""
-msgid "You haven't added any issues to your project yet"
-msgstr ""
-
-msgid "You haven't selected any issues yet"
-msgstr ""
-
msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
msgstr ""
@@ -36912,9 +36877,6 @@ msgstr ""
msgid "to join %{source_name}"
msgstr ""
-msgid "to list"
-msgstr ""
-
msgid "toggle collapse"
msgstr ""
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
deleted file mode 100644
index ff9e0b9d054..00000000000
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ /dev/null
@@ -1,271 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'Issue Boards add issue modal', :js do
- let(:project) { create(:project, :public) }
- let(:board) { create(:board, project: project) }
- let(:user) { create(:user) }
- let!(:planning) { create(:label, project: project, name: 'Planning') }
- let!(:label) { create(:label, project: project) }
- let!(:list1) { create(:list, board: board, label: planning, position: 0) }
- let!(:list2) { create(:list, board: board, label: label, position: 1) }
- let!(:issue) { create(:issue, project: project, title: 'abc', description: 'def') }
- let!(:issue2) { create(:issue, project: project, title: 'hij', description: 'klm') }
-
- before do
- stub_feature_flags(graphql_board_lists: false)
- project.add_maintainer(user)
-
- sign_in(user)
-
- visit project_board_path(project, board)
- wait_for_requests
- end
-
- it 'resets filtered search state' do
- visit project_board_path(project, board, search: 'testing')
-
- wait_for_requests
-
- click_button('Add issues')
-
- page.within('.add-issues-modal') do
- expect(find('.form-control').value).to eq('')
- expect(page).to have_selector('.clear-search', visible: false)
- expect(find('.form-control')[:placeholder]).to eq('Search or filter results...')
- end
- end
-
- context 'modal interaction' do
- before do
- stub_feature_flags(add_issues_button: true)
- end
-
- it 'opens modal' do
- click_button('Add issues')
-
- expect(page).to have_selector('.add-issues-modal')
- end
-
- it 'closes modal' do
- click_button('Add issues')
-
- page.within('.add-issues-modal') do
- find('.close').click
- end
-
- expect(page).not_to have_selector('.add-issues-modal')
- end
-
- it 'closes modal if cancel button clicked' do
- click_button('Add issues')
-
- page.within('.add-issues-modal') do
- click_button 'Cancel'
- end
-
- expect(page).not_to have_selector('.add-issues-modal')
- end
-
- it 'does not show tooltip on add issues button' do
- button = page.find('.filter-dropdown-container button', text: 'Add issues')
-
- expect(button[:title]).not_to eq("Please add a list to your board first")
- end
- end
-
- context 'issues list' do
- before do
- stub_feature_flags(add_issues_button: true)
- click_button('Add issues')
-
- wait_for_requests
- end
-
- it 'loads issues' do
- page.within('.add-issues-modal') do
- page.within('.gl-tabs') do
- expect(page).to have_content('2')
- end
-
- expect(page).to have_selector('.board-card', count: 2)
- end
- end
-
- it 'shows selected issues tab and empty state message' do
- page.within('.add-issues-modal') do
- click_link 'Selected issues'
-
- expect(page).not_to have_selector('.board-card')
- expect(page).to have_content("Go back to Open issues and select some issues to add to your board.")
- end
- end
-
- context 'list dropdown' do
- it 'resets after deleting list' do
- page.within('.add-issues-modal') do
- expect(find('.add-issues-footer')).to have_button(planning.title)
-
- click_button 'Cancel'
- end
-
- page.within(find('.board:nth-child(2)')) do
- find('button[title="List settings"]').click
- end
-
- page.within(find('.js-board-settings-sidebar')) do
- accept_confirm { find('[data-testid="remove-list"]').click }
- end
-
- click_button('Add issues')
-
- wait_for_requests
-
- page.within('.add-issues-modal') do
- expect(find('.add-issues-footer')).not_to have_button(planning.title)
- expect(find('.add-issues-footer')).to have_button(label.title)
- end
- end
- end
-
- context 'search' do
- it 'returns issues' do
- page.within('.add-issues-modal') do
- find('.form-control').native.send_keys(issue.title)
- find('.form-control').native.send_keys(:enter)
-
- wait_for_requests
-
- expect(page).to have_selector('.board-card', count: 1)
- end
- end
-
- it 'returns no issues' do
- page.within('.add-issues-modal') do
- find('.form-control').native.send_keys('testing search')
- find('.form-control').native.send_keys(:enter)
-
- wait_for_requests
-
- expect(page).not_to have_selector('.board-card')
- expect(page).not_to have_content("You haven't added any issues to your project yet")
- end
- end
- end
-
- context 'selecting issues' do
- it 'selects single issue' do
- page.within('.add-issues-modal') do
- first('.board-card .board-card-number').click
-
- page.within('.gl-tabs') do
- expect(page).to have_content('Selected issues 1')
- end
- end
- end
-
- it 'changes button text' do
- page.within('.add-issues-modal') do
- first('.board-card .board-card-number').click
-
- expect(first('.add-issues-footer .btn')).to have_content('Add 1 issue')
- end
- end
-
- it 'changes button text with plural' do
- page.within('.add-issues-modal') do
- all('.board-card .js-board-card-number-container').each do |el|
- el.click
- end
-
- expect(first('.add-issues-footer .btn')).to have_content('Add 2 issues')
- end
- end
-
- it 'shows only selected issues on selected tab' do
- page.within('.add-issues-modal') do
- first('.board-card .board-card-number').click
-
- click_link 'Selected issues'
-
- expect(page).to have_selector('.board-card', count: 1)
- end
- end
-
- it 'selects all issues' do
- page.within('.add-issues-modal') do
- click_button 'Select all'
-
- expect(page).to have_selector('.is-active', count: 2)
- end
- end
-
- it 'deselects all issues' do
- page.within('.add-issues-modal') do
- click_button 'Select all'
-
- expect(page).to have_selector('.is-active', count: 2)
-
- click_button 'Deselect all'
-
- expect(page).not_to have_selector('.is-active')
- end
- end
-
- it "selects all that aren't already selected" do
- page.within('.add-issues-modal') do
- first('.board-card .board-card-number').click
-
- expect(page).to have_selector('.is-active', count: 1)
-
- click_button 'Select all'
-
- expect(page).to have_selector('.is-active', count: 2)
- end
- end
-
- it 'unselects from selected tab' do
- page.within('.add-issues-modal') do
- first('.board-card .board-card-number').click
-
- click_link 'Selected issues'
-
- first('.board-card .board-card-number').click
-
- expect(page).not_to have_selector('.is-active')
- end
- end
- end
-
- context 'adding issues' do
- it 'adds to board' do
- page.within('.add-issues-modal') do
- first('.board-card .board-card-number').click
-
- click_button 'Add 1 issue'
- end
-
- page.within(find('.board:nth-child(2)')) do
- expect(page).to have_selector('.board-card')
- end
- end
-
- it 'adds to second list' do
- page.within('.add-issues-modal') do
- first('.board-card .board-card-number').click
-
- click_button planning.title
-
- click_link label.title
-
- click_button 'Add 1 issue'
- end
-
- page.within(find('.board:nth-child(3)')) do
- expect(page).to have_selector('.board-card')
- end
- end
- end
- end
-end
diff --git a/spec/features/boards/focus_mode_spec.rb b/spec/features/boards/focus_mode_spec.rb
index b1684ad69a6..2bd1e625236 100644
--- a/spec/features/boards/focus_mode_spec.rb
+++ b/spec/features/boards/focus_mode_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe 'Issue Boards focus mode', :js do
wait_for_requests
end
- it 'shows focus mode button to guest users' do
- expect(page).to have_selector('.board-extra-actions .js-focus-mode-btn')
+ it 'shows focus mode button to anonymous users' do
+ expect(page).to have_selector('.js-focus-mode-btn')
end
end
diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb
deleted file mode 100644
index d2b7686a9e2..00000000000
--- a/spec/features/boards/modal_filter_spec.rb
+++ /dev/null
@@ -1,230 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'Issue Boards add issue modal filtering', :js do
- let(:project) { create(:project, :public) }
- let(:board) { create(:board, project: project) }
- let(:planning) { create(:label, project: project, name: 'Planning') }
- let!(:list1) { create(:list, board: board, label: planning, position: 0) }
- let(:user) { create(:user) }
- let(:user2) { create(:user) }
- let!(:issue1) { create(:issue, project: project) }
-
- before do
- stub_feature_flags(graphql_board_lists: false)
- stub_feature_flags(add_issues_button: true)
- project.add_maintainer(user)
-
- sign_in(user)
- end
-
- it 'shows empty state when no results found' do
- visit_board
-
- page.within('.add-issues-modal') do
- find('.form-control').native.send_keys('testing empty state')
- find('.form-control').native.send_keys(:enter)
-
- wait_for_requests
-
- expect(page).to have_content('There are no issues to show.')
- end
- end
-
- it 'restores filters when closing' do
- visit_board
-
- set_filter('milestone')
- click_filter_link('Upcoming')
- submit_filter
-
- page.within('.add-issues-modal') do
- wait_for_requests
-
- expect(page).to have_selector('.board-card', count: 0)
-
- click_button 'Cancel'
- end
-
- click_button('Add issues')
-
- page.within('.add-issues-modal') do
- wait_for_requests
-
- expect(page).to have_selector('.board-card', count: 1)
- end
- end
-
- it 'resotres filters after clicking clear button' do
- visit_board
-
- set_filter('milestone')
- click_filter_link('Upcoming')
- submit_filter
-
- page.within('.add-issues-modal') do
- wait_for_requests
-
- expect(page).to have_selector('.board-card', count: 0)
-
- find('.clear-search').click
-
- wait_for_requests
-
- expect(page).to have_selector('.board-card', count: 1)
- end
- end
-
- context 'author' do
- let!(:issue) { create(:issue, project: project, author: user2) }
-
- before do
- project.add_developer(user2)
-
- visit_board
- end
-
- it 'filters by selected user' do
- set_filter('author')
- click_filter_link(user2.name)
- submit_filter
-
- page.within('.add-issues-modal') do
- wait_for_requests
-
- expect(page).to have_selector('.js-visual-token', text: user2.name)
- expect(page).to have_selector('.board-card', count: 1)
- end
- end
- end
-
- context 'assignee' do
- let!(:issue) { create(:issue, project: project, assignees: [user2]) }
-
- before do
- project.add_developer(user2)
-
- visit_board
- end
-
- it 'filters by unassigned' do
- set_filter('assignee')
- click_filter_link('None')
- submit_filter
-
- page.within('.add-issues-modal') do
- wait_for_requests
-
- expect(page).to have_selector('.js-visual-token', text: 'None')
- expect(page).to have_selector('.board-card', count: 1)
- end
- end
-
- it 'filters by selected user' do
- set_filter('assignee')
- click_filter_link(user2.name)
- submit_filter
-
- page.within('.add-issues-modal') do
- wait_for_requests
-
- expect(page).to have_selector('.js-visual-token', text: user2.name)
- expect(page).to have_selector('.board-card', count: 1)
- end
- end
- end
-
- context 'milestone' do
- let(:milestone) { create(:milestone, project: project) }
- let!(:issue) { create(:issue, project: project, milestone: milestone) }
-
- before do
- visit_board
- end
-
- it 'filters by upcoming milestone' do
- set_filter('milestone')
- click_filter_link('Upcoming')
- submit_filter
-
- page.within('.add-issues-modal') do
- wait_for_requests
-
- expect(page).to have_selector('.js-visual-token', text: 'Upcoming')
- expect(page).to have_selector('.board-card', count: 0)
- end
- end
-
- it 'filters by selected milestone' do
- set_filter('milestone')
- click_filter_link(milestone.name)
- submit_filter
-
- page.within('.add-issues-modal') do
- wait_for_requests
-
- expect(page).to have_selector('.js-visual-token', text: milestone.name)
- expect(page).to have_selector('.board-card', count: 1)
- end
- end
- end
-
- context 'label' do
- let(:label) { create(:label, project: project) }
- let!(:issue) { create(:labeled_issue, project: project, labels: [label]) }
-
- before do
- visit_board
- end
-
- it 'filters by no label' do
- set_filter('label')
- click_filter_link('None')
- submit_filter
-
- page.within('.add-issues-modal') do
- wait_for_requests
-
- expect(page).to have_selector('.js-visual-token', text: 'None')
- expect(page).to have_selector('.board-card', count: 1)
- end
- end
-
- it 'filters by label' do
- set_filter('label')
- click_filter_link(label.title)
- submit_filter
-
- page.within('.add-issues-modal') do
- wait_for_requests
-
- expect(page).to have_selector('.js-visual-token', text: label.title)
- expect(page).to have_selector('.board-card', count: 1)
- end
- end
- end
-
- def visit_board
- visit project_board_path(project, board)
- wait_for_requests
-
- click_button('Add issues')
- end
-
- def set_filter(type, text = '')
- find('.add-issues-modal .filtered-search').native.send_keys("#{type}:=#{text}")
- end
-
- def submit_filter
- find('.add-issues-modal .filtered-search').native.send_keys(:enter)
- end
-
- def click_filter_link(link_text)
- page.within('.add-issues-modal .filtered-search-box') do
- expect(page).to have_button(link_text)
-
- click_button(link_text)
- end
- end
-end
diff --git a/spec/frontend/boards/modal_store_spec.js b/spec/frontend/boards/modal_store_spec.js
deleted file mode 100644
index 5b5ae4b6556..00000000000
--- a/spec/frontend/boards/modal_store_spec.js
+++ /dev/null
@@ -1,134 +0,0 @@
-/* global ListIssue */
-
-import '~/boards/models/label';
-import '~/boards/models/assignee';
-import '~/boards/models/issue';
-import '~/boards/models/list';
-import Store from '~/boards/stores/modal_store';
-
-describe('Modal store', () => {
- let issue;
- let issue2;
-
- beforeEach(() => {
- // Set up default state
- Store.store.issues = [];
- Store.store.selectedIssues = [];
-
- issue = new ListIssue({
- title: 'Testing',
- id: 1,
- iid: 1,
- confidential: false,
- labels: [],
- assignees: [],
- });
- issue2 = new ListIssue({
- title: 'Testing',
- id: 2,
- iid: 2,
- confidential: false,
- labels: [],
- assignees: [],
- });
- Store.store.issues.push(issue);
- Store.store.issues.push(issue2);
- });
-
- it('returns selected count', () => {
- expect(Store.selectedCount()).toBe(0);
- });
-
- it('toggles the issue as selected', () => {
- Store.toggleIssue(issue);
-
- expect(issue.selected).toBe(true);
- expect(Store.selectedCount()).toBe(1);
- });
-
- it('toggles the issue as un-selected', () => {
- Store.toggleIssue(issue);
- Store.toggleIssue(issue);
-
- expect(issue.selected).toBe(false);
- expect(Store.selectedCount()).toBe(0);
- });
-
- it('toggles all issues as selected', () => {
- Store.toggleAll();
-
- expect(issue.selected).toBe(true);
- expect(issue2.selected).toBe(true);
- expect(Store.selectedCount()).toBe(2);
- });
-
- it('toggles all issues as un-selected', () => {
- Store.toggleAll();
- Store.toggleAll();
-
- expect(issue.selected).toBe(false);
- expect(issue2.selected).toBe(false);
- expect(Store.selectedCount()).toBe(0);
- });
-
- it('toggles all if a single issue is selected', () => {
- Store.toggleIssue(issue);
- Store.toggleAll();
-
- expect(issue.selected).toBe(true);
- expect(issue2.selected).toBe(true);
- expect(Store.selectedCount()).toBe(2);
- });
-
- it('adds issue to selected array', () => {
- issue.selected = true;
- Store.addSelectedIssue(issue);
-
- expect(Store.selectedCount()).toBe(1);
- });
-
- it('removes issue from selected array', () => {
- Store.addSelectedIssue(issue);
- Store.removeSelectedIssue(issue);
-
- expect(Store.selectedCount()).toBe(0);
- });
-
- it('returns selected issue index if present', () => {
- Store.toggleIssue(issue);
-
- expect(Store.selectedIssueIndex(issue)).toBe(0);
- });
-
- it('returns -1 if issue is not selected', () => {
- expect(Store.selectedIssueIndex(issue)).toBe(-1);
- });
-
- it('finds the selected issue', () => {
- Store.toggleIssue(issue);
-
- expect(Store.findSelectedIssue(issue)).toBe(issue);
- });
-
- it('does not find a selected issue', () => {
- expect(Store.findSelectedIssue(issue)).toBe(undefined);
- });
-
- it('does not remove from selected issue if tab is not all', () => {
- Store.store.activeTab = 'selected';
-
- Store.toggleIssue(issue);
- Store.toggleIssue(issue);
-
- expect(Store.store.selectedIssues.length).toBe(1);
- expect(Store.selectedCount()).toBe(0);
- });
-
- it('gets selected issue array with only selected issues', () => {
- Store.toggleIssue(issue);
- Store.toggleIssue(issue2);
- Store.toggleIssue(issue2);
-
- expect(Store.getSelectedIssues().length).toBe(1);
- });
-});
diff --git a/spec/helpers/timeboxes_helper_spec.rb b/spec/helpers/timeboxes_helper_spec.rb
index 9cbed7668ac..1b9442c0a09 100644
--- a/spec/helpers/timeboxes_helper_spec.rb
+++ b/spec/helpers/timeboxes_helper_spec.rb
@@ -3,42 +3,6 @@
require 'spec_helper'
RSpec.describe TimeboxesHelper do
- describe '#milestones_filter_dropdown_path' do
- let(:project) { create(:project) }
- let(:project2) { create(:project) }
- let(:group) { create(:group) }
-
- context 'when @project present' do
- it 'returns project milestones JSON URL' do
- assign(:project, project)
-
- expect(helper.milestones_filter_dropdown_path).to eq(project_milestones_path(project, :json))
- end
- end
-
- context 'when @target_project present' do
- it 'returns targeted project milestones JSON URL' do
- assign(:target_project, project2)
-
- expect(helper.milestones_filter_dropdown_path).to eq(project_milestones_path(project2, :json))
- end
- end
-
- context 'when @group present' do
- it 'returns group milestones JSON URL' do
- assign(:group, group)
-
- expect(helper.milestones_filter_dropdown_path).to eq(group_milestones_path(group, :json))
- end
- end
-
- context 'when neither of @project/@target_project/@group present' do
- it 'returns dashboard milestones JSON URL' do
- expect(helper.milestones_filter_dropdown_path).to eq(dashboard_milestones_path(:json))
- end
- end
- end
-
describe "#timebox_date_range" do
let(:yesterday) { Date.yesterday }
let(:tomorrow) { yesterday + 2 }
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 9ffefd4bbf7..d8eb4ebc432 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -202,6 +202,16 @@ RSpec.describe Service do
end
end
+ describe '#project_level?' do
+ it 'is true when service has a project' do
+ expect(build(:service, project: project)).to be_project_level
+ end
+
+ it 'is false when service has no project' do
+ expect(build(:service, project: nil)).not_to be_project_level
+ end
+ end
+
describe '.find_or_initialize_non_project_specific_integration' do
let!(:service1) { create(:jira_service, project_id: nil, group_id: group.id) }
let!(:service2) { create(:jira_service) }