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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/issues_list/components')
-rw-r--r--app/assets/javascripts/issues_list/components/issuable.vue441
-rw-r--r--app/assets/javascripts/issues_list/components/issuables_list_app.vue426
-rw-r--r--app/assets/javascripts/issues_list/components/issue_card_time_info.vue104
-rw-r--r--app/assets/javascripts/issues_list/components/issues_list_app.vue822
-rw-r--r--app/assets/javascripts/issues_list/components/jira_issues_import_status_app.vue112
-rw-r--r--app/assets/javascripts/issues_list/components/new_issue_dropdown.vue127
6 files changed, 0 insertions, 2032 deletions
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 6476d5be38c..00000000000
--- 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">
- &middot;
- <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 71136bf0159..00000000000
--- 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/components/issue_card_time_info.vue b/app/assets/javascripts/issues_list/components/issue_card_time_info.vue
deleted file mode 100644
index aece7372182..00000000000
--- a/app/assets/javascripts/issues_list/components/issue_card_time_info.vue
+++ /dev/null
@@ -1,104 +0,0 @@
-<script>
-import { GlLink, GlIcon, GlTooltipDirective } from '@gitlab/ui';
-import {
- dateInWords,
- getTimeRemainingInWords,
- isInFuture,
- isInPast,
- isToday,
-} from '~/lib/utils/datetime_utility';
-import { __ } from '~/locale';
-
-export default {
- components: {
- GlLink,
- GlIcon,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- props: {
- issue: {
- type: Object,
- required: true,
- },
- },
- computed: {
- milestoneDate() {
- if (this.issue.milestone?.dueDate) {
- const { dueDate, startDate } = this.issue.milestone;
- const date = dateInWords(new Date(dueDate), true);
- const remainingTime = this.milestoneRemainingTime(dueDate, startDate);
- return `${date} (${remainingTime})`;
- }
- return __('Milestone');
- },
- milestoneLink() {
- return this.issue.milestone.webPath || this.issue.milestone.webUrl;
- },
- dueDate() {
- return this.issue.dueDate && dateInWords(new Date(this.issue.dueDate), true);
- },
- showDueDateInRed() {
- return isInPast(new Date(this.issue.dueDate)) && !this.issue.closedAt;
- },
- timeEstimate() {
- return this.issue.humanTimeEstimate || this.issue.timeStats?.humanTimeEstimate;
- },
- },
- methods: {
- milestoneRemainingTime(dueDate, startDate) {
- const due = new Date(dueDate);
- const start = new Date(startDate);
-
- if (dueDate && isInPast(due)) {
- return __('Past due');
- } else if (dueDate && isToday(due)) {
- return __('Today');
- } else if (startDate && isInFuture(start)) {
- return __('Upcoming');
- } else if (dueDate) {
- return getTimeRemainingInWords(due);
- }
- return '';
- },
- },
-};
-</script>
-
-<template>
- <span>
- <span
- v-if="issue.milestone"
- class="issuable-milestone gl-mr-3"
- data-testid="issuable-milestone"
- >
- <gl-link v-gl-tooltip :href="milestoneLink" :title="milestoneDate">
- <gl-icon name="clock" />
- {{ issue.milestone.title }}
- </gl-link>
- </span>
- <span
- v-if="issue.dueDate"
- v-gl-tooltip
- class="issuable-due-date gl-mr-3"
- :class="{ 'gl-text-red-500': showDueDateInRed }"
- :title="__('Due date')"
- data-testid="issuable-due-date"
- >
- <gl-icon name="calendar" />
- {{ dueDate }}
- </span>
- <span
- v-if="timeEstimate"
- v-gl-tooltip
- class="gl-mr-3"
- :title="__('Estimate')"
- data-testid="time-estimate"
- >
- <gl-icon name="timer" />
- {{ timeEstimate }}
- </span>
- <slot></slot>
- </span>
-</template>
diff --git a/app/assets/javascripts/issues_list/components/issues_list_app.vue b/app/assets/javascripts/issues_list/components/issues_list_app.vue
deleted file mode 100644
index 6ced1080b71..00000000000
--- a/app/assets/javascripts/issues_list/components/issues_list_app.vue
+++ /dev/null
@@ -1,822 +0,0 @@
-<script>
-import {
- GlButton,
- GlEmptyState,
- GlFilteredSearchToken,
- GlIcon,
- GlLink,
- GlSprintf,
- GlTooltipDirective,
-} from '@gitlab/ui';
-import * as Sentry from '@sentry/browser';
-import fuzzaldrinPlus from 'fuzzaldrin-plus';
-import { orderBy } from 'lodash';
-import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql';
-import getIssuesCountsQuery from 'ee_else_ce/issues_list/queries/get_issues_counts.query.graphql';
-import IssueCardTimeInfo from 'ee_else_ce/issues_list/components/issue_card_time_info.vue';
-import createFlash, { FLASH_TYPES } from '~/flash';
-import { TYPE_USER } from '~/graphql_shared/constants';
-import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { ITEM_TYPE } from '~/groups/constants';
-import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
-import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
-import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
-import { IssuableListTabs, IssuableStates } from '~/vue_shared/issuable/list/constants';
-import {
- CREATED_DESC,
- i18n,
- MAX_LIST_SIZE,
- PAGE_SIZE,
- PARAM_DUE_DATE,
- PARAM_SORT,
- PARAM_STATE,
- RELATIVE_POSITION_ASC,
- TOKEN_TYPE_ASSIGNEE,
- TOKEN_TYPE_AUTHOR,
- TOKEN_TYPE_CONFIDENTIAL,
- TOKEN_TYPE_LABEL,
- TOKEN_TYPE_MILESTONE,
- TOKEN_TYPE_MY_REACTION,
- TOKEN_TYPE_RELEASE,
- TOKEN_TYPE_TYPE,
- UPDATED_DESC,
- urlSortParams,
-} from '~/issues_list/constants';
-import {
- convertToApiParams,
- convertToSearchQuery,
- convertToUrlParams,
- getDueDateValue,
- getFilterTokens,
- getInitialPageParams,
- getSortKey,
- getSortOptions,
-} from '~/issues_list/utils';
-import axios from '~/lib/utils/axios_utils';
-import { scrollUp } from '~/lib/utils/scroll_utils';
-import { getParameterByName, joinPaths } from '~/lib/utils/url_utility';
-import {
- DEFAULT_NONE_ANY,
- OPERATOR_IS_ONLY,
- TOKEN_TITLE_ASSIGNEE,
- TOKEN_TITLE_AUTHOR,
- TOKEN_TITLE_CONFIDENTIAL,
- TOKEN_TITLE_LABEL,
- TOKEN_TITLE_MILESTONE,
- TOKEN_TITLE_MY_REACTION,
- TOKEN_TITLE_RELEASE,
- TOKEN_TITLE_TYPE,
-} from '~/vue_shared/components/filtered_search_bar/constants';
-import eventHub from '../eventhub';
-import reorderIssuesMutation from '../queries/reorder_issues.mutation.graphql';
-import searchLabelsQuery from '../queries/search_labels.query.graphql';
-import searchMilestonesQuery from '../queries/search_milestones.query.graphql';
-import searchUsersQuery from '../queries/search_users.query.graphql';
-import NewIssueDropdown from './new_issue_dropdown.vue';
-
-const AuthorToken = () =>
- import('~/vue_shared/components/filtered_search_bar/tokens/author_token.vue');
-const EmojiToken = () =>
- import('~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue');
-const LabelToken = () =>
- import('~/vue_shared/components/filtered_search_bar/tokens/label_token.vue');
-const MilestoneToken = () =>
- import('~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue');
-const ReleaseToken = () =>
- import('~/vue_shared/components/filtered_search_bar/tokens/release_token.vue');
-
-export default {
- i18n,
- IssuableListTabs,
- components: {
- CsvImportExportButtons,
- GlButton,
- GlEmptyState,
- GlIcon,
- GlLink,
- GlSprintf,
- IssuableByEmail,
- IssuableList,
- IssueCardTimeInfo,
- NewIssueDropdown,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- inject: {
- autocompleteAwardEmojisPath: {
- default: '',
- },
- calendarPath: {
- default: '',
- },
- canBulkUpdate: {
- default: false,
- },
- emptyStateSvgPath: {
- default: '',
- },
- exportCsvPath: {
- default: '',
- },
- fullPath: {
- default: '',
- },
- hasAnyIssues: {
- default: false,
- },
- hasAnyProjects: {
- default: false,
- },
- hasBlockedIssuesFeature: {
- default: false,
- },
- hasIssueWeightsFeature: {
- default: false,
- },
- hasMultipleIssueAssigneesFeature: {
- default: false,
- },
- initialEmail: {
- default: '',
- },
- isAnonymousSearchDisabled: {
- default: false,
- },
- isIssueRepositioningDisabled: {
- default: false,
- },
- isProject: {
- default: false,
- },
- isSignedIn: {
- default: false,
- },
- jiraIntegrationPath: {
- default: '',
- },
- newIssuePath: {
- default: '',
- },
- releasesPath: {
- default: '',
- },
- rssPath: {
- default: '',
- },
- showNewIssueLink: {
- default: false,
- },
- signInPath: {
- default: '',
- },
- },
- props: {
- eeSearchTokens: {
- type: Array,
- required: false,
- default: () => [],
- },
- },
- data() {
- const state = getParameterByName(PARAM_STATE);
- const defaultSortKey = state === IssuableStates.Closed ? UPDATED_DESC : CREATED_DESC;
- let sortKey = getSortKey(getParameterByName(PARAM_SORT)) || defaultSortKey;
-
- if (this.isIssueRepositioningDisabled && sortKey === RELATIVE_POSITION_ASC) {
- this.showIssueRepositioningMessage();
- sortKey = defaultSortKey;
- }
-
- const isSearchDisabled =
- this.isAnonymousSearchDisabled &&
- !this.isSignedIn &&
- window.location.search.includes('search=');
-
- if (isSearchDisabled) {
- this.showAnonymousSearchingMessage();
- }
-
- return {
- dueDateFilter: getDueDateValue(getParameterByName(PARAM_DUE_DATE)),
- exportCsvPathWithQuery: this.getExportCsvPathWithQuery(),
- filterTokens: isSearchDisabled ? [] : getFilterTokens(window.location.search),
- issues: [],
- issuesCounts: {},
- issuesError: null,
- pageInfo: {},
- pageParams: getInitialPageParams(sortKey),
- showBulkEditSidebar: false,
- sortKey,
- state: state || IssuableStates.Opened,
- };
- },
- apollo: {
- issues: {
- query: getIssuesQuery,
- variables() {
- return this.queryVariables;
- },
- update(data) {
- return data[this.namespace]?.issues.nodes ?? [];
- },
- result({ data }) {
- this.pageInfo = data[this.namespace]?.issues.pageInfo ?? {};
- this.exportCsvPathWithQuery = this.getExportCsvPathWithQuery();
- },
- error(error) {
- this.issuesError = this.$options.i18n.errorFetchingIssues;
- Sentry.captureException(error);
- },
- skip() {
- return !this.hasAnyIssues;
- },
- debounce: 200,
- },
- issuesCounts: {
- query: getIssuesCountsQuery,
- variables() {
- return this.queryVariables;
- },
- update(data) {
- return data[this.namespace] ?? {};
- },
- error(error) {
- this.issuesError = this.$options.i18n.errorFetchingCounts;
- Sentry.captureException(error);
- },
- skip() {
- return !this.hasAnyIssues;
- },
- debounce: 200,
- context: {
- isSingleRequest: true,
- },
- },
- },
- computed: {
- queryVariables() {
- return {
- fullPath: this.fullPath,
- isProject: this.isProject,
- isSignedIn: this.isSignedIn,
- search: this.searchQuery,
- sort: this.sortKey,
- state: this.state,
- ...this.pageParams,
- ...this.apiFilterParams,
- };
- },
- namespace() {
- return this.isProject ? ITEM_TYPE.PROJECT : ITEM_TYPE.GROUP;
- },
- hasSearch() {
- return this.searchQuery || Object.keys(this.urlFilterParams).length;
- },
- isBulkEditButtonDisabled() {
- return this.showBulkEditSidebar || !this.issues.length;
- },
- isManualOrdering() {
- return this.sortKey === RELATIVE_POSITION_ASC;
- },
- isOpenTab() {
- return this.state === IssuableStates.Opened;
- },
- showCsvButtons() {
- return this.isProject && this.isSignedIn;
- },
- showNewIssueDropdown() {
- return !this.isProject && this.hasAnyProjects;
- },
- apiFilterParams() {
- return convertToApiParams(this.filterTokens);
- },
- urlFilterParams() {
- return convertToUrlParams(this.filterTokens);
- },
- searchQuery() {
- return convertToSearchQuery(this.filterTokens) || undefined;
- },
- searchTokens() {
- const preloadedAuthors = [];
-
- if (gon.current_user_id) {
- preloadedAuthors.push({
- id: convertToGraphQLId(TYPE_USER, gon.current_user_id),
- name: gon.current_user_fullname,
- username: gon.current_username,
- avatar_url: gon.current_user_avatar_url,
- });
- }
-
- const tokens = [
- {
- type: TOKEN_TYPE_AUTHOR,
- title: TOKEN_TITLE_AUTHOR,
- icon: 'pencil',
- token: AuthorToken,
- dataType: 'user',
- unique: true,
- defaultAuthors: [],
- fetchAuthors: this.fetchUsers,
- recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-author`,
- preloadedAuthors,
- },
- {
- type: TOKEN_TYPE_ASSIGNEE,
- title: TOKEN_TITLE_ASSIGNEE,
- icon: 'user',
- token: AuthorToken,
- dataType: 'user',
- unique: !this.hasMultipleIssueAssigneesFeature,
- defaultAuthors: DEFAULT_NONE_ANY,
- fetchAuthors: this.fetchUsers,
- recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-assignee`,
- preloadedAuthors,
- },
- {
- type: TOKEN_TYPE_MILESTONE,
- title: TOKEN_TITLE_MILESTONE,
- icon: 'clock',
- token: MilestoneToken,
- fetchMilestones: this.fetchMilestones,
- recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-milestone`,
- },
- {
- type: TOKEN_TYPE_LABEL,
- title: TOKEN_TITLE_LABEL,
- icon: 'labels',
- token: LabelToken,
- defaultLabels: DEFAULT_NONE_ANY,
- fetchLabels: this.fetchLabels,
- recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-label`,
- },
- {
- type: TOKEN_TYPE_TYPE,
- title: TOKEN_TITLE_TYPE,
- icon: 'issues',
- token: GlFilteredSearchToken,
- options: [
- { icon: 'issue-type-issue', title: 'issue', value: 'issue' },
- { icon: 'issue-type-incident', title: 'incident', value: 'incident' },
- { icon: 'issue-type-test-case', title: 'test_case', value: 'test_case' },
- ],
- },
- ];
-
- if (this.isProject) {
- tokens.push({
- type: TOKEN_TYPE_RELEASE,
- title: TOKEN_TITLE_RELEASE,
- icon: 'rocket',
- token: ReleaseToken,
- fetchReleases: this.fetchReleases,
- recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-release`,
- });
- }
-
- if (this.isSignedIn) {
- tokens.push({
- type: TOKEN_TYPE_MY_REACTION,
- title: TOKEN_TITLE_MY_REACTION,
- icon: 'thumb-up',
- token: EmojiToken,
- unique: true,
- fetchEmojis: this.fetchEmojis,
- recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-my_reaction`,
- });
-
- tokens.push({
- type: TOKEN_TYPE_CONFIDENTIAL,
- title: TOKEN_TITLE_CONFIDENTIAL,
- icon: 'eye-slash',
- token: GlFilteredSearchToken,
- unique: true,
- operators: OPERATOR_IS_ONLY,
- options: [
- { icon: 'eye-slash', value: 'yes', title: this.$options.i18n.confidentialYes },
- { icon: 'eye', value: 'no', title: this.$options.i18n.confidentialNo },
- ],
- });
- }
-
- if (this.eeSearchTokens.length) {
- tokens.push(...this.eeSearchTokens);
- }
-
- tokens.sort((a, b) => a.title.localeCompare(b.title));
-
- return orderBy(tokens, ['title']);
- },
- showPaginationControls() {
- return this.issues.length > 0 && (this.pageInfo.hasNextPage || this.pageInfo.hasPreviousPage);
- },
- sortOptions() {
- return getSortOptions(this.hasIssueWeightsFeature, this.hasBlockedIssuesFeature);
- },
- tabCounts() {
- const { openedIssues, closedIssues, allIssues } = this.issuesCounts;
- return {
- [IssuableStates.Opened]: openedIssues?.count,
- [IssuableStates.Closed]: closedIssues?.count,
- [IssuableStates.All]: allIssues?.count,
- };
- },
- currentTabCount() {
- return this.tabCounts[this.state] ?? 0;
- },
- urlParams() {
- return {
- due_date: this.dueDateFilter,
- search: this.searchQuery,
- sort: urlSortParams[this.sortKey],
- state: this.state,
- ...this.urlFilterParams,
- };
- },
- },
- created() {
- this.cache = {};
- },
- mounted() {
- eventHub.$on('issuables:toggleBulkEdit', this.toggleBulkEditSidebar);
- },
- beforeDestroy() {
- eventHub.$off('issuables:toggleBulkEdit', this.toggleBulkEditSidebar);
- },
- methods: {
- fetchWithCache(path, cacheName, searchKey, search, wrapData = false) {
- if (this.cache[cacheName]) {
- const data = search
- ? fuzzaldrinPlus.filter(this.cache[cacheName], search, { key: searchKey })
- : this.cache[cacheName].slice(0, MAX_LIST_SIZE);
- return wrapData ? Promise.resolve({ data }) : Promise.resolve(data);
- }
-
- return axios.get(path).then(({ data }) => {
- this.cache[cacheName] = data;
- const result = data.slice(0, MAX_LIST_SIZE);
- return wrapData ? { data: result } : result;
- });
- },
- fetchEmojis(search) {
- return this.fetchWithCache(this.autocompleteAwardEmojisPath, 'emojis', 'name', search);
- },
- fetchReleases(search) {
- return this.fetchWithCache(this.releasesPath, 'releases', 'tag', search);
- },
- fetchLabels(search) {
- return this.$apollo
- .query({
- query: searchLabelsQuery,
- variables: { fullPath: this.fullPath, search, isProject: this.isProject },
- })
- .then(({ data }) => data[this.namespace]?.labels.nodes)
- .then((labels) =>
- // TODO remove once we can search by title-only on the backend
- // https://gitlab.com/gitlab-org/gitlab/-/issues/346353
- labels.filter((label) => label.title.toLowerCase().includes(search.toLowerCase())),
- );
- },
- fetchMilestones(search) {
- return this.$apollo
- .query({
- query: searchMilestonesQuery,
- variables: { fullPath: this.fullPath, search, isProject: this.isProject },
- })
- .then(({ data }) => data[this.namespace]?.milestones.nodes);
- },
- fetchUsers(search) {
- return this.$apollo
- .query({
- query: searchUsersQuery,
- variables: { fullPath: this.fullPath, search, isProject: this.isProject },
- })
- .then(({ data }) =>
- data[this.namespace]?.[`${this.namespace}Members`].nodes.map((member) => member.user),
- );
- },
- getExportCsvPathWithQuery() {
- return `${this.exportCsvPath}${window.location.search}`;
- },
- getStatus(issue) {
- if (issue.closedAt && issue.moved) {
- return this.$options.i18n.closedMoved;
- }
- if (issue.closedAt) {
- return this.$options.i18n.closed;
- }
- return undefined;
- },
- handleUpdateLegacyBulkEdit() {
- // If "select all" checkbox was checked, wait for all checkboxes
- // to be checked before updating IssuableBulkUpdateSidebar class
- this.$nextTick(() => {
- eventHub.$emit('issuables:updateBulkEdit');
- });
- },
- async handleBulkUpdateClick() {
- if (!this.hasInitBulkEdit) {
- const initBulkUpdateSidebar = await import(
- '~/issuable/bulk_update_sidebar/issuable_init_bulk_update_sidebar'
- );
- initBulkUpdateSidebar.default.init('issuable_');
-
- const usersSelect = await import('~/users_select');
- const UsersSelect = usersSelect.default;
- new UsersSelect(); // eslint-disable-line no-new
-
- this.hasInitBulkEdit = true;
- }
-
- eventHub.$emit('issuables:enableBulkEdit');
- },
- handleClickTab(state) {
- if (this.state !== state) {
- this.pageParams = getInitialPageParams(this.sortKey);
- }
- this.state = state;
- },
- handleDismissAlert() {
- this.issuesError = null;
- },
- handleFilter(filter) {
- if (this.isAnonymousSearchDisabled && !this.isSignedIn) {
- this.showAnonymousSearchingMessage();
- return;
- }
- this.pageParams = getInitialPageParams(this.sortKey);
- this.filterTokens = filter;
- },
- handleNextPage() {
- this.pageParams = {
- afterCursor: this.pageInfo.endCursor,
- firstPageSize: PAGE_SIZE,
- };
- scrollUp();
- },
- handlePreviousPage() {
- this.pageParams = {
- beforeCursor: this.pageInfo.startCursor,
- lastPageSize: PAGE_SIZE,
- };
- scrollUp();
- },
- handleReorder({ newIndex, oldIndex }) {
- const issueToMove = this.issues[oldIndex];
- const isDragDropDownwards = newIndex > oldIndex;
- const isMovingToBeginning = newIndex === 0;
- const isMovingToEnd = newIndex === this.issues.length - 1;
-
- let moveBeforeId;
- let moveAfterId;
-
- if (isDragDropDownwards) {
- const afterIndex = isMovingToEnd ? newIndex : newIndex + 1;
- moveBeforeId = this.issues[newIndex].id;
- moveAfterId = this.issues[afterIndex].id;
- } else {
- const beforeIndex = isMovingToBeginning ? newIndex : newIndex - 1;
- moveBeforeId = this.issues[beforeIndex].id;
- moveAfterId = this.issues[newIndex].id;
- }
-
- return axios
- .put(joinPaths(issueToMove.webPath, 'reorder'), {
- move_before_id: isMovingToBeginning ? null : getIdFromGraphQLId(moveBeforeId),
- move_after_id: isMovingToEnd ? null : getIdFromGraphQLId(moveAfterId),
- group_full_path: this.isProject ? undefined : this.fullPath,
- })
- .then(() => {
- const serializedVariables = JSON.stringify(this.queryVariables);
- return this.$apollo.mutate({
- mutation: reorderIssuesMutation,
- variables: { oldIndex, newIndex, namespace: this.namespace, serializedVariables },
- });
- })
- .catch((error) => {
- this.issuesError = this.$options.i18n.reorderError;
- Sentry.captureException(error);
- });
- },
- handleSort(sortKey) {
- if (this.isIssueRepositioningDisabled && sortKey === RELATIVE_POSITION_ASC) {
- this.showIssueRepositioningMessage();
- return;
- }
-
- if (this.sortKey !== sortKey) {
- this.pageParams = getInitialPageParams(sortKey);
- }
- this.sortKey = sortKey;
- },
- showAnonymousSearchingMessage() {
- createFlash({
- message: this.$options.i18n.anonymousSearchingMessage,
- type: FLASH_TYPES.NOTICE,
- });
- },
- showIssueRepositioningMessage() {
- createFlash({
- message: this.$options.i18n.issueRepositioningMessage,
- type: FLASH_TYPES.NOTICE,
- });
- },
- toggleBulkEditSidebar(showBulkEditSidebar) {
- this.showBulkEditSidebar = showBulkEditSidebar;
- },
- },
-};
-</script>
-
-<template>
- <div v-if="hasAnyIssues">
- <issuable-list
- :namespace="fullPath"
- recent-searches-storage-key="issues"
- :search-input-placeholder="$options.i18n.searchPlaceholder"
- :search-tokens="searchTokens"
- :initial-filter-value="filterTokens"
- :sort-options="sortOptions"
- :initial-sort-by="sortKey"
- :issuables="issues"
- :error="issuesError"
- label-filter-param="label_name"
- :tabs="$options.IssuableListTabs"
- :current-tab="state"
- :tab-counts="tabCounts"
- :issuables-loading="$apollo.queries.issues.loading"
- :is-manual-ordering="isManualOrdering"
- :show-bulk-edit-sidebar="showBulkEditSidebar"
- :show-pagination-controls="showPaginationControls"
- :use-keyset-pagination="true"
- :has-next-page="pageInfo.hasNextPage"
- :has-previous-page="pageInfo.hasPreviousPage"
- :url-params="urlParams"
- @click-tab="handleClickTab"
- @dismiss-alert="handleDismissAlert"
- @filter="handleFilter"
- @next-page="handleNextPage"
- @previous-page="handlePreviousPage"
- @reorder="handleReorder"
- @sort="handleSort"
- @update-legacy-bulk-edit="handleUpdateLegacyBulkEdit"
- >
- <template #nav-actions>
- <gl-button
- v-gl-tooltip
- :href="rssPath"
- icon="rss"
- :title="$options.i18n.rssLabel"
- :aria-label="$options.i18n.rssLabel"
- />
- <gl-button
- v-gl-tooltip
- :href="calendarPath"
- icon="calendar"
- :title="$options.i18n.calendarLabel"
- :aria-label="$options.i18n.calendarLabel"
- />
- <csv-import-export-buttons
- v-if="showCsvButtons"
- class="gl-md-mr-3"
- :export-csv-path="exportCsvPathWithQuery"
- :issuable-count="currentTabCount"
- />
- <gl-button
- v-if="canBulkUpdate"
- :disabled="isBulkEditButtonDisabled"
- @click="handleBulkUpdateClick"
- >
- {{ $options.i18n.editIssues }}
- </gl-button>
- <gl-button v-if="showNewIssueLink" :href="newIssuePath" variant="confirm">
- {{ $options.i18n.newIssueLabel }}
- </gl-button>
- <new-issue-dropdown v-if="showNewIssueDropdown" />
- </template>
-
- <template #timeframe="{ issuable = {} }">
- <issue-card-time-info :issue="issuable" />
- </template>
-
- <template #status="{ issuable = {} }">
- {{ getStatus(issuable) }}
- </template>
-
- <template #statistics="{ issuable = {} }">
- <li
- v-if="issuable.mergeRequestsCount"
- v-gl-tooltip
- class="gl-display-none gl-sm-display-block"
- :title="$options.i18n.relatedMergeRequests"
- data-testid="merge-requests"
- >
- <gl-icon name="merge-request" />
- {{ issuable.mergeRequestsCount }}
- </li>
- <li
- v-if="issuable.upvotes"
- v-gl-tooltip
- class="issuable-upvotes gl-display-none gl-sm-display-block"
- :title="$options.i18n.upvotes"
- data-testid="issuable-upvotes"
- >
- <gl-icon name="thumb-up" />
- {{ issuable.upvotes }}
- </li>
- <li
- v-if="issuable.downvotes"
- v-gl-tooltip
- class="issuable-downvotes gl-display-none gl-sm-display-block"
- :title="$options.i18n.downvotes"
- data-testid="issuable-downvotes"
- >
- <gl-icon name="thumb-down" />
- {{ issuable.downvotes }}
- </li>
- <slot :issuable="issuable"></slot>
- </template>
-
- <template #empty-state>
- <gl-empty-state
- v-if="hasSearch"
- :description="$options.i18n.noSearchResultsDescription"
- :title="$options.i18n.noSearchResultsTitle"
- :svg-path="emptyStateSvgPath"
- >
- <template #actions>
- <gl-button v-if="showNewIssueLink" :href="newIssuePath" variant="confirm">
- {{ $options.i18n.newIssueLabel }}
- </gl-button>
- </template>
- </gl-empty-state>
-
- <gl-empty-state
- v-else-if="isOpenTab"
- :description="$options.i18n.noOpenIssuesDescription"
- :title="$options.i18n.noOpenIssuesTitle"
- :svg-path="emptyStateSvgPath"
- >
- <template #actions>
- <gl-button v-if="showNewIssueLink" :href="newIssuePath" variant="confirm">
- {{ $options.i18n.newIssueLabel }}
- </gl-button>
- </template>
- </gl-empty-state>
-
- <gl-empty-state
- v-else
- :title="$options.i18n.noClosedIssuesTitle"
- :svg-path="emptyStateSvgPath"
- />
- </template>
- </issuable-list>
-
- <issuable-by-email v-if="initialEmail" class="gl-text-center gl-pt-5 gl-pb-7" />
- </div>
-
- <div v-else-if="isSignedIn">
- <gl-empty-state
- :description="$options.i18n.noIssuesSignedInDescription"
- :title="$options.i18n.noIssuesSignedInTitle"
- :svg-path="emptyStateSvgPath"
- >
- <template #actions>
- <gl-button v-if="showNewIssueLink" :href="newIssuePath" variant="confirm">
- {{ $options.i18n.newIssueLabel }}
- </gl-button>
- <csv-import-export-buttons
- v-if="showCsvButtons"
- class="gl-mr-3"
- :export-csv-path="exportCsvPathWithQuery"
- :issuable-count="currentTabCount"
- />
- <new-issue-dropdown v-if="showNewIssueDropdown" />
- </template>
- </gl-empty-state>
- <hr />
- <p class="gl-text-center gl-font-weight-bold gl-mb-0">
- {{ $options.i18n.jiraIntegrationTitle }}
- </p>
- <p class="gl-text-center gl-mb-0">
- <gl-sprintf :message="$options.i18n.jiraIntegrationMessage">
- <template #jiraDocsLink="{ content }">
- <gl-link :href="jiraIntegrationPath">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </p>
- <p class="gl-text-center gl-text-gray-500">
- {{ $options.i18n.jiraIntegrationSecondaryMessage }}
- </p>
- </div>
-
- <gl-empty-state
- v-else
- :description="$options.i18n.noIssuesSignedOutDescription"
- :title="$options.i18n.noIssuesSignedOutTitle"
- :svg-path="emptyStateSvgPath"
- :primary-button-text="$options.i18n.noIssuesSignedOutButtonText"
- :primary-button-link="signInPath"
- />
-</template>
diff --git a/app/assets/javascripts/issues_list/components/jira_issues_import_status_app.vue b/app/assets/javascripts/issues_list/components/jira_issues_import_status_app.vue
deleted file mode 100644
index fb1dbef666c..00000000000
--- a/app/assets/javascripts/issues_list/components/jira_issues_import_status_app.vue
+++ /dev/null
@@ -1,112 +0,0 @@
-<script>
-import { GlAlert, GlLabel } from '@gitlab/ui';
-import { last } from 'lodash';
-import {
- calculateJiraImportLabel,
- isInProgress,
- setFinishedAlertHideMap,
- shouldShowFinishedAlert,
-} from '~/jira_import/utils/jira_import_utils';
-import { n__ } from '~/locale';
-import getIssuesListDetailsQuery from '../queries/get_issues_list_details.query.graphql';
-
-export default {
- name: 'JiraIssuesImportStatus',
- components: {
- GlAlert,
- GlLabel,
- },
- props: {
- canEdit: {
- type: Boolean,
- required: true,
- },
- isJiraConfigured: {
- type: Boolean,
- required: true,
- },
- issuesPath: {
- type: String,
- required: true,
- },
- projectPath: {
- type: String,
- required: true,
- },
- },
- data() {
- return {
- jiraImport: {},
- };
- },
- apollo: {
- jiraImport: {
- query: getIssuesListDetailsQuery,
- variables() {
- return {
- fullPath: this.projectPath,
- };
- },
- update: ({ project }) => {
- const label = calculateJiraImportLabel(
- project.jiraImports.nodes,
- project.issues.nodes.flatMap(({ labels }) => labels.nodes),
- );
- return {
- importedIssuesCount: last(project.jiraImports.nodes)?.importedIssuesCount,
- label,
- shouldShowFinishedAlert: shouldShowFinishedAlert(label.title, project.jiraImportStatus),
- shouldShowInProgressAlert: isInProgress(project.jiraImportStatus),
- };
- },
- },
- },
- computed: {
- finishedMessage() {
- return n__(
- '%d issue successfully imported with the label',
- '%d issues successfully imported with the label',
- this.jiraImport.importedIssuesCount,
- );
- },
- labelTarget() {
- return `${this.issuesPath}?label_name[]=${encodeURIComponent(this.jiraImport.label.title)}`;
- },
- shouldRender() {
- return this.jiraImport.shouldShowInProgressAlert || this.jiraImport.shouldShowFinishedAlert;
- },
- },
- methods: {
- hideFinishedAlert() {
- setFinishedAlertHideMap(this.jiraImport.label.title);
- this.jiraImport.shouldShowFinishedAlert = false;
- },
- hideInProgressAlert() {
- this.jiraImport.shouldShowInProgressAlert = false;
- },
- },
-};
-</script>
-
-<template>
- <div v-if="shouldRender" class="gl-my-5">
- <gl-alert v-if="jiraImport.shouldShowInProgressAlert" @dismiss="hideInProgressAlert">
- {{ __('Import in progress. Refresh page to see newly added issues.') }}
- </gl-alert>
-
- <gl-alert
- v-else-if="jiraImport.shouldShowFinishedAlert"
- variant="success"
- @dismiss="hideFinishedAlert"
- >
- {{ finishedMessage }}
- <gl-label
- :background-color="jiraImport.label.color"
- scoped
- size="sm"
- :target="labelTarget"
- :title="jiraImport.label.title"
- />
- </gl-alert>
- </div>
-</template>
diff --git a/app/assets/javascripts/issues_list/components/new_issue_dropdown.vue b/app/assets/javascripts/issues_list/components/new_issue_dropdown.vue
deleted file mode 100644
index e749579af80..00000000000
--- a/app/assets/javascripts/issues_list/components/new_issue_dropdown.vue
+++ /dev/null
@@ -1,127 +0,0 @@
-<script>
-import {
- GlDropdown,
- GlDropdownItem,
- GlDropdownText,
- GlLoadingIcon,
- GlSearchBoxByType,
-} from '@gitlab/ui';
-import createFlash from '~/flash';
-import searchProjectsQuery from '~/issues_list/queries/search_projects.query.graphql';
-import { DASH_SCOPE, joinPaths } from '~/lib/utils/url_utility';
-import { __, sprintf } from '~/locale';
-import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
-
-export default {
- i18n: {
- defaultDropdownText: __('Select project to create issue'),
- noMatchesFound: __('No matches found'),
- toggleButtonLabel: __('Toggle project select'),
- },
- components: {
- GlDropdown,
- GlDropdownItem,
- GlDropdownText,
- GlLoadingIcon,
- GlSearchBoxByType,
- },
- inject: ['fullPath'],
- data() {
- return {
- projects: [],
- search: '',
- selectedProject: {},
- shouldSkipQuery: true,
- };
- },
- apollo: {
- projects: {
- query: searchProjectsQuery,
- variables() {
- return {
- fullPath: this.fullPath,
- search: this.search,
- };
- },
- update: ({ group }) => group.projects.nodes ?? [],
- error(error) {
- createFlash({
- message: __('An error occurred while loading projects.'),
- captureError: true,
- error,
- });
- },
- skip() {
- return this.shouldSkipQuery;
- },
- debounce: DEBOUNCE_DELAY,
- },
- },
- computed: {
- dropdownHref() {
- return this.hasSelectedProject
- ? joinPaths(this.selectedProject.webUrl, DASH_SCOPE, 'issues/new')
- : undefined;
- },
- dropdownText() {
- return this.hasSelectedProject
- ? sprintf(__('New issue in %{project}'), { project: this.selectedProject.name })
- : this.$options.i18n.defaultDropdownText;
- },
- hasSelectedProject() {
- return this.selectedProject.id;
- },
- projectsWithIssuesEnabled() {
- return this.projects.filter((project) => project.issuesEnabled);
- },
- showNoSearchResultsText() {
- return !this.projectsWithIssuesEnabled.length && this.search;
- },
- },
- methods: {
- handleDropdownClick() {
- if (!this.dropdownHref) {
- this.$refs.dropdown.show();
- }
- },
- handleDropdownShown() {
- if (this.shouldSkipQuery) {
- this.shouldSkipQuery = false;
- }
- this.$refs.search.focusInput();
- },
- selectProject(project) {
- this.selectedProject = project;
- },
- },
-};
-</script>
-
-<template>
- <gl-dropdown
- ref="dropdown"
- right
- split
- :split-href="dropdownHref"
- :text="dropdownText"
- :toggle-text="$options.i18n.toggleButtonLabel"
- variant="confirm"
- @click="handleDropdownClick"
- @shown="handleDropdownShown"
- >
- <gl-search-box-by-type ref="search" v-model.trim="search" />
- <gl-loading-icon v-if="$apollo.queries.projects.loading" />
- <template v-else>
- <gl-dropdown-item
- v-for="project of projectsWithIssuesEnabled"
- :key="project.id"
- @click="selectProject(project)"
- >
- {{ project.nameWithNamespace }}
- </gl-dropdown-item>
- <gl-dropdown-text v-if="showNoSearchResultsText">
- {{ $options.i18n.noMatchesFound }}
- </gl-dropdown-text>
- </template>
- </gl-dropdown>
-</template>