diff --git a/app/assets/javascripts/issuable/bulk_update_sidebar/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable/bulk_update_sidebar/issuable_bulk_update_sidebar.js index 0deb71a10802dda7da120dc9c01d4d3b3a055d0a..42ac7d4ef9fe7802eaa81c84a37a2958d30e8078 100644 --- a/app/assets/javascripts/issuable/bulk_update_sidebar/issuable_bulk_update_sidebar.js +++ b/app/assets/javascripts/issuable/bulk_update_sidebar/issuable_bulk_update_sidebar.js @@ -1,8 +1,6 @@ /* eslint-disable class-methods-use-this, no-new */ import $ from 'jquery'; -import { property } from 'lodash'; - import issuableEventHub from '~/issues_list/eventhub'; import LabelsSelect from '~/labels/labels_select'; import MilestoneSelect from '~/milestones/milestone_select'; @@ -16,8 +14,6 @@ const SIDEBAR_COLLAPSED_CLASS = 'right-sidebar-collapsed issuable-bulk-update-si export default class IssuableBulkUpdateSidebar { constructor() { - this.vueIssuablesListFeature = property(['gon', 'features', 'vueIssuablesList'])(window); - this.initDomElements(); this.bindEvents(); this.initDropdowns(); @@ -143,7 +139,7 @@ export default class IssuableBulkUpdateSidebar { } toggleCheckboxDisplay(show) { - this.$checkAllContainer.toggleClass(HIDDEN_CLASS, !show || this.vueIssuablesListFeature); + this.$checkAllContainer.toggleClass(HIDDEN_CLASS, !show); this.$issueChecks.toggleClass(HIDDEN_CLASS, !show); } diff --git a/app/assets/javascripts/issues/manual_ordering.js b/app/assets/javascripts/issues/manual_ordering.js index 9613246d6a610710ea4f554a3a738fef6212b7dd..c78505d061064c2328a3e274f25454035a2c50c6 100644 --- a/app/assets/javascripts/issues/manual_ordering.js +++ b/app/assets/javascripts/issues/manual_ordering.js @@ -20,7 +20,7 @@ const updateIssue = (url, issueList, { move_before_id, move_after_id }) => }); }); -const initManualOrdering = (draggableSelector = 'li.issue') => { +const initManualOrdering = () => { const issueList = document.querySelector('.manual-ordering'); if (!issueList || !(gon.current_user_id > 0)) { @@ -37,14 +37,14 @@ const initManualOrdering = (draggableSelector = 'li.issue') => { group: { name: 'issues', }, - draggable: draggableSelector, + draggable: 'li.issue', onStart: () => { sortableStart(); }, onUpdate: (event) => { const el = event.item; - const url = el.getAttribute('url') || el.dataset.url; + const url = el.getAttribute('url'); const prev = el.previousElementSibling; const next = el.nextElementSibling; diff --git a/app/assets/javascripts/issues_list/components/issuable.vue b/app/assets/javascripts/issues_list/components/issuable.vue deleted file mode 100644 index 6476d5be38c930deeac9f3d67a2381f024a6258f..0000000000000000000000000000000000000000 --- a/app/assets/javascripts/issues_list/components/issuable.vue +++ /dev/null @@ -1,441 +0,0 @@ -<script> -/* - * This is tightly coupled to projects/issues/_issue.html.haml, - * any changes done to the haml need to be reflected here. - */ - -// TODO: need to move this component to graphql - https://gitlab.com/gitlab-org/gitlab/-/issues/221246 -import jiraLogo from '@gitlab/svgs/dist/illustrations/logos/jira.svg'; -import { - GlLink, - GlTooltipDirective as GlTooltip, - GlSprintf, - GlLabel, - GlIcon, - GlSafeHtmlDirective as SafeHtml, -} from '@gitlab/ui'; -import { escape, isNumber } from 'lodash'; -import { isScopedLabel } from '~/lib/utils/common_utils'; -import { - dateInWords, - formatDate, - getDayDifference, - getTimeago, - timeFor, - newDateAsLocaleTime, -} from '~/lib/utils/datetime_utility'; -import { convertToCamelCase } from '~/lib/utils/text_utility'; -import { mergeUrlParams, setUrlFragment, isExternal } from '~/lib/utils/url_utility'; -import { sprintf, __ } from '~/locale'; -import initUserPopovers from '~/user_popovers'; -import IssueAssignees from '~/issuable/components/issue_assignees.vue'; - -export default { - i18n: { - openedAgo: __('created %{timeAgoString} by %{user}'), - openedAgoJira: __('created %{timeAgoString} by %{user} in Jira'), - openedAgoServiceDesk: __('created %{timeAgoString} by %{email} via %{user}'), - }, - components: { - IssueAssignees, - GlLink, - GlLabel, - GlIcon, - GlSprintf, - IssueHealthStatus: () => - import('ee_component/related_items_tree/components/issue_health_status.vue'), - }, - directives: { - GlTooltip, - SafeHtml, - }, - inject: ['scopedLabelsAvailable'], - props: { - issuable: { - type: Object, - required: true, - }, - isBulkEditing: { - type: Boolean, - required: false, - default: false, - }, - selected: { - type: Boolean, - required: false, - default: false, - }, - baseUrl: { - type: String, - required: false, - default() { - return window.location.href; - }, - }, - }, - data() { - return { - jiraLogo, - }; - }, - computed: { - milestoneLink() { - const { title } = this.issuable.milestone; - - return this.issuableLink({ milestone_title: title }); - }, - hasWeight() { - return isNumber(this.issuable.weight); - }, - dueDate() { - return this.issuable.due_date ? newDateAsLocaleTime(this.issuable.due_date) : undefined; - }, - dueDateWords() { - return this.dueDate ? dateInWords(this.dueDate, true) : undefined; - }, - isOverdue() { - return this.dueDate ? this.dueDate < new Date() : false; - }, - isClosed() { - return this.issuable.state === 'closed'; - }, - isJiraIssue() { - return this.issuable.external_tracker === 'jira'; - }, - webUrl() { - return this.issuable.gitlab_web_url || this.issuable.web_url; - }, - isIssuableUrlExternal() { - return isExternal(this.webUrl); - }, - linkTarget() { - return this.isIssuableUrlExternal ? '_blank' : null; - }, - issueCreatedToday() { - return getDayDifference(new Date(this.issuable.created_at), new Date()) < 1; - }, - labelIdsString() { - return JSON.stringify(this.issuable.labels.map((l) => l.id)); - }, - milestoneDueDate() { - const { due_date: dueDate } = this.issuable.milestone || {}; - - return dueDate ? newDateAsLocaleTime(dueDate) : undefined; - }, - milestoneTooltipText() { - if (this.milestoneDueDate) { - return sprintf(__('%{primary} (%{secondary})'), { - primary: formatDate(this.milestoneDueDate, 'mmm d, yyyy'), - secondary: timeFor(this.milestoneDueDate), - }); - } - return __('Milestone'); - }, - issuableAuthor() { - return this.issuable.author; - }, - issuableCreatedAt() { - return getTimeago().format(this.issuable.created_at); - }, - popoverDataAttrs() { - const { id, username, name, avatar_url } = this.issuableAuthor; - - return { - 'data-user-id': id, - 'data-username': username, - 'data-name': name, - 'data-avatar-url': avatar_url, - }; - }, - referencePath() { - return this.issuable.references.relative; - }, - updatedDateString() { - return formatDate(new Date(this.issuable.updated_at), 'mmm d, yyyy h:MMtt'); - }, - updatedDateAgo() { - // snake_case because it's the same i18n string as the HAML view - return sprintf(__('updated %{time_ago}'), { - time_ago: escape(getTimeago().format(this.issuable.updated_at)), - }); - }, - issuableMeta() { - return [ - { - key: 'merge-requests', - visible: this.issuable.merge_requests_count > 0, - value: this.issuable.merge_requests_count, - title: __('Related merge requests'), - dataTestId: 'merge-requests', - class: 'js-merge-requests', - icon: 'merge-request', - }, - { - key: 'upvotes', - visible: this.issuable.upvotes > 0, - value: this.issuable.upvotes, - title: __('Upvotes'), - dataTestId: 'upvotes', - class: 'js-upvotes issuable-upvotes', - icon: 'thumb-up', - }, - { - key: 'downvotes', - visible: this.issuable.downvotes > 0, - value: this.issuable.downvotes, - title: __('Downvotes'), - dataTestId: 'downvotes', - class: 'js-downvotes issuable-downvotes', - icon: 'thumb-down', - }, - { - key: 'blocking-issues', - visible: this.issuable.blocking_issues_count > 0, - value: this.issuable.blocking_issues_count, - title: __('Blocking issues'), - dataTestId: 'blocking-issues', - href: setUrlFragment(this.webUrl, 'related-issues'), - icon: 'issue-block', - }, - { - key: 'comments-count', - visible: !this.isJiraIssue, - value: this.issuable.user_notes_count, - title: __('Comments'), - dataTestId: 'notes-count', - href: setUrlFragment(this.webUrl, 'notes'), - class: { 'no-comments': !this.issuable.user_notes_count, 'issuable-comments': true }, - icon: 'comments', - }, - ]; - }, - healthStatus() { - return convertToCamelCase(this.issuable.health_status); - }, - openedMessage() { - if (this.isJiraIssue) return this.$options.i18n.openedAgoJira; - if (this.issuable.service_desk_reply_to) return this.$options.i18n.openedAgoServiceDesk; - return this.$options.i18n.openedAgo; - }, - }, - mounted() { - // TODO: Refactor user popover to use its own component instead of - // spawning event listeners on Vue-rendered elements. - initUserPopovers([this.$refs.openedAgoByContainer.$el]); - }, - methods: { - issuableLink(params) { - return mergeUrlParams(params, this.baseUrl); - }, - isScoped({ name }) { - return isScopedLabel({ title: name }) && this.scopedLabelsAvailable; - }, - labelHref({ name }) { - if (this.isJiraIssue) { - return this.issuableLink({ 'labels[]': name }); - } - - return this.issuableLink({ 'label_name[]': name }); - }, - onSelect(ev) { - this.$emit('select', { - issuable: this.issuable, - selected: ev.target.checked, - }); - }, - issuableMetaComponent(href) { - return href ? 'gl-link' : 'span'; - }, - }, - - confidentialTooltipText: __('Confidential'), -}; -</script> -<template> - <li - :id="`issue_${issuable.id}`" - class="issue" - :class="{ today: issueCreatedToday, closed: isClosed }" - :data-id="issuable.id" - :data-labels="labelIdsString" - :data-url="webUrl" - data-qa-selector="issue_container" - :data-qa-issue-title="issuable.title" - > - <div class="gl-display-flex"> - <!-- Bulk edit checkbox --> - <div v-if="isBulkEditing" class="gl-mr-3"> - <input - :id="`selected_issue_${issuable.id}`" - :checked="selected" - class="selected-issuable" - type="checkbox" - :data-id="issuable.id" - @input="onSelect" - /> - </div> - - <!-- Issuable info container --> - <!-- Issuable main info --> - <div class="gl-flex-grow-1"> - <div class="title"> - <span class="issue-title-text"> - <gl-icon - v-if="issuable.confidential" - v-gl-tooltip - name="eye-slash" - class="gl-vertical-align-text-bottom" - :size="16" - :title="$options.confidentialTooltipText" - :aria-label="$options.confidentialTooltipText" - /> - <gl-link - :href="webUrl" - :target="linkTarget" - data-testid="issuable-title" - data-qa-selector="issue_link" - > - {{ issuable.title }} - <gl-icon - v-if="isIssuableUrlExternal" - name="external-link" - class="gl-vertical-align-text-bottom gl-ml-2" - /> - </gl-link> - </span> - <span - v-if="issuable.has_tasks" - class="gl-ml-2 task-status gl-display-none d-sm-inline-block" - >{{ issuable.task_status }}</span - > - </div> - - <div class="issuable-info"> - <span class="js-ref-path gl-mr-4 mr-sm-0"> - <span - v-if="isJiraIssue" - v-safe-html="jiraLogo" - class="svg-container logo-container" - data-testid="jira-logo" - ></span> - {{ referencePath }} - </span> - - <span data-testid="openedByMessage" class="gl-display-none d-sm-inline-block gl-mr-4"> - · - <gl-sprintf :message="openedMessage"> - <template #timeAgoString> - <span>{{ issuableCreatedAt }}</span> - </template> - <template #user> - <gl-link - ref="openedAgoByContainer" - v-bind="popoverDataAttrs" - :href="issuableAuthor.web_url" - :target="linkTarget" - >{{ issuableAuthor.name }}</gl-link - > - </template> - <template #email> - <span>{{ issuable.service_desk_reply_to }}</span> - </template> - </gl-sprintf> - </span> - - <gl-link - v-if="issuable.milestone" - v-gl-tooltip - class="gl-display-none d-sm-inline-block gl-mr-4 js-milestone milestone" - :href="milestoneLink" - :title="milestoneTooltipText" - > - <gl-icon name="clock" class="s16 gl-vertical-align-text-bottom" /> - {{ issuable.milestone.title }} - </gl-link> - - <span - v-if="dueDate" - v-gl-tooltip - class="gl-display-none d-sm-inline-block gl-mr-4 js-due-date" - :class="{ cred: isOverdue }" - :title="__('Due date')" - > - <gl-icon name="calendar" /> - {{ dueDateWords }} - </span> - - <span - v-if="hasWeight" - v-gl-tooltip - :title="__('Weight')" - class="gl-display-none d-sm-inline-block gl-mr-4" - data-testid="weight" - data-qa-selector="issuable_weight_content" - > - <gl-icon name="weight" class="align-text-bottom" /> - {{ issuable.weight }} - </span> - - <issue-health-status - v-if="issuable.health_status" - :health-status="healthStatus" - class="gl-mr-4 issuable-tag-valign" - /> - - <gl-label - v-for="label in issuable.labels" - :key="label.id" - data-qa-selector="issuable-label" - :target="labelHref(label)" - :background-color="label.color" - :description="label.description" - :color="label.text_color" - :title="label.name" - :scoped="isScoped(label)" - size="sm" - class="gl-mr-2 issuable-tag-valign" - >{{ label.name }}</gl-label - > - </div> - </div> - - <!-- Issuable meta --> - <div - class="gl-flex-shrink-0 gl-display-flex gl-flex-direction-column align-items-end gl-justify-content-center" - > - <div class="controls gl-display-flex"> - <span v-if="isJiraIssue" data-testid="issuable-status">{{ issuable.status }}</span> - <span v-else-if="isClosed" class="issuable-status">{{ __('CLOSED') }}</span> - - <issue-assignees - :assignees="issuable.assignees" - class="gl-align-items-center gl-display-flex gl-ml-3" - :icon-size="16" - img-css-classes="gl-mr-2!" - :max-visible="4" - /> - - <template v-for="meta in issuableMeta"> - <span - v-if="meta.visible" - :key="meta.key" - v-gl-tooltip - class="gl-display-none gl-sm-display-flex gl-align-items-center gl-ml-3" - :class="meta.class" - :data-testid="meta.dataTestId" - :title="meta.title" - > - <component :is="issuableMetaComponent(meta.href)" :href="meta.href"> - <gl-icon v-if="meta.icon" :name="meta.icon" /> - {{ meta.value }} - </component> - </span> - </template> - </div> - <div v-gl-tooltip class="issuable-updated-at" :title="updatedDateString"> - {{ updatedDateAgo }} - </div> - </div> - </div> - </li> -</template> diff --git a/app/assets/javascripts/issues_list/components/issuables_list_app.vue b/app/assets/javascripts/issues_list/components/issuables_list_app.vue deleted file mode 100644 index 71136bf0159075a9ecd92fc3ba85f179e6298d65..0000000000000000000000000000000000000000 --- a/app/assets/javascripts/issues_list/components/issuables_list_app.vue +++ /dev/null @@ -1,426 +0,0 @@ -<script> -import { - GlEmptyState, - GlPagination, - GlDeprecatedSkeletonLoading as GlSkeletonLoading, - GlSafeHtmlDirective as SafeHtml, -} from '@gitlab/ui'; -import { toNumber, omit } from 'lodash'; -import createFlash from '~/flash'; -import axios from '~/lib/utils/axios_utils'; -import { scrollToElement, historyPushState } from '~/lib/utils/common_utils'; -import { setUrlParams, queryToObject, getParameterByName } from '~/lib/utils/url_utility'; -import { __ } from '~/locale'; -import initManualOrdering from '~/issues/manual_ordering'; -import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; -import { - sortOrderMap, - availableSortOptionsJira, - RELATIVE_POSITION, - PAGE_SIZE, - PAGE_SIZE_MANUAL, - LOADING_LIST_ITEMS_LENGTH, -} from '../constants'; -import issuableEventHub from '../eventhub'; -import { emptyStateHelper } from '../service_desk_helper'; -import Issuable from './issuable.vue'; - -/** - * @deprecated Use app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue instead - */ -export default { - LOADING_LIST_ITEMS_LENGTH, - directives: { - SafeHtml, - }, - components: { - GlEmptyState, - GlPagination, - GlSkeletonLoading, - Issuable, - FilteredSearchBar, - }, - props: { - canBulkEdit: { - type: Boolean, - required: false, - default: false, - }, - emptyStateMeta: { - type: Object, - required: true, - }, - endpoint: { - type: String, - required: true, - }, - projectPath: { - type: String, - required: false, - default: '', - }, - sortKey: { - type: String, - required: false, - default: '', - }, - type: { - type: String, - required: false, - default: '', - }, - }, - data() { - return { - availableSortOptionsJira, - filters: {}, - isBulkEditing: false, - issuables: [], - loading: false, - page: getParameterByName('page') !== null ? toNumber(getParameterByName('page')) : 1, - selection: {}, - totalItems: 0, - }; - }, - computed: { - allIssuablesSelected() { - // WARNING: Because we are only keeping track of selected values - // this works, we will need to rethink this if we start tracking - // [id]: false for not selected values. - return this.issuables.length === Object.keys(this.selection).length; - }, - emptyState() { - if (this.issuables.length) { - return {}; // Empty state shouldn't be shown here - } - - if (this.isServiceDesk) { - return emptyStateHelper(this.emptyStateMeta); - } - - if (this.hasFilters) { - return { - title: __('Sorry, your filter produced no results'), - svgPath: this.emptyStateMeta.svgPath, - description: __('To widen your search, change or remove filters above'), - primaryLink: this.emptyStateMeta.createIssuePath, - primaryText: __('New issue'), - }; - } - - if (this.filters.state === 'opened') { - return { - title: __('There are no open issues'), - svgPath: this.emptyStateMeta.svgPath, - description: __('To keep this project going, create a new issue'), - primaryLink: this.emptyStateMeta.createIssuePath, - primaryText: __('New issue'), - }; - } else if (this.filters.state === 'closed') { - return { - title: __('There are no closed issues'), - svgPath: this.emptyStateMeta.svgPath, - }; - } - - return { - title: __('There are no issues to show'), - svgPath: this.emptyStateMeta.svgPath, - description: __( - 'The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project.', - ), - }; - }, - hasFilters() { - const ignored = ['utf8', 'state', 'scope', 'order_by', 'sort']; - return Object.keys(omit(this.filters, ignored)).length > 0; - }, - isManualOrdering() { - return this.sortKey === RELATIVE_POSITION; - }, - itemsPerPage() { - return this.isManualOrdering ? PAGE_SIZE_MANUAL : PAGE_SIZE; - }, - baseUrl() { - return window.location.href.replace(/(\?.*)?(#.*)?$/, ''); - }, - paginationNext() { - return this.page + 1; - }, - paginationPrev() { - return this.page - 1; - }, - paginationProps() { - const paginationProps = { value: this.page }; - - if (this.totalItems) { - return { - ...paginationProps, - perPage: this.itemsPerPage, - totalItems: this.totalItems, - }; - } - - return { - ...paginationProps, - prevPage: this.paginationPrev, - nextPage: this.paginationNext, - }; - }, - isServiceDesk() { - return this.type === 'service_desk'; - }, - isJira() { - return this.type === 'jira'; - }, - initialFilterValue() { - const value = []; - const { search } = this.getQueryObject(); - - if (search) { - value.push(search); - } - return value; - }, - initialSortBy() { - const { sort } = this.getQueryObject(); - return sort || 'created_desc'; - }, - }, - watch: { - selection() { - // We need to call nextTick here to wait for all of the boxes to be checked and rendered - // before we query the dom in issuable_bulk_update_actions.js. - this.$nextTick(() => { - issuableEventHub.$emit('issuables:updateBulkEdit'); - }); - }, - issuables() { - this.$nextTick(() => { - initManualOrdering(); - }); - }, - }, - mounted() { - if (this.canBulkEdit) { - this.unsubscribeToggleBulkEdit = issuableEventHub.$on('issuables:toggleBulkEdit', (val) => { - this.isBulkEditing = val; - }); - } - this.fetchIssuables(); - }, - beforeDestroy() { - // eslint-disable-next-line @gitlab/no-global-event-off - issuableEventHub.$off('issuables:toggleBulkEdit'); - }, - methods: { - isSelected(issuableId) { - return Boolean(this.selection[issuableId]); - }, - setSelection(ids) { - ids.forEach((id) => { - this.select(id, true); - }); - }, - clearSelection() { - this.selection = {}; - }, - select(id, isSelect = true) { - if (isSelect) { - this.$set(this.selection, id, true); - } else { - this.$delete(this.selection, id); - } - }, - fetchIssuables(pageToFetch) { - this.loading = true; - - this.clearSelection(); - - this.setFilters(); - - return axios - .get(this.endpoint, { - params: { - ...this.filters, - - with_labels_details: true, - page: pageToFetch || this.page, - per_page: this.itemsPerPage, - }, - }) - .then((response) => { - this.loading = false; - this.issuables = response.data; - this.totalItems = Number(response.headers['x-total']); - this.page = Number(response.headers['x-page']); - }) - .catch(() => { - this.loading = false; - return createFlash({ - message: __('An error occurred while loading issues'), - }); - }); - }, - getQueryObject() { - return queryToObject(window.location.search, { gatherArrays: true }); - }, - onPaginate(newPage) { - if (newPage === this.page) return; - - scrollToElement('#content-body'); - - // NOTE: This allows for the params to be updated on pagination - historyPushState( - setUrlParams({ ...this.filters, page: newPage }, window.location.href, true), - ); - - this.fetchIssuables(newPage); - }, - onSelectAll() { - if (this.allIssuablesSelected) { - this.selection = {}; - } else { - this.setSelection(this.issuables.map(({ id }) => id)); - } - }, - onSelectIssuable({ issuable, selected }) { - if (!this.canBulkEdit) return; - - this.select(issuable.id, selected); - }, - setFilters() { - const { - label_name: labels, - milestone_title: milestoneTitle, - 'not[label_name]': excludedLabels, - 'not[milestone_title]': excludedMilestone, - ...filters - } = this.getQueryObject(); - - // TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/227880 - - if (milestoneTitle) { - filters.milestone = milestoneTitle; - } - if (Array.isArray(labels)) { - filters.labels = labels.join(','); - } - if (!filters.state) { - filters.state = 'opened'; - } - - if (excludedLabels) { - filters['not[labels]'] = excludedLabels; - } - - if (excludedMilestone) { - filters['not[milestone]'] = excludedMilestone; - } - - Object.assign(filters, sortOrderMap[this.sortKey]); - - this.filters = filters; - }, - refetchIssuables() { - const ignored = ['utf8']; - const params = omit(this.filters, ignored); - - historyPushState(setUrlParams(params, window.location.href, true, true)); - this.fetchIssuables(); - }, - handleFilter(filters) { - const searchTokens = []; - - filters.forEach((filter) => { - if (filter.type === 'filtered-search-term') { - if (filter.value.data) { - searchTokens.push(filter.value.data); - } - } - }); - - if (searchTokens.length) { - this.filters.search = searchTokens.join(' '); - } - this.page = 1; - - this.refetchIssuables(); - }, - handleSort(sort) { - this.filters.sort = sort; - this.page = 1; - - this.refetchIssuables(); - }, - }, -}; -</script> - -<template> - <div> - <filtered-search-bar - v-if="isJira" - :namespace="projectPath" - :search-input-placeholder="__('Search Jira issues')" - :tokens="[]" - :sort-options="availableSortOptionsJira" - :initial-filter-value="initialFilterValue" - :initial-sort-by="initialSortBy" - class="row-content-block" - @onFilter="handleFilter" - @onSort="handleSort" - /> - <ul v-if="loading" class="content-list"> - <li v-for="n in $options.LOADING_LIST_ITEMS_LENGTH" :key="n" class="issue gl-px-5! gl-py-5!"> - <gl-skeleton-loading /> - </li> - </ul> - <div v-else-if="issuables.length"> - <div v-if="isBulkEditing" class="issue px-3 py-3 border-bottom border-light"> - <input - id="check-all-issues" - type="checkbox" - :checked="allIssuablesSelected" - class="mr-2" - @click="onSelectAll" - /> - <strong>{{ __('Select all') }}</strong> - </div> - <ul - class="content-list issuable-list issues-list" - :class="{ 'manual-ordering': isManualOrdering }" - > - <issuable - v-for="issuable in issuables" - :key="issuable.id" - class="pr-3" - :class="{ 'user-can-drag': isManualOrdering }" - :issuable="issuable" - :is-bulk-editing="isBulkEditing" - :selected="isSelected(issuable.id)" - :base-url="baseUrl" - @select="onSelectIssuable" - /> - </ul> - <div class="mt-3"> - <gl-pagination - v-bind="paginationProps" - class="gl-justify-content-center" - @input="onPaginate" - /> - </div> - </div> - <gl-empty-state - v-else - :title="emptyState.title" - :svg-path="emptyState.svgPath" - :primary-button-link="emptyState.primaryLink" - :primary-button-text="emptyState.primaryText" - > - <template #description> - <div v-safe-html="emptyState.description"></div> - </template> - </gl-empty-state> - </div> -</template> diff --git a/app/assets/javascripts/issues_list/constants.js b/app/assets/javascripts/issues_list/constants.js index c9eaf0b99089a27de20ca53e467476f39a0e992f..4a380848b4f594fad1e5322eb70c8ed586de2fb6 100644 --- a/app/assets/javascripts/issues_list/constants.js +++ b/app/assets/javascripts/issues_list/constants.js @@ -9,62 +9,6 @@ import { OPERATOR_IS_NOT, } from '~/vue_shared/components/filtered_search_bar/constants'; -// Maps sort order as it appears in the URL query to API `order_by` and `sort` params. -const PRIORITY = 'priority'; -const ASC = 'asc'; -const DESC = 'desc'; -const CREATED_AT = 'created_at'; -const UPDATED_AT = 'updated_at'; -const DUE_DATE = 'due_date'; -const MILESTONE_DUE = 'milestone_due'; -const POPULARITY = 'popularity'; -const WEIGHT = 'weight'; -const LABEL_PRIORITY = 'label_priority'; -const TITLE = 'title'; -export const RELATIVE_POSITION = 'relative_position'; -export const LOADING_LIST_ITEMS_LENGTH = 8; -export const PAGE_SIZE = 20; -export const PAGE_SIZE_MANUAL = 100; - -export const sortOrderMap = { - priority: { order_by: PRIORITY, sort: ASC }, // asc and desc are flipped for some reason - created_date: { order_by: CREATED_AT, sort: DESC }, - created_asc: { order_by: CREATED_AT, sort: ASC }, - updated_desc: { order_by: UPDATED_AT, sort: DESC }, - updated_asc: { order_by: UPDATED_AT, sort: ASC }, - milestone_due_desc: { order_by: MILESTONE_DUE, sort: DESC }, - milestone: { order_by: MILESTONE_DUE, sort: ASC }, - due_date_desc: { order_by: DUE_DATE, sort: DESC }, - due_date: { order_by: DUE_DATE, sort: ASC }, - popularity: { order_by: POPULARITY, sort: DESC }, - popularity_asc: { order_by: POPULARITY, sort: ASC }, - label_priority: { order_by: LABEL_PRIORITY, sort: ASC }, // asc and desc are flipped - relative_position: { order_by: RELATIVE_POSITION, sort: ASC }, - weight_desc: { order_by: WEIGHT, sort: DESC }, - weight: { order_by: WEIGHT, sort: ASC }, - title: { order_by: TITLE, sort: ASC }, - title_desc: { order_by: TITLE, sort: DESC }, -}; - -export const availableSortOptionsJira = [ - { - id: 1, - title: __('Created date'), - sortDirection: { - descending: 'created_desc', - ascending: 'created_asc', - }, - }, - { - id: 2, - title: __('Last updated'), - sortDirection: { - descending: 'updated_desc', - ascending: 'updated_asc', - }, - }, -]; - export const i18n = { anonymousSearchingMessage: __('You must sign in to search for specific terms.'), calendarLabel: __('Subscribe to calendar'), @@ -108,11 +52,13 @@ export const i18n = { upvotes: __('Upvotes'), }; -export const JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY = 'jira-import-success-alert-hide-map'; - +export const MAX_LIST_SIZE = 10; +export const PAGE_SIZE = 20; +export const PAGE_SIZE_MANUAL = 100; export const PARAM_DUE_DATE = 'due_date'; export const PARAM_SORT = 'sort'; export const PARAM_STATE = 'state'; +export const RELATIVE_POSITION = 'relative_position'; export const defaultPageSizeParams = { firstPageSize: PAGE_SIZE, @@ -183,8 +129,6 @@ export const urlSortParams = { [TITLE_DESC]: 'title_desc', }; -export const MAX_LIST_SIZE = 10; - export const API_PARAM = 'apiParam'; export const URL_PARAM = 'urlParam'; export const NORMAL_FILTER = 'normalFilter'; diff --git a/app/assets/javascripts/issues_list/index.js b/app/assets/javascripts/issues_list/index.js index 9d2ec8b32d2b96f7bba1bf5022873a7e4f4be9cf..30c227619e03d7948643f2998bbfe9272a6415ea 100644 --- a/app/assets/javascripts/issues_list/index.js +++ b/app/assets/javascripts/issues_list/index.js @@ -4,8 +4,7 @@ import VueApollo from 'vue-apollo'; import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql'; import IssuesListApp from 'ee_else_ce/issues_list/components/issues_list_app.vue'; import createDefaultClient from '~/lib/graphql'; -import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils'; -import IssuablesListApp from './components/issuables_list_app.vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; import JiraIssuesImportStatusRoot from './components/jira_issues_import_status_app.vue'; export function mountJiraIssuesListApp() { @@ -45,35 +44,6 @@ export function mountJiraIssuesListApp() { }); } -export function mountIssuablesListApp() { - if (!gon.features?.vueIssuablesList) { - return; - } - - document.querySelectorAll('.js-issuables-list').forEach((el) => { - const { canBulkEdit, emptyStateMeta = {}, scopedLabelsAvailable, ...data } = el.dataset; - - return new Vue({ - el, - provide: { - scopedLabelsAvailable: parseBoolean(scopedLabelsAvailable), - }, - render(createElement) { - return createElement(IssuablesListApp, { - props: { - ...data, - emptyStateMeta: - Object.keys(emptyStateMeta).length !== 0 - ? convertObjectPropsToCamelCase(JSON.parse(emptyStateMeta)) - : {}, - canBulkEdit: Boolean(canBulkEdit), - }, - }); - }, - }); - }); -} - export function mountIssuesListApp() { const el = document.querySelector('.js-issues-list'); diff --git a/app/assets/javascripts/issues_list/service_desk_helper.js b/app/assets/javascripts/issues_list/service_desk_helper.js deleted file mode 100644 index 815f338f1a078faac1dd4f6de8551c369b627deb..0000000000000000000000000000000000000000 --- a/app/assets/javascripts/issues_list/service_desk_helper.js +++ /dev/null @@ -1,111 +0,0 @@ -import { __, s__ } from '~/locale'; - -/** - * Generates empty state messages for Service Desk issues list. - * - * @param {emptyStateMeta} emptyStateMeta - Meta data used to generate empty state messages - * @returns {Object} Object containing empty state messages generated using the meta data. - */ -export function generateMessages(emptyStateMeta) { - const { - svgPath, - serviceDeskHelpPage, - serviceDeskAddress, - editProjectPage, - incomingEmailHelpPage, - } = emptyStateMeta; - - const serviceDeskSupportedTitle = s__( - 'ServiceDesk|Use Service Desk to connect with your users and offer customer support through email right inside GitLab', - ); - - const serviceDeskSupportedMessage = s__( - 'ServiceDesk|Issues created from Service Desk emails will appear here. Each comment becomes part of the email conversation.', - ); - - const commonDescription = ` - <span>${serviceDeskSupportedMessage}</span> - <a href="${serviceDeskHelpPage}">${__('Learn more.')}</a>`; - - return { - serviceDeskEnabledAndCanEditProjectSettings: { - title: serviceDeskSupportedTitle, - svgPath, - description: `<p>${s__('ServiceDesk|Your users can send emails to this address:')} - <code>${serviceDeskAddress}</code> - </p> - ${commonDescription}`, - }, - serviceDeskEnabledAndCannotEditProjectSettings: { - title: serviceDeskSupportedTitle, - svgPath, - description: commonDescription, - }, - serviceDeskDisabledAndCanEditProjectSettings: { - title: serviceDeskSupportedTitle, - svgPath, - description: commonDescription, - primaryLink: editProjectPage, - primaryText: s__('ServiceDesk|Enable Service Desk'), - }, - serviceDeskDisabledAndCannotEditProjectSettings: { - title: serviceDeskSupportedTitle, - svgPath, - description: commonDescription, - }, - serviceDeskIsNotSupported: { - title: s__('ServiceDesk|Service Desk is not supported'), - svgPath, - description: s__( - 'ServiceDesk|To enable Service Desk on this instance, an instance administrator must first set up incoming email.', - ), - primaryLink: incomingEmailHelpPage, - primaryText: __('Learn more.'), - }, - serviceDeskIsNotEnabled: { - title: s__('ServiceDesk|Service Desk is not enabled'), - svgPath, - description: s__( - 'ServiceDesk|For help setting up the Service Desk for your instance, please contact an administrator.', - ), - }, - }; -} - -/** - * Returns the attributes used for gl-empty-state in the Service Desk issues list. - * - * @param {Object} emptyStateMeta - Meta data used to generate empty state messages - * @returns {Object} - */ -export function emptyStateHelper(emptyStateMeta) { - const messages = generateMessages(emptyStateMeta); - - const { isServiceDeskSupported, canEditProjectSettings, isServiceDeskEnabled } = emptyStateMeta; - - if (isServiceDeskSupported) { - if (isServiceDeskEnabled && canEditProjectSettings) { - return messages.serviceDeskEnabledAndCanEditProjectSettings; - } - - if (isServiceDeskEnabled && !canEditProjectSettings) { - return messages.serviceDeskEnabledAndCannotEditProjectSettings; - } - - // !isServiceDeskEnabled && canEditProjectSettings - if (canEditProjectSettings) { - return messages.serviceDeskDisabledAndCanEditProjectSettings; - } - - // !isServiceDeskEnabled && !canEditProjectSettings - return messages.serviceDeskDisabledAndCannotEditProjectSettings; - } - - // !serviceDeskSupported && canEditProjectSettings - if (canEditProjectSettings) { - return messages.serviceDeskIsNotSupported; - } - - // !serviceDeskSupported && !canEditProjectSettings - return messages.serviceDeskIsNotEnabled; -} diff --git a/app/assets/javascripts/jira_import/utils/constants.js b/app/assets/javascripts/jira_import/utils/constants.js index 178159be0096261fd50a341884c45d1876b41384..4230f85e443254ebc703a9a8cc71ac6d779146fa 100644 --- a/app/assets/javascripts/jira_import/utils/constants.js +++ b/app/assets/javascripts/jira_import/utils/constants.js @@ -1,5 +1,7 @@ import { __ } from '~/locale'; +export const JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY = 'jira-import-success-alert-hide-map'; + export const debounceWait = 500; export const dropdownLabel = __( diff --git a/app/assets/javascripts/jira_import/utils/jira_import_utils.js b/app/assets/javascripts/jira_import/utils/jira_import_utils.js index 4e3b5b2fbdeb4072305ab21fecdf685ef4b4eecb..bd83dd4d219be2a0ea0a2848ca30ac8f52e58c7b 100644 --- a/app/assets/javascripts/jira_import/utils/jira_import_utils.js +++ b/app/assets/javascripts/jira_import/utils/jira_import_utils.js @@ -1,5 +1,5 @@ import { last } from 'lodash'; -import { JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY } from '~/issues_list/constants'; +import { JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY } from './constants'; export const IMPORT_STATE = { FAILED: 'failed', diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js index bf0fcdc7095e6ddb85d025feffe5a94bd46001d2..b68e52d8ab1dbd7eeb6370d4fefa7f797f4f6f71 100644 --- a/app/assets/javascripts/pages/groups/issues/index.js +++ b/app/assets/javascripts/pages/groups/issues/index.js @@ -1,6 +1,6 @@ import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; import { initBulkUpdateSidebar } from '~/issuable/bulk_update_sidebar'; -import { mountIssuablesListApp, mountIssuesListApp } from '~/issues_list'; +import { mountIssuesListApp } from '~/issues_list'; import initManualOrdering from '~/issues/manual_ordering'; import { FILTERED_SEARCH } from '~/filtered_search/constants'; import initFilteredSearch from '~/pages/search/init_filtered_search'; @@ -23,8 +23,4 @@ if (gon.features?.vueIssuesList) { }); projectSelect(); initManualOrdering(); - - if (gon.features?.vueIssuablesList) { - mountIssuablesListApp(); - } } diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js index 04da10f153f424c0ad971688177ec18fc1443bc4..c8fdcfe05022c2cdea82f6cb44e17a4c9048c5e5 100644 --- a/app/assets/javascripts/pages/projects/issues/index/index.js +++ b/app/assets/javascripts/pages/projects/issues/index/index.js @@ -2,7 +2,7 @@ import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import { initCsvImportExportButtons, initIssuableByEmail } from '~/issuable'; import { initBulkUpdateSidebar, initIssueStatusSelect } from '~/issuable/bulk_update_sidebar'; -import { mountIssuablesListApp, mountIssuesListApp, mountJiraIssuesListApp } from '~/issues_list'; +import { mountIssuesListApp, mountJiraIssuesListApp } from '~/issues_list'; import initManualOrdering from '~/issues/manual_ordering'; import { FILTERED_SEARCH } from '~/filtered_search/constants'; import { ISSUABLE_INDEX } from '~/issuable/constants'; @@ -27,10 +27,6 @@ if (gon.features?.vueIssuesList) { initCsvImportExportButtons(); initIssuableByEmail(); initManualOrdering(); - - if (gon.features?.vueIssuablesList) { - mountIssuablesListApp(); - } } new ShortcutsNavigation(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/projects/issues/service_desk/index.js b/app/assets/javascripts/pages/projects/issues/service_desk/index.js index b21bcb0a9bf75b3a1a381cc3a707018370235ff9..7dd128fedb98b2d516ecb7897186bea34c97071b 100644 --- a/app/assets/javascripts/pages/projects/issues/service_desk/index.js +++ b/app/assets/javascripts/pages/projects/issues/service_desk/index.js @@ -1,8 +1,3 @@ import { initFilteredSearchServiceDesk } from '~/issues'; -import { mountIssuablesListApp } from '~/issues_list'; initFilteredSearchServiceDesk(); - -if (gon.features?.vueIssuablesList) { - mountIssuablesListApp(); -} diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 62336c7eedefa0be6cece3616faf7fb26a9a05d8..18e1610488d11b012039277e92063b3d2962e0c2 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -32,7 +32,6 @@ class GroupsController < Groups::ApplicationController before_action :user_actions, only: [:show] before_action do - push_frontend_feature_flag(:vue_issuables_list, @group) push_frontend_feature_flag(:vue_issues_list, @group, default_enabled: :yaml) push_frontend_feature_flag(:iteration_cadences, @group, default_enabled: :yaml) end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 9d043ba064558a764381a562dcb15e390b0408c3..36c1897cf102c0e288ef1ff31ba9e8b6f45ffd24 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -43,7 +43,6 @@ class Projects::IssuesController < Projects::ApplicationController before_action do push_frontend_feature_flag(:tribute_autocomplete, @project) - push_frontend_feature_flag(:vue_issuables_list, project) push_frontend_feature_flag(:improved_emoji_picker, project, default_enabled: :yaml) push_frontend_feature_flag(:vue_issues_list, project&.group, default_enabled: :yaml) push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index f2d5f3fec9edfb96f6f70c9a7295b0836597e406..b1a5e4068cc35e0e7d1ba067a53e1e89a7fe45b5 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -168,21 +168,6 @@ def show_moved_service_desk_issue_warning?(issue) issue.moved_from.project.service_desk_enabled? && !issue.project.service_desk_enabled? end - def use_startup_call? - request.query_parameters.empty? && @sort == 'created_date' - end - - def startup_call_params - { - state: 'opened', - with_labels_details: 'true', - page: 1, - per_page: 20, - order_by: 'created_at', - sort: 'desc' - } - end - def issue_header_actions_data(project, issuable, current_user) new_issuable_params = { issue: { description: _('Related to #%{issue_id}.') % { issue_id: issuable.iid } + "\n\n" } } if issuable.incident? diff --git a/app/helpers/projects/issues/service_desk_helper.rb b/app/helpers/projects/issues/service_desk_helper.rb deleted file mode 100644 index 0f87e5ed83705b59334ed2e013d3194afeca038d..0000000000000000000000000000000000000000 --- a/app/helpers/projects/issues/service_desk_helper.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module Projects::Issues::ServiceDeskHelper - def service_desk_meta(project) - empty_state_meta = { - is_service_desk_supported: Gitlab::ServiceDesk.supported?, - is_service_desk_enabled: project.service_desk_enabled?, - can_edit_project_settings: can?(current_user, :admin_project, project) - } - - if Gitlab::ServiceDesk.supported? - empty_state_meta.merge(supported_meta(project)) - else - empty_state_meta.merge(unsupported_meta(project)) - end - end - - private - - def supported_meta(project) - { - service_desk_address: project.service_desk_address, - service_desk_help_page: help_page_path('user/project/service_desk'), - edit_project_page: edit_project_path(project), - svg_path: image_path('illustrations/service_desk_empty.svg') - } - end - - def unsupported_meta(project) - { - incoming_email_help_page: help_page_path('administration/incoming_email', anchor: 'set-it-up'), - svg_path: image_path('illustrations/service-desk-setup.svg') - } - end -end diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index cdc84150eba0112f66046dc201493025052b79a8..a9258a4e0d0089368bdb08c21dfc5b1abec0f515 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -25,14 +25,4 @@ - if @can_bulk_update = render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues - - if Feature.enabled?(:vue_issuables_list, @group) && @issues.to_a.any? - - if use_startup_call? - - add_page_startup_api_call(api_v4_groups_issues_path(id: @group.id, params: startup_call_params)) - .js-issuables-list{ data: { endpoint: expose_url(api_v4_groups_issues_path(id: @group.id)), - 'can-bulk-edit': @can_bulk_update.to_json, - 'empty-state-meta': { svg_path: image_path('illustrations/issues.svg') }, - 'sort-key': @sort, - type: 'issues', - 'scoped-labels-available': scoped_labels_available?(@group).to_json } } - - else - = render 'shared/issues', project_select_button: true + = render 'shared/issues', project_select_button: true diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 2dc21685057b950349a08ae174fe6281b7694b1f..4c96875ce421a6e8602639a1842c930d54570620 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -1,4 +1,3 @@ --# DANGER: Any changes to this file need to be reflected in issuables_list/components/issuable.vue! %li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id, qa_selector: 'issue_container', qa_issue_title: issue.title } } .issuable-info-container - if @can_bulk_update diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index 1ab51df6d2435ff7941716d56404c7e810ae463f..09b0b7a4d9b351d925bcf866b3ce7b507e7eb7a5 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -1,25 +1,10 @@ -- is_project_overview = local_assigns.fetch(:is_project_overview, false) = render 'shared/alerts/positioning_disabled' if @sort == 'relative_position' -- if Feature.enabled?(:vue_issuables_list, @project) && !is_project_overview - - data_endpoint = local_assigns.fetch(:data_endpoint, expose_path(api_v4_projects_issues_path(id: @project.id))) - - default_empty_state_meta = { create_issue_path: new_project_issue_path(@project), svg_path: image_path('illustrations/issues.svg') } - - data_empty_state_meta = local_assigns.fetch(:data_empty_state_meta, default_empty_state_meta) - - type = local_assigns.fetch(:type, 'issues') - - if type == 'issues' && use_startup_call? - - add_page_startup_api_call(api_v4_projects_issues_path(id: @project.id, params: startup_call_params)) - .js-issuables-list{ data: { endpoint: data_endpoint, - 'empty-state-meta': data_empty_state_meta.to_json, - 'can-bulk-edit': @can_bulk_update.to_json, - 'sort-key': @sort, - type: type, - 'scoped-labels-available': scoped_labels_available?(@project).to_json } } -- else - - empty_state_path = local_assigns.fetch(:empty_state_path, 'shared/empty_states/issues') - %ul.content-list.issues-list.issuable-list{ class: issue_manual_ordering_class } - = render partial: "projects/issues/issue", collection: @issues - - if @issues.blank? - = render empty_state_path +%ul.content-list.issues-list.issuable-list{ class: issue_manual_ordering_class } + = render partial: "projects/issues/issue", collection: @issues + - if @issues.blank? + - empty_state_path = local_assigns.fetch(:empty_state_path, 'shared/empty_states/issues') + = render empty_state_path - - if @issues.present? - = paginate_collection @issues, total_pages: @total_pages +- if @issues.present? + = paginate_collection @issues, total_pages: @total_pages diff --git a/app/views/projects/issues/_service_desk_empty_state.html.haml b/app/views/projects/issues/_service_desk_empty_state.html.haml index afeb2a23ea28cc9eda3c6a8cea44edb271064c7b..3e0b80700fefd5b10789ede276ea775030067b97 100644 --- a/app/views/projects/issues/_service_desk_empty_state.html.haml +++ b/app/views/projects/issues/_service_desk_empty_state.html.haml @@ -27,7 +27,11 @@ .svg-content = render 'shared/empty_states/icons/service_desk_setup.svg' .text-content - %h4= s_('ServiceDesk|Service Desk is enabled but not yet active') - %p - = s_("ServiceDesk|To activate Service Desk on this instance, an instance administrator must first set up incoming email.") - = link_to _('Learn more.'), help_page_path('administration/incoming_email', anchor: 'set-it-up') + - if can_edit_project_settings + %h4= s_('ServiceDesk|Service Desk is not supported') + %p + = s_("ServiceDesk|To enable Service Desk on this instance, an instance administrator must first set up incoming email.") + = link_to _('Learn more.'), help_page_path('administration/incoming_email', anchor: 'set-it-up') + - else + %h4= s_('ServiceDesk|Service Desk is not enabled') + %p= s_("ServiceDesk|For help setting up the Service Desk for your instance, please contact an administrator.") diff --git a/app/views/projects/issues/service_desk.html.haml b/app/views/projects/issues/service_desk.html.haml index b0d8791c566063175b516a7b38235d13fafd68ec..fb5880f633a30ae917283827e40dca5559af9015 100644 --- a/app/views/projects/issues/service_desk.html.haml +++ b/app/views/projects/issues/service_desk.html.haml @@ -7,9 +7,7 @@ - support_bot_attrs = { service_desk_enabled: @project.service_desk_enabled?, **UserSerializer.new.represent(User.support_bot) }.to_json -- data_endpoint = "#{expose_path(api_v4_projects_issues_path(id: @project.id))}?author_username=#{User.support_bot.username}" - -%div{ class: "js-service-desk-issues service-desk-issues", data: { support_bot: support_bot_attrs, service_desk_meta: service_desk_meta(@project) } } +.js-service-desk-issues.service-desk-issues{ data: { support_bot: support_bot_attrs } } .top-area = render 'shared/issuable/nav', type: :issues .nav-controls.d-block.d-sm-none @@ -20,12 +18,5 @@ - if Gitlab::ServiceDesk.supported? = render 'service_desk_info_content' - -# TODO Remove empty_state_path once vue_issuables_list FF is removed. - -# https://gitlab.com/gitlab-org/gitlab/-/issues/235652 - -# `empty_state_path` is used to render the empty state in the HAML version of issuables list. .issues-holder - = render 'projects/issues/issues', - empty_state_path: 'service_desk_empty_state', - data_endpoint: data_endpoint, - data_empty_state_meta: service_desk_meta(@project), - type: 'service_desk' + = render 'projects/issues/issues', empty_state_path: 'service_desk_empty_state' diff --git a/config/feature_flags/development/vue_issuables_list.yml b/config/feature_flags/development/vue_issuables_list.yml deleted file mode 100644 index 75ef82999ca105651c936004c633b499b8a6a338..0000000000000000000000000000000000000000 --- a/config/feature_flags/development/vue_issuables_list.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: vue_issuables_list -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/15091 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/208093 -milestone: '12.5' -type: development -group: group::project management -default_enabled: false diff --git a/ee/spec/features/issues/blocking_issues_spec.rb b/ee/spec/features/issues/blocking_issues_spec.rb index 772c63787f6d3eed014cbfd97b65624169db4f56..4d553e73d2a5b373417cc8a2785f772f37af27d0 100644 --- a/ee/spec/features/issues/blocking_issues_spec.rb +++ b/ee/spec/features/issues/blocking_issues_spec.rb @@ -9,8 +9,6 @@ let_it_be(:issue2) { build(:issue, project: project, created_at: 3.days.ago, title: 'blocks two issues') } before do - stub_feature_flags(vue_issuables_list: false) - visit project_issues_path(project) end diff --git a/ee/spec/features/issues/filtered_search/filter_issues_by_iteration_spec.rb b/ee/spec/features/issues/filtered_search/filter_issues_by_iteration_spec.rb index 0ed8bbc74543af100876f6cac5adfbb832acd760..c576bd3cba56805433e05658119166626bbb0a1b 100644 --- a/ee/spec/features/issues/filtered_search/filter_issues_by_iteration_spec.rb +++ b/ee/spec/features/issues/filtered_search/filter_issues_by_iteration_spec.rb @@ -147,14 +147,6 @@ def iteration_period(iteration) it_behaves_like 'filters by iteration' it_behaves_like 'shows iterations when using iteration token' - - context 'when vue_issuables_list is disabled' do - before do - stub_feature_flags(vue_issuables_list: false) - end - - it_behaves_like 'filters by iteration' - end end context 'group issues list' do @@ -164,14 +156,6 @@ def iteration_period(iteration) it_behaves_like 'filters by iteration' it_behaves_like 'shows iterations when using iteration token' - - context 'when vue_issuables_list is disabled' do - before do - stub_feature_flags(vue_issuables_list: false) - end - - it_behaves_like 'filters by iteration' - end end context 'project board' do diff --git a/ee/spec/features/issues/filtered_search/filter_issues_epic_spec.rb b/ee/spec/features/issues/filtered_search/filter_issues_epic_spec.rb index 1133db304dc35b8031e43d4e5492cf54d6e47f32..1f535962e6273a815e6fc66a6577c4a799c3597f 100644 --- a/ee/spec/features/issues/filtered_search/filter_issues_epic_spec.rb +++ b/ee/spec/features/issues/filtered_search/filter_issues_epic_spec.rb @@ -21,7 +21,6 @@ before do stub_licensed_features(epics: true) - stub_feature_flags(vue_issuables_list: false) group.add_maintainer(user) sign_in(user) diff --git a/ee/spec/features/issues/filtered_search/filter_issues_weight_spec.rb b/ee/spec/features/issues/filtered_search/filter_issues_weight_spec.rb index 772f4225d23b0749def4a3f9f2ff8130b7a8081b..c9c2b6ffbe9c738cfff262a77ee2213e230ad34c 100644 --- a/ee/spec/features/issues/filtered_search/filter_issues_weight_spec.rb +++ b/ee/spec/features/issues/filtered_search/filter_issues_weight_spec.rb @@ -14,14 +14,6 @@ let(:filter_dropdown) { find("#js-dropdown-weight .filter-dropdown") } - shared_examples 'filters by negated weight' do - it 'excludes issues with specified weight' do - input_filtered_search(search) - - expect_issues_list_count(1) - end - end - def expect_issues_list_count(open_count, closed_count = 0) all_count = open_count + closed_count @@ -57,14 +49,10 @@ def expect_issues_list_count(open_count, closed_count = 0) describe 'negated weight only' do let(:search) { 'weight:!=2' } - it_behaves_like 'filters by negated weight' - - context 'when vue_issuables_list is disabled' do - before do - stub_feature_flags(vue_issuables_list: false) - end + it 'excludes issues with specified weight' do + input_filtered_search(search) - it_behaves_like 'filters by negated weight' + expect_issues_list_count(1) end end diff --git a/ee/spec/features/issues/user_views_issues_spec.rb b/ee/spec/features/issues/user_views_issues_spec.rb index 47bb61fd5ad2ac9ed5308f65559593cd984d002e..4d7f1fc7fc1fe90707da9706731cb840b44f2a32 100644 --- a/ee/spec/features/issues/user_views_issues_spec.rb +++ b/ee/spec/features/issues/user_views_issues_spec.rb @@ -10,7 +10,6 @@ let_it_be(:issue3) { create(:issue, project: project, health_status: 'at_risk') } before do - stub_feature_flags(vue_issuables_list: false) sign_in(user) visit project_issues_path(project) end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3602d2ebaf0583fdd66ebeba8d83391b912c699f..ac329d01c79c4b5e22f78bbba589ed5e5a771c69 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -841,9 +841,6 @@ msgstr "" msgid "%{policy_link} (notifying after %{elapsed_time} minutes unless %{status})" msgstr "" -msgid "%{primary} (%{secondary})" -msgstr "" - msgid "%{project_path} is a project that you can use to add a README to your GitLab profile. Create a public project and initialize the repository with a README to get started. %{help_link_start}Learn more.%{help_link_end}" msgstr "" @@ -31023,9 +31020,6 @@ msgstr "" msgid "Search GitLab" msgstr "" -msgid "Search Jira issues" -msgstr "" - msgid "Search a group" msgstr "" @@ -32347,18 +32341,12 @@ msgstr "" msgid "ServiceDesk|Issues created from Service Desk emails will appear here. Each comment becomes part of the email conversation." msgstr "" -msgid "ServiceDesk|Service Desk is enabled but not yet active" -msgstr "" - msgid "ServiceDesk|Service Desk is not enabled" msgstr "" msgid "ServiceDesk|Service Desk is not supported" msgstr "" -msgid "ServiceDesk|To activate Service Desk on this instance, an instance administrator must first set up incoming email." -msgstr "" - msgid "ServiceDesk|To enable Service Desk on this instance, an instance administrator must first set up incoming email." msgstr "" @@ -41856,12 +41844,6 @@ msgstr "" msgid "created %{timeAgoString} by %{email} via %{user}" msgstr "" -msgid "created %{timeAgoString} by %{user}" -msgstr "" - -msgid "created %{timeAgoString} by %{user} in Jira" -msgstr "" - msgid "created %{timeAgo}" msgstr "" diff --git a/spec/features/issues/service_desk_spec.rb b/spec/features/issues/service_desk_spec.rb index 0a879fdd4d4ea7dca1802794ca684c7bec87c221..cc0d35afd604094e2d0ae5752a23e0994de33dcc 100644 --- a/spec/features/issues/service_desk_spec.rb +++ b/spec/features/issues/service_desk_spec.rb @@ -9,8 +9,6 @@ let_it_be(:support_bot) { User.support_bot } before do - stub_feature_flags(vue_issuables_list: true) - # The following two conditions equate to Gitlab::ServiceDesk.supported == true allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true) allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true) diff --git a/spec/features/issues/user_sees_breadcrumb_links_spec.rb b/spec/features/issues/user_sees_breadcrumb_links_spec.rb index 9f8cd2a769d1c333c73e4daddebd6c3dd086823f..669c7c454112990552e3e4c61b826350083cf6d7 100644 --- a/spec/features/issues/user_sees_breadcrumb_links_spec.rb +++ b/spec/features/issues/user_sees_breadcrumb_links_spec.rb @@ -8,8 +8,6 @@ let(:user) { project.creator } before do - stub_feature_flags(vue_issuables_list: false) - sign_in(user) visit(new_project_issue_path(project)) end diff --git a/spec/frontend/issues_list/components/__snapshots__/issuables_list_app_spec.js.snap b/spec/frontend/issues_list/components/__snapshots__/issuables_list_app_spec.js.snap deleted file mode 100644 index c327b7de827f71fc467acf3857064702fd9e0516..0000000000000000000000000000000000000000 --- a/spec/frontend/issues_list/components/__snapshots__/issuables_list_app_spec.js.snap +++ /dev/null @@ -1,14 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Issuables list component with empty issues response with all state should display a catch-all if there are no issues to show 1`] = ` -<gl-empty-state-stub - svgpath="/emptySvg" - title="There are no issues to show" -/> -`; - -exports[`Issuables list component with empty issues response with closed state should display a message "There are no closed issues" if there are no closed issues 1`] = `"There are no closed issues"`; - -exports[`Issuables list component with empty issues response with empty query should display the message "There are no open issues" 1`] = `"There are no open issues"`; - -exports[`Issuables list component with empty issues response with query in window location should display "Sorry, your filter produced no results" if filters are too specific 1`] = `"Sorry, your filter produced no results"`; diff --git a/spec/frontend/issues_list/components/issuable_spec.js b/spec/frontend/issues_list/components/issuable_spec.js deleted file mode 100644 index f3c2ae1f9dc9bd6642f0ca568ebd66694c403ac1..0000000000000000000000000000000000000000 --- a/spec/frontend/issues_list/components/issuable_spec.js +++ /dev/null @@ -1,508 +0,0 @@ -import { GlSprintf, GlLabel, GlIcon, GlLink } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import { TEST_HOST } from 'helpers/test_constants'; -import { trimText } from 'helpers/text_helper'; -import Issuable from '~/issues_list/components/issuable.vue'; -import { isScopedLabel } from '~/lib/utils/common_utils'; -import { formatDate } from '~/lib/utils/datetime_utility'; -import { mergeUrlParams } from '~/lib/utils/url_utility'; -import initUserPopovers from '~/user_popovers'; -import IssueAssignees from '~/issuable/components/issue_assignees.vue'; -import { simpleIssue, testAssignees, testLabels } from '../issuable_list_test_data'; - -jest.mock('~/user_popovers'); - -const TODAY = new Date(); - -const createTestDateFromDelta = (timeDelta) => - formatDate(new Date(TODAY.getTime() + timeDelta), 'yyyy-mm-dd'); - -// TODO: Encapsulate date helpers https://gitlab.com/gitlab-org/gitlab/-/issues/320883 -const MONTHS_IN_MS = 1000 * 60 * 60 * 24 * 31; -const TEST_MONTH_AGO = createTestDateFromDelta(-MONTHS_IN_MS); -const TEST_MONTH_LATER = createTestDateFromDelta(MONTHS_IN_MS); -const DATE_FORMAT = 'mmm d, yyyy'; -const TEST_USER_NAME = 'Tyler Durden'; -const TEST_BASE_URL = `${TEST_HOST}/issues`; -const TEST_TASK_STATUS = '50 of 100 tasks completed'; -const TEST_MILESTONE = { - title: 'Milestone title', - web_url: `${TEST_HOST}/milestone/1`, -}; -const TEXT_CLOSED = 'CLOSED'; -const TEST_META_COUNT = 100; -const MOCK_GITLAB_URL = 'http://0.0.0.0:3000'; - -describe('Issuable component', () => { - let issuable; - let wrapper; - - const factory = (props = {}, scopedLabelsAvailable = false) => { - wrapper = shallowMount(Issuable, { - propsData: { - issuable: simpleIssue, - baseUrl: TEST_BASE_URL, - ...props, - }, - provide: { - scopedLabelsAvailable, - }, - stubs: { - 'gl-sprintf': GlSprintf, - }, - }); - }; - - beforeEach(() => { - issuable = { ...simpleIssue }; - gon.gitlab_url = MOCK_GITLAB_URL; - }); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - const checkExists = (findFn) => () => findFn().exists(); - const hasIcon = (iconName, iconWrapper = wrapper) => - iconWrapper.findAll(GlIcon).wrappers.some((icon) => icon.props('name') === iconName); - const hasConfidentialIcon = () => hasIcon('eye-slash'); - const findTaskStatus = () => wrapper.find('.task-status'); - const findOpenedAgoContainer = () => wrapper.find('[data-testid="openedByMessage"]'); - const findAuthor = () => wrapper.find({ ref: 'openedAgoByContainer' }); - const findMilestone = () => wrapper.find('.js-milestone'); - const findMilestoneTooltip = () => findMilestone().attributes('title'); - const findDueDate = () => wrapper.find('.js-due-date'); - const findLabels = () => wrapper.findAll(GlLabel); - const findWeight = () => wrapper.find('[data-testid="weight"]'); - const findAssignees = () => wrapper.find(IssueAssignees); - const findBlockingIssuesCount = () => wrapper.find('[data-testid="blocking-issues"]'); - const findMergeRequestsCount = () => wrapper.find('[data-testid="merge-requests"]'); - const findUpvotes = () => wrapper.find('[data-testid="upvotes"]'); - const findDownvotes = () => wrapper.find('[data-testid="downvotes"]'); - const findNotes = () => wrapper.find('[data-testid="notes-count"]'); - const findBulkCheckbox = () => wrapper.find('input.selected-issuable'); - const findScopedLabels = () => findLabels().filter((w) => isScopedLabel({ title: w.text() })); - const findUnscopedLabels = () => findLabels().filter((w) => !isScopedLabel({ title: w.text() })); - const findIssuableTitle = () => wrapper.find('[data-testid="issuable-title"]'); - const findIssuableStatus = () => wrapper.find('[data-testid="issuable-status"]'); - const containsJiraLogo = () => wrapper.find('[data-testid="jira-logo"]').exists(); - const findHealthStatus = () => wrapper.find('.health-status'); - - describe('when mounted', () => { - it('initializes user popovers', () => { - expect(initUserPopovers).not.toHaveBeenCalled(); - - factory(); - - expect(initUserPopovers).toHaveBeenCalledWith([wrapper.vm.$refs.openedAgoByContainer.$el]); - }); - }); - - describe('when scopedLabels feature is available', () => { - beforeEach(() => { - issuable.labels = [...testLabels]; - - factory({ issuable }, true); - }); - - describe('when label is scoped', () => { - it('returns label with correct props', () => { - const scopedLabel = findScopedLabels().at(0); - - expect(scopedLabel.props('scoped')).toBe(true); - }); - }); - - describe('when label is not scoped', () => { - it('returns label with correct props', () => { - const notScopedLabel = findUnscopedLabels().at(0); - - expect(notScopedLabel.props('scoped')).toBe(false); - }); - }); - }); - - describe('when scopedLabels feature is not available', () => { - beforeEach(() => { - issuable.labels = [...testLabels]; - - factory({ issuable }); - }); - - describe('when label is scoped', () => { - it('label scoped props is false', () => { - const scopedLabel = findScopedLabels().at(0); - - expect(scopedLabel.props('scoped')).toBe(false); - }); - }); - - describe('when label is not scoped', () => { - it('label scoped props is false', () => { - const notScopedLabel = findUnscopedLabels().at(0); - - expect(notScopedLabel.props('scoped')).toBe(false); - }); - }); - }); - - describe('with simple issuable', () => { - beforeEach(() => { - Object.assign(issuable, { - has_tasks: false, - task_status: TEST_TASK_STATUS, - created_at: TEST_MONTH_AGO, - author: { - ...issuable.author, - name: TEST_USER_NAME, - }, - labels: [], - }); - - factory({ issuable }); - }); - - it.each` - desc | check - ${'bulk editing checkbox'} | ${checkExists(findBulkCheckbox)} - ${'confidential icon'} | ${hasConfidentialIcon} - ${'task status'} | ${checkExists(findTaskStatus)} - ${'milestone'} | ${checkExists(findMilestone)} - ${'due date'} | ${checkExists(findDueDate)} - ${'labels'} | ${checkExists(findLabels)} - ${'weight'} | ${checkExists(findWeight)} - ${'blocking issues count'} | ${checkExists(findBlockingIssuesCount)} - ${'merge request count'} | ${checkExists(findMergeRequestsCount)} - ${'upvotes'} | ${checkExists(findUpvotes)} - ${'downvotes'} | ${checkExists(findDownvotes)} - `('does not render $desc', ({ check }) => { - expect(check()).toBe(false); - }); - - it('show relative reference path', () => { - expect(wrapper.find('.js-ref-path').text()).toBe(issuable.references.relative); - }); - - it('does not have closed text', () => { - expect(wrapper.text()).not.toContain(TEXT_CLOSED); - }); - - it('does not have closed class', () => { - expect(wrapper.classes('closed')).toBe(false); - }); - - it('renders fuzzy created date and author', () => { - expect(trimText(findOpenedAgoContainer().text())).toContain( - `created 1 month ago by ${TEST_USER_NAME}`, - ); - }); - - it('renders no comments', () => { - expect(findNotes().classes('no-comments')).toBe(true); - }); - - it.each` - gitlabWebUrl | webUrl | expectedHref | expectedTarget | isExternal - ${undefined} | ${`${MOCK_GITLAB_URL}/issue`} | ${`${MOCK_GITLAB_URL}/issue`} | ${undefined} | ${false} - ${undefined} | ${'https://jira.com/issue'} | ${'https://jira.com/issue'} | ${'_blank'} | ${true} - ${'/gitlab-org/issue'} | ${'https://jira.com/issue'} | ${'/gitlab-org/issue'} | ${undefined} | ${false} - `( - 'renders issuable title correctly when `gitlabWebUrl` is `$gitlabWebUrl` and webUrl is `$webUrl`', - async ({ webUrl, gitlabWebUrl, expectedHref, expectedTarget, isExternal }) => { - factory({ - issuable: { - ...issuable, - web_url: webUrl, - gitlab_web_url: gitlabWebUrl, - }, - }); - - const titleEl = findIssuableTitle(); - - expect(titleEl.exists()).toBe(true); - expect(titleEl.find(GlLink).attributes('href')).toBe(expectedHref); - expect(titleEl.find(GlLink).attributes('target')).toBe(expectedTarget); - expect(titleEl.find(GlLink).text()).toBe(issuable.title); - - expect(titleEl.find(GlIcon).exists()).toBe(isExternal); - }, - ); - }); - - describe('with confidential issuable', () => { - beforeEach(() => { - issuable.confidential = true; - - factory({ issuable }); - }); - - it('renders the confidential icon', () => { - expect(hasConfidentialIcon()).toBe(true); - }); - }); - - describe('with Jira issuable', () => { - beforeEach(() => { - issuable.external_tracker = 'jira'; - - factory({ issuable }); - }); - - it('renders the Jira icon', () => { - expect(containsJiraLogo()).toBe(true); - }); - - it('opens issuable in a new tab', () => { - expect(findIssuableTitle().props('target')).toBe('_blank'); - }); - - it('opens author in a new tab', () => { - expect(findAuthor().props('target')).toBe('_blank'); - }); - - describe('with Jira status', () => { - const expectedStatus = 'In Progress'; - - beforeEach(() => { - issuable.status = expectedStatus; - - factory({ issuable }); - }); - - it('renders the Jira status', () => { - expect(findIssuableStatus().text()).toBe(expectedStatus); - }); - }); - }); - - describe('with task status', () => { - beforeEach(() => { - Object.assign(issuable, { - has_tasks: true, - task_status: TEST_TASK_STATUS, - }); - - factory({ issuable }); - }); - - it('renders task status', () => { - expect(findTaskStatus().exists()).toBe(true); - expect(findTaskStatus().text()).toBe(TEST_TASK_STATUS); - }); - }); - - describe.each` - desc | dueDate | expectedTooltipPart - ${'past due'} | ${TEST_MONTH_AGO} | ${'Past due'} - ${'future due'} | ${TEST_MONTH_LATER} | ${'1 month remaining'} - `('with milestone with $desc', ({ dueDate, expectedTooltipPart }) => { - beforeEach(() => { - issuable.milestone = { ...TEST_MILESTONE, due_date: dueDate }; - - factory({ issuable }); - }); - - it('renders milestone', () => { - expect(findMilestone().exists()).toBe(true); - expect(hasIcon('clock', findMilestone())).toBe(true); - expect(findMilestone().text()).toEqual(TEST_MILESTONE.title); - }); - - it('renders tooltip', () => { - expect(findMilestoneTooltip()).toBe( - `${formatDate(dueDate, DATE_FORMAT)} (${expectedTooltipPart})`, - ); - }); - - it('renders milestone with the correct href', () => { - const { title } = issuable.milestone; - const expected = mergeUrlParams({ milestone_title: title }, TEST_BASE_URL); - - expect(findMilestone().attributes('href')).toBe(expected); - }); - }); - - describe.each` - dueDate | hasClass | desc - ${TEST_MONTH_LATER} | ${false} | ${'with future due date'} - ${TEST_MONTH_AGO} | ${true} | ${'with past due date'} - `('$desc', ({ dueDate, hasClass }) => { - beforeEach(() => { - issuable.due_date = dueDate; - - factory({ issuable }); - }); - - it('renders due date', () => { - expect(findDueDate().exists()).toBe(true); - expect(findDueDate().text()).toBe(formatDate(dueDate, DATE_FORMAT)); - }); - - it(hasClass ? 'has cred class' : 'does not have cred class', () => { - expect(findDueDate().classes('cred')).toEqual(hasClass); - }); - }); - - describe('with labels', () => { - beforeEach(() => { - issuable.labels = [...testLabels]; - - factory({ issuable }); - }); - - it('renders labels', () => { - factory({ issuable }); - - const labels = findLabels().wrappers.map((label) => ({ - href: label.props('target'), - text: label.text(), - tooltip: label.attributes('description'), - })); - - const expected = testLabels.map((label) => ({ - href: mergeUrlParams({ 'label_name[]': label.name }, TEST_BASE_URL), - text: label.name, - tooltip: label.description, - })); - - expect(labels).toEqual(expected); - }); - }); - - describe('with labels for Jira issuable', () => { - beforeEach(() => { - issuable.labels = [...testLabels]; - issuable.external_tracker = 'jira'; - - factory({ issuable }); - }); - - it('renders labels', () => { - factory({ issuable }); - - const labels = findLabels().wrappers.map((label) => ({ - href: label.props('target'), - text: label.text(), - tooltip: label.attributes('description'), - })); - - const expected = testLabels.map((label) => ({ - href: mergeUrlParams({ 'labels[]': label.name }, TEST_BASE_URL), - text: label.name, - tooltip: label.description, - })); - - expect(labels).toEqual(expected); - }); - }); - - describe.each` - weight - ${0} - ${10} - ${12345} - `('with weight $weight', ({ weight }) => { - beforeEach(() => { - issuable.weight = weight; - - factory({ issuable }); - }); - - it('renders weight', () => { - expect(findWeight().exists()).toBe(true); - expect(findWeight().text()).toEqual(weight.toString()); - }); - }); - - describe('with closed state', () => { - beforeEach(() => { - issuable.state = 'closed'; - - factory({ issuable }); - }); - - it('renders closed text', () => { - expect(wrapper.text()).toContain(TEXT_CLOSED); - }); - - it('has closed class', () => { - expect(wrapper.classes('closed')).toBe(true); - }); - }); - - describe('with assignees', () => { - beforeEach(() => { - issuable.assignees = testAssignees; - - factory({ issuable }); - }); - - it('renders assignees', () => { - expect(findAssignees().exists()).toBe(true); - expect(findAssignees().props('assignees')).toEqual(testAssignees); - }); - }); - - describe.each` - desc | key | finder - ${'with blocking issues count'} | ${'blocking_issues_count'} | ${findBlockingIssuesCount} - ${'with merge requests count'} | ${'merge_requests_count'} | ${findMergeRequestsCount} - ${'with upvote count'} | ${'upvotes'} | ${findUpvotes} - ${'with downvote count'} | ${'downvotes'} | ${findDownvotes} - ${'with notes count'} | ${'user_notes_count'} | ${findNotes} - `('$desc', ({ key, finder }) => { - beforeEach(() => { - issuable[key] = TEST_META_COUNT; - - factory({ issuable }); - }); - - it('renders correct count', () => { - expect(finder().exists()).toBe(true); - expect(finder().text()).toBe(TEST_META_COUNT.toString()); - expect(finder().classes('no-comments')).toBe(false); - }); - }); - - describe('with bulk editing', () => { - describe.each` - selected | desc - ${true} | ${'when selected'} - ${false} | ${'when unselected'} - `('$desc', ({ selected }) => { - beforeEach(() => { - factory({ isBulkEditing: true, selected }); - }); - - it(`renders checked is ${selected}`, () => { - expect(findBulkCheckbox().element.checked).toBe(selected); - }); - - it('emits select when clicked', () => { - expect(wrapper.emitted().select).toBeUndefined(); - - findBulkCheckbox().trigger('click'); - - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.emitted().select).toEqual([[{ issuable, selected: !selected }]]); - }); - }); - }); - }); - - if (IS_EE) { - describe('with health status', () => { - it('renders health status tag', () => { - factory({ issuable }); - expect(findHealthStatus().exists()).toBe(true); - }); - - it('does not render when health status is absent', () => { - issuable.health_status = null; - factory({ issuable }); - expect(findHealthStatus().exists()).toBe(false); - }); - }); - } -}); diff --git a/spec/frontend/issues_list/components/issuables_list_app_spec.js b/spec/frontend/issues_list/components/issuables_list_app_spec.js deleted file mode 100644 index 11854db534e462974c56c2e8e69064e02c421649..0000000000000000000000000000000000000000 --- a/spec/frontend/issues_list/components/issuables_list_app_spec.js +++ /dev/null @@ -1,653 +0,0 @@ -import { - GlEmptyState, - GlPagination, - GlDeprecatedSkeletonLoading as GlSkeletonLoading, -} from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import axios from 'axios'; -import MockAdapter from 'axios-mock-adapter'; -import setWindowLocation from 'helpers/set_window_location_helper'; -import { TEST_HOST } from 'helpers/test_constants'; -import waitForPromises from 'helpers/wait_for_promises'; -import createFlash from '~/flash'; -import Issuable from '~/issues_list/components/issuable.vue'; -import IssuablesListApp from '~/issues_list/components/issuables_list_app.vue'; -import { PAGE_SIZE, PAGE_SIZE_MANUAL, RELATIVE_POSITION } from '~/issues_list/constants'; -import issuablesEventBus from '~/issues_list/eventhub'; -import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; - -jest.mock('~/flash'); -jest.mock('~/issues_list/eventhub'); -jest.mock('~/lib/utils/common_utils', () => ({ - ...jest.requireActual('~/lib/utils/common_utils'), - scrollToElement: () => {}, -})); - -const TEST_LOCATION = `${TEST_HOST}/issues`; -const TEST_ENDPOINT = '/issues'; -const TEST_CREATE_ISSUES_PATH = '/createIssue'; -const TEST_SVG_PATH = '/emptySvg'; - -const MOCK_ISSUES = Array(PAGE_SIZE_MANUAL) - .fill(0) - .map((_, i) => ({ - id: i, - web_url: `url${i}`, - })); - -describe('Issuables list component', () => { - let mockAxios; - let wrapper; - let apiSpy; - - const setupApiMock = (cb) => { - apiSpy = jest.fn(cb); - - mockAxios.onGet(TEST_ENDPOINT).reply((cfg) => apiSpy(cfg)); - }; - - const factory = (props = { sortKey: 'priority' }) => { - const emptyStateMeta = { - createIssuePath: TEST_CREATE_ISSUES_PATH, - svgPath: TEST_SVG_PATH, - }; - - wrapper = shallowMount(IssuablesListApp, { - propsData: { - endpoint: TEST_ENDPOINT, - emptyStateMeta, - ...props, - }, - }); - }; - - const findLoading = () => wrapper.find(GlSkeletonLoading); - const findIssuables = () => wrapper.findAll(Issuable); - const findFilteredSearchBar = () => wrapper.find(FilteredSearchBar); - const findFirstIssuable = () => findIssuables().wrappers[0]; - const findEmptyState = () => wrapper.find(GlEmptyState); - - beforeEach(() => { - mockAxios = new MockAdapter(axios); - - setWindowLocation(TEST_LOCATION); - }); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - mockAxios.restore(); - }); - - describe('with failed issues response', () => { - beforeEach(() => { - setupApiMock(() => [500]); - - factory(); - - return waitForPromises(); - }); - - it('does not show loading', () => { - expect(wrapper.vm.loading).toBe(false); - }); - - it('flashes an error', () => { - expect(createFlash).toHaveBeenCalledTimes(1); - }); - }); - - describe('with successful issues response', () => { - beforeEach(() => { - setupApiMock(() => [ - 200, - MOCK_ISSUES.slice(0, PAGE_SIZE), - { - 'x-total': 100, - 'x-page': 2, - }, - ]); - }); - - it('has default props and data', () => { - factory(); - expect(wrapper.vm).toMatchObject({ - // Props - canBulkEdit: false, - emptyStateMeta: { - createIssuePath: TEST_CREATE_ISSUES_PATH, - svgPath: TEST_SVG_PATH, - }, - // Data - filters: { - state: 'opened', - }, - isBulkEditing: false, - issuables: [], - loading: true, - page: 1, - selection: {}, - totalItems: 0, - }); - }); - - it('does not call API until mounted', () => { - factory(); - expect(apiSpy).not.toHaveBeenCalled(); - }); - - describe('when mounted', () => { - beforeEach(() => { - factory(); - }); - - it('calls API', () => { - expect(apiSpy).toHaveBeenCalled(); - }); - - it('shows loading', () => { - expect(findLoading().exists()).toBe(true); - expect(findIssuables().length).toBe(0); - expect(findEmptyState().exists()).toBe(false); - }); - }); - - describe('when finished loading', () => { - beforeEach(() => { - factory(); - - return waitForPromises(); - }); - - it('does not display empty state', () => { - expect(wrapper.vm.issuables.length).toBeGreaterThan(0); - expect(wrapper.vm.emptyState).toEqual({}); - expect(wrapper.find(GlEmptyState).exists()).toBe(false); - }); - - it('sets the proper page and total items', () => { - expect(wrapper.vm.totalItems).toBe(100); - expect(wrapper.vm.page).toBe(2); - }); - - it('renders one page of issuables and pagination', () => { - expect(findIssuables().length).toBe(PAGE_SIZE); - expect(wrapper.find(GlPagination).exists()).toBe(true); - }); - }); - - it('does not render FilteredSearchBar', () => { - factory(); - - expect(findFilteredSearchBar().exists()).toBe(false); - }); - }); - - describe('with bulk editing enabled', () => { - beforeEach(() => { - issuablesEventBus.$on.mockReset(); - issuablesEventBus.$emit.mockReset(); - - setupApiMock(() => [200, MOCK_ISSUES.slice(0)]); - factory({ canBulkEdit: true }); - - return waitForPromises(); - }); - - it('is not enabled by default', () => { - expect(wrapper.vm.isBulkEditing).toBe(false); - }); - - it('does not select issues by default', () => { - expect(wrapper.vm.selection).toEqual({}); - }); - - it('"Select All" checkbox toggles all visible issuables"', () => { - wrapper.vm.onSelectAll(); - expect(wrapper.vm.selection).toEqual( - wrapper.vm.issuables.reduce((acc, i) => ({ ...acc, [i.id]: true }), {}), - ); - - wrapper.vm.onSelectAll(); - expect(wrapper.vm.selection).toEqual({}); - }); - - it('"Select All checkbox" selects all issuables if only some are selected"', () => { - wrapper.vm.selection = { [wrapper.vm.issuables[0].id]: true }; - wrapper.vm.onSelectAll(); - expect(wrapper.vm.selection).toEqual( - wrapper.vm.issuables.reduce((acc, i) => ({ ...acc, [i.id]: true }), {}), - ); - }); - - it('selects and deselects issuables', () => { - const [i0, i1, i2] = wrapper.vm.issuables; - - expect(wrapper.vm.selection).toEqual({}); - wrapper.vm.onSelectIssuable({ issuable: i0, selected: false }); - expect(wrapper.vm.selection).toEqual({}); - wrapper.vm.onSelectIssuable({ issuable: i1, selected: true }); - expect(wrapper.vm.selection).toEqual({ 1: true }); - wrapper.vm.onSelectIssuable({ issuable: i0, selected: true }); - expect(wrapper.vm.selection).toEqual({ 1: true, 0: true }); - wrapper.vm.onSelectIssuable({ issuable: i2, selected: true }); - expect(wrapper.vm.selection).toEqual({ 1: true, 0: true, 2: true }); - wrapper.vm.onSelectIssuable({ issuable: i2, selected: true }); - expect(wrapper.vm.selection).toEqual({ 1: true, 0: true, 2: true }); - wrapper.vm.onSelectIssuable({ issuable: i0, selected: false }); - expect(wrapper.vm.selection).toEqual({ 1: true, 2: true }); - }); - - it('broadcasts a message to the bulk edit sidebar when a value is added to selection', () => { - issuablesEventBus.$emit.mockReset(); - const i1 = wrapper.vm.issuables[1]; - - wrapper.vm.onSelectIssuable({ issuable: i1, selected: true }); - - return wrapper.vm.$nextTick().then(() => { - expect(issuablesEventBus.$emit).toHaveBeenCalledTimes(1); - expect(issuablesEventBus.$emit).toHaveBeenCalledWith('issuables:updateBulkEdit'); - }); - }); - - it('does not broadcast a message to the bulk edit sidebar when a value is not added to selection', () => { - issuablesEventBus.$emit.mockReset(); - - return wrapper.vm - .$nextTick() - .then(waitForPromises) - .then(() => { - const i1 = wrapper.vm.issuables[1]; - - wrapper.vm.onSelectIssuable({ issuable: i1, selected: false }); - }) - .then(wrapper.vm.$nextTick) - .then(() => { - expect(issuablesEventBus.$emit).toHaveBeenCalledTimes(0); - }); - }); - - it('listens to a message to toggle bulk editing', () => { - expect(wrapper.vm.isBulkEditing).toBe(false); - expect(issuablesEventBus.$on.mock.calls[0][0]).toBe('issuables:toggleBulkEdit'); - issuablesEventBus.$on.mock.calls[0][1](true); // Call the message handler - - return waitForPromises() - .then(() => { - expect(wrapper.vm.isBulkEditing).toBe(true); - issuablesEventBus.$on.mock.calls[0][1](false); - }) - .then(() => { - expect(wrapper.vm.isBulkEditing).toBe(false); - }); - }); - }); - - describe('with query params in window.location', () => { - const expectedFilters = { - assignee_username: 'root', - author_username: 'root', - confidential: 'yes', - my_reaction_emoji: 'airplane', - scope: 'all', - state: 'opened', - weight: '0', - milestone: 'v3.0', - labels: 'Aquapod,Astro', - order_by: 'milestone_due', - sort: 'desc', - }; - - describe('when page is not present in params', () => { - const query = - '?assignee_username=root&author_username=root&confidential=yes&label_name%5B%5D=Aquapod&label_name%5B%5D=Astro&milestone_title=v3.0&my_reaction_emoji=airplane&scope=all&sort=priority&state=opened&weight=0¬[label_name][]=Afterpod¬[milestone_title][]=13'; - - beforeEach(() => { - setWindowLocation(query); - - setupApiMock(() => [200, MOCK_ISSUES.slice(0)]); - factory({ sortKey: 'milestone_due_desc' }); - - return waitForPromises(); - }); - - afterEach(() => { - apiSpy.mockClear(); - }); - - it('applies filters and sorts', () => { - expect(wrapper.vm.hasFilters).toBe(true); - expect(wrapper.vm.filters).toEqual({ - ...expectedFilters, - 'not[milestone]': ['13'], - 'not[labels]': ['Afterpod'], - }); - - expect(apiSpy).toHaveBeenCalledWith( - expect.objectContaining({ - params: { - ...expectedFilters, - with_labels_details: true, - page: 1, - per_page: PAGE_SIZE, - 'not[milestone]': ['13'], - 'not[labels]': ['Afterpod'], - }, - }), - ); - }); - - it('passes the base url to issuable', () => { - expect(findFirstIssuable().props('baseUrl')).toBe(TEST_LOCATION); - }); - }); - - describe('when page is present in the param', () => { - const query = - '?assignee_username=root&author_username=root&confidential=yes&label_name%5B%5D=Aquapod&label_name%5B%5D=Astro&milestone_title=v3.0&my_reaction_emoji=airplane&scope=all&sort=priority&state=opened&weight=0&page=3'; - - beforeEach(() => { - setWindowLocation(query); - - setupApiMock(() => [200, MOCK_ISSUES.slice(0)]); - factory({ sortKey: 'milestone_due_desc' }); - - return waitForPromises(); - }); - - afterEach(() => { - apiSpy.mockClear(); - }); - - it('applies filters and sorts', () => { - expect(apiSpy).toHaveBeenCalledWith( - expect.objectContaining({ - params: { - ...expectedFilters, - with_labels_details: true, - page: 3, - per_page: PAGE_SIZE, - }, - }), - ); - }); - }); - }); - - describe('with hash in window.location', () => { - beforeEach(() => { - setWindowLocation(`${TEST_LOCATION}#stuff`); - setupApiMock(() => [200, MOCK_ISSUES.slice(0)]); - factory(); - return waitForPromises(); - }); - - it('passes the base url to issuable', () => { - expect(findFirstIssuable().props('baseUrl')).toBe(TEST_LOCATION); - }); - }); - - describe('with manual sort', () => { - beforeEach(() => { - setupApiMock(() => [200, MOCK_ISSUES.slice(0)]); - factory({ sortKey: RELATIVE_POSITION }); - }); - - it('uses manual page size', () => { - expect(apiSpy).toHaveBeenCalledWith( - expect.objectContaining({ - params: expect.objectContaining({ - per_page: PAGE_SIZE_MANUAL, - }), - }), - ); - }); - }); - - describe('with empty issues response', () => { - beforeEach(() => { - setupApiMock(() => [200, []]); - }); - - describe('with query in window location', () => { - beforeEach(() => { - setWindowLocation('?weight=Any'); - - factory(); - - return waitForPromises().then(() => wrapper.vm.$nextTick()); - }); - - it('should display "Sorry, your filter produced no results" if filters are too specific', () => { - expect(findEmptyState().props('title')).toMatchSnapshot(); - }); - }); - - describe('with closed state', () => { - beforeEach(() => { - setWindowLocation('?state=closed'); - - factory(); - - return waitForPromises().then(() => wrapper.vm.$nextTick()); - }); - - it('should display a message "There are no closed issues" if there are no closed issues', () => { - expect(findEmptyState().props('title')).toMatchSnapshot(); - }); - }); - - describe('with all state', () => { - beforeEach(() => { - setWindowLocation('?state=all'); - - factory(); - - return waitForPromises().then(() => wrapper.vm.$nextTick()); - }); - - it('should display a catch-all if there are no issues to show', () => { - expect(findEmptyState().element).toMatchSnapshot(); - }); - }); - - describe('with empty query', () => { - beforeEach(() => { - factory(); - - return wrapper.vm.$nextTick().then(waitForPromises); - }); - - it('should display the message "There are no open issues"', () => { - expect(findEmptyState().props('title')).toMatchSnapshot(); - }); - }); - }); - - describe('when paginates', () => { - const newPage = 3; - - describe('when total-items is defined in response headers', () => { - beforeEach(() => { - window.history.pushState = jest.fn(); - setupApiMock(() => [ - 200, - MOCK_ISSUES.slice(0, PAGE_SIZE), - { - 'x-total': 100, - 'x-page': 2, - }, - ]); - - factory(); - - return waitForPromises(); - }); - - afterEach(() => { - // reset to original value - window.history.pushState.mockRestore(); - }); - - it('calls window.history.pushState one time', () => { - // Trigger pagination - wrapper.find(GlPagination).vm.$emit('input', newPage); - - expect(window.history.pushState).toHaveBeenCalledTimes(1); - }); - - it('sets params in the url', () => { - // Trigger pagination - wrapper.find(GlPagination).vm.$emit('input', newPage); - - expect(window.history.pushState).toHaveBeenCalledWith( - {}, - '', - `${TEST_LOCATION}?state=opened&order_by=priority&sort=asc&page=${newPage}`, - ); - }); - }); - - describe('when total-items is not defined in the headers', () => { - const page = 2; - const prevPage = page - 1; - const nextPage = page + 1; - - beforeEach(() => { - setupApiMock(() => [ - 200, - MOCK_ISSUES.slice(0, PAGE_SIZE), - { - 'x-page': page, - }, - ]); - - factory(); - - return waitForPromises(); - }); - - it('finds the correct props applied to GlPagination', () => { - expect(wrapper.find(GlPagination).props()).toMatchObject({ - nextPage, - prevPage, - value: page, - }); - }); - }); - }); - - describe('when type is "jira"', () => { - it('renders FilteredSearchBar', () => { - factory({ type: 'jira' }); - - expect(findFilteredSearchBar().exists()).toBe(true); - }); - - describe('initialSortBy', () => { - const query = '?sort=updated_asc'; - - it('sets default value', () => { - factory({ type: 'jira' }); - - expect(findFilteredSearchBar().props('initialSortBy')).toBe('created_desc'); - }); - - it('sets value according to query', () => { - setWindowLocation(query); - - factory({ type: 'jira' }); - - expect(findFilteredSearchBar().props('initialSortBy')).toBe('updated_asc'); - }); - }); - - describe('initialFilterValue', () => { - it('does not set value when no query', () => { - factory({ type: 'jira' }); - - expect(findFilteredSearchBar().props('initialFilterValue')).toEqual([]); - }); - - it('sets value according to query', () => { - const query = '?search=free+text'; - - setWindowLocation(query); - - factory({ type: 'jira' }); - - expect(findFilteredSearchBar().props('initialFilterValue')).toEqual(['free text']); - }); - }); - - describe('on filter search', () => { - beforeEach(() => { - factory({ type: 'jira' }); - - window.history.pushState = jest.fn(); - }); - - afterEach(() => { - window.history.pushState.mockRestore(); - }); - - const emitOnFilter = (filter) => findFilteredSearchBar().vm.$emit('onFilter', filter); - - describe('empty filter', () => { - const mockFilter = []; - - it('updates URL with correct params', () => { - emitOnFilter(mockFilter); - - expect(window.history.pushState).toHaveBeenCalledWith( - {}, - '', - `${TEST_LOCATION}?state=opened`, - ); - }); - }); - - describe('filter with search term', () => { - const mockFilter = [ - { - type: 'filtered-search-term', - value: { data: 'free' }, - }, - ]; - - it('updates URL with correct params', () => { - emitOnFilter(mockFilter); - - expect(window.history.pushState).toHaveBeenCalledWith( - {}, - '', - `${TEST_LOCATION}?state=opened&search=free`, - ); - }); - }); - - describe('filter with multiple search terms', () => { - const mockFilter = [ - { - type: 'filtered-search-term', - value: { data: 'free' }, - }, - { - type: 'filtered-search-term', - value: { data: 'text' }, - }, - ]; - - it('updates URL with correct params', () => { - emitOnFilter(mockFilter); - - expect(window.history.pushState).toHaveBeenCalledWith( - {}, - '', - `${TEST_LOCATION}?state=opened&search=free+text`, - ); - }); - }); - }); - }); -}); diff --git a/spec/frontend/issues_list/issuable_list_test_data.js b/spec/frontend/issues_list/issuable_list_test_data.js deleted file mode 100644 index 313aa15bd31069c1124b0012012b22f4c3fe94ab..0000000000000000000000000000000000000000 --- a/spec/frontend/issues_list/issuable_list_test_data.js +++ /dev/null @@ -1,77 +0,0 @@ -export const simpleIssue = { - id: 442, - iid: 31, - title: 'Dismiss Cipher with no integrity', - state: 'opened', - created_at: '2019-08-26T19:06:32.667Z', - updated_at: '2019-08-28T19:53:58.314Z', - labels: [], - milestone: null, - assignees: [], - author: { - id: 3, - name: 'Elnora Bernhard', - username: 'treva.lesch', - state: 'active', - avatar_url: 'https://www.gravatar.com/avatar/a8c0d9c2882406cf2a9b71494625a796?s=80&d=identicon', - web_url: 'http://localhost:3001/treva.lesch', - }, - assignee: null, - user_notes_count: 0, - blocking_issues_count: 0, - merge_requests_count: 0, - upvotes: 0, - downvotes: 0, - due_date: null, - confidential: false, - web_url: 'http://localhost:3001/h5bp/html5-boilerplate/issues/31', - has_tasks: false, - weight: null, - references: { - relative: 'html-boilerplate#45', - }, - health_status: 'on_track', -}; - -export const testLabels = [ - { - id: 1, - name: 'Tanuki', - description: 'A cute animal', - color: '#ff0000', - text_color: '#ffffff', - }, - { - id: 2, - name: 'Octocat', - description: 'A grotesque mish-mash of whiskers and tentacles', - color: '#333333', - text_color: '#000000', - }, - { - id: 3, - name: 'scoped::label', - description: 'A scoped label', - color: '#00ff00', - text_color: '#ffffff', - }, -]; - -export const testAssignees = [ - { - id: 1, - name: 'Administrator', - username: 'root', - state: 'active', - avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - web_url: 'http://localhost:3001/root', - }, - { - id: 22, - name: 'User 0', - username: 'user0', - state: 'active', - avatar_url: 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80&d=identicon', - web_url: 'http://localhost:3001/user0', - }, -]; diff --git a/spec/frontend/issues_list/service_desk_helper_spec.js b/spec/frontend/issues_list/service_desk_helper_spec.js deleted file mode 100644 index 16aee853341eba3c05f936997b124949e37548ce..0000000000000000000000000000000000000000 --- a/spec/frontend/issues_list/service_desk_helper_spec.js +++ /dev/null @@ -1,28 +0,0 @@ -import { emptyStateHelper, generateMessages } from '~/issues_list/service_desk_helper'; - -describe('service desk helper', () => { - const emptyStateMessages = generateMessages({}); - - // Note: isServiceDeskEnabled must not be true when isServiceDeskSupported is false (it's an invalid case). - describe.each` - isServiceDeskSupported | isServiceDeskEnabled | canEditProjectSettings | expectedMessage - ${true} | ${true} | ${true} | ${'serviceDeskEnabledAndCanEditProjectSettings'} - ${true} | ${true} | ${false} | ${'serviceDeskEnabledAndCannotEditProjectSettings'} - ${true} | ${false} | ${true} | ${'serviceDeskDisabledAndCanEditProjectSettings'} - ${true} | ${false} | ${false} | ${'serviceDeskDisabledAndCannotEditProjectSettings'} - ${false} | ${false} | ${true} | ${'serviceDeskIsNotSupported'} - ${false} | ${false} | ${false} | ${'serviceDeskIsNotEnabled'} - `( - 'isServiceDeskSupported = $isServiceDeskSupported, isServiceDeskEnabled = $isServiceDeskEnabled, canEditProjectSettings = $canEditProjectSettings', - ({ isServiceDeskSupported, isServiceDeskEnabled, canEditProjectSettings, expectedMessage }) => { - it(`displays ${expectedMessage} message`, () => { - const emptyStateMeta = { - isServiceDeskEnabled, - isServiceDeskSupported, - canEditProjectSettings, - }; - expect(emptyStateHelper(emptyStateMeta)).toEqual(emptyStateMessages[expectedMessage]); - }); - }, - ); -}); diff --git a/spec/frontend/jira_import/utils/jira_import_utils_spec.js b/spec/frontend/jira_import/utils/jira_import_utils_spec.js index 9696d95f8c49952910a4f95d9bfcb80d60f90941..4207038f50cc199f1892ac1fe0ce6496391dc79e 100644 --- a/spec/frontend/jira_import/utils/jira_import_utils_spec.js +++ b/spec/frontend/jira_import/utils/jira_import_utils_spec.js @@ -1,5 +1,5 @@ import { useLocalStorageSpy } from 'helpers/local_storage_helper'; -import { JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY } from '~/issues_list/constants'; +import { JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY } from '~/jira_import/utils/constants'; import { calculateJiraImportLabel, extractJiraProjectsOptions, diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index a03809e5ed06151e29426f802a66ffc06dcbcf56..065ac526ae44b910033de23fb04fc72dce5a2829 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -246,27 +246,6 @@ end end - describe '#use_startup_call' do - it 'returns false when a query param is present' do - allow(controller.request).to receive(:query_parameters).and_return({ foo: 'bar' }) - - expect(helper.use_startup_call?).to eq(false) - end - - it 'returns false when user has stored sort preference' do - controller.instance_variable_set(:@sort, 'updated_asc') - - expect(helper.use_startup_call?).to eq(false) - end - - it 'returns true when request.query_parameters is empty with default sorting preference' do - controller.instance_variable_set(:@sort, 'created_date') - allow(controller.request).to receive(:query_parameters).and_return({}) - - expect(helper.use_startup_call?).to eq(true) - end - end - describe '#issue_header_actions_data' do let(:current_user) { create(:user) } diff --git a/spec/helpers/projects/issues/service_desk_helper_spec.rb b/spec/helpers/projects/issues/service_desk_helper_spec.rb deleted file mode 100644 index 05766ee13c6da39e1ed04d35e119854528d6b8f6..0000000000000000000000000000000000000000 --- a/spec/helpers/projects/issues/service_desk_helper_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Projects::Issues::ServiceDeskHelper do - let_it_be(:project) { create(:project, :public, service_desk_enabled: true) } - - let(:user) { build_stubbed(:user) } - let(:current_user) { user } - - describe '#service_desk_meta' do - subject { helper.service_desk_meta(project) } - - context "when service desk is supported and user can edit project settings" do - before do - allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true) - allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true) - allow(helper).to receive(:current_user).and_return(user) - allow(helper).to receive(:can?).with(current_user, :admin_project, project).and_return(true) - end - - it { - is_expected.to eq({ - is_service_desk_supported: true, - is_service_desk_enabled: true, - can_edit_project_settings: true, - service_desk_address: project.service_desk_address, - service_desk_help_page: help_page_path('user/project/service_desk'), - edit_project_page: edit_project_path(project), - svg_path: ActionController::Base.helpers.image_path('illustrations/service_desk_empty.svg') - }) - } - end - - context "when service desk is not supported and user cannot edit project settings" do - before do - allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(false) - allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(false) - allow(helper).to receive(:current_user).and_return(user) - allow(helper).to receive(:can?).with(current_user, :admin_project, project).and_return(false) - end - - it { - is_expected.to eq({ - is_service_desk_supported: false, - is_service_desk_enabled: false, - can_edit_project_settings: false, - incoming_email_help_page: help_page_path('administration/incoming_email', anchor: 'set-it-up'), - svg_path: ActionController::Base.helpers.image_path('illustrations/service-desk-setup.svg') - }) - } - end - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a2f4236b3ed3a70e4e46a429bf5cf0cbceb76a98..e30a61bfba4d85b6525811697f942049f4aabb01 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -290,10 +290,9 @@ stub_feature_flags(diffs_virtual_scrolling: false) - # The following `vue_issues_list`/`vue_issuables_list` stubs can be removed + # The following `vue_issues_list` stub can be removed # once the Vue issues page has feature parity with the current Haml page stub_feature_flags(vue_issues_list: false) - stub_feature_flags(vue_issuables_list: false) # Disable `refactor_blob_viewer` as we refactor # the blob viewer. See the follwing epic for more: