diff options
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 /<trigger> 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 /<command> 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) } |
