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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-03-16 21:18:33 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-03-16 21:18:33 +0300
commitf64a639bcfa1fc2bc89ca7db268f594306edfd7c (patch)
treea2c3c2ebcc3b45e596949db485d6ed18ffaacfa1 /app/assets/javascripts/issues_list
parentbfbc3e0d6583ea1a91f627528bedc3d65ba4b10f (diff)
Add latest changes from gitlab-org/gitlab@13-10-stable-eev13.10.0-rc40
Diffstat (limited to 'app/assets/javascripts/issues_list')
-rw-r--r--app/assets/javascripts/issues_list/components/issuable.vue31
-rw-r--r--app/assets/javascripts/issues_list/components/issue_card_time_info.vue124
-rw-r--r--app/assets/javascripts/issues_list/components/issues_list_app.vue143
-rw-r--r--app/assets/javascripts/issues_list/components/jira_issues_import_status_app.vue (renamed from app/assets/javascripts/issues_list/components/jira_issues_list_root.vue)6
-rw-r--r--app/assets/javascripts/issues_list/index.js42
-rw-r--r--app/assets/javascripts/issues_list/service_desk_helper.js30
6 files changed, 341 insertions, 35 deletions
diff --git a/app/assets/javascripts/issues_list/components/issuable.vue b/app/assets/javascripts/issues_list/components/issuable.vue
index b7af6e098e1..60b01a6d37f 100644
--- a/app/assets/javascripts/issues_list/components/issuable.vue
+++ b/app/assets/javascripts/issues_list/components/issuable.vue
@@ -25,16 +25,16 @@ import {
newDateAsLocaleTime,
} from '~/lib/utils/datetime_utility';
import { convertToCamelCase } from '~/lib/utils/text_utility';
-import { mergeUrlParams } from '~/lib/utils/url_utility';
+import { mergeUrlParams, setUrlFragment, isExternal } from '~/lib/utils/url_utility';
import { sprintf, __ } from '~/locale';
import initUserPopovers from '~/user_popovers';
import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
export default {
i18n: {
- openedAgo: __('opened %{timeAgoString} by %{user}'),
- openedAgoJira: __('opened %{timeAgoString} by %{user} in Jira'),
- openedAgoServiceDesk: __('opened %{timeAgoString} by %{email} via %{user}'),
+ openedAgo: __('created %{timeAgoString} by %{user}'),
+ openedAgoJira: __('created %{timeAgoString} by %{user} in Jira'),
+ openedAgoServiceDesk: __('created %{timeAgoString} by %{email} via %{user}'),
},
components: {
IssueAssignees,
@@ -102,8 +102,14 @@ export default {
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.isJiraIssue ? '_blank' : null;
+ return this.isIssuableUrlExternal ? '_blank' : null;
},
issueCreatedToday() {
return getDayDifference(new Date(this.issuable.created_at), new Date()) < 1;
@@ -188,7 +194,7 @@ export default {
value: this.issuable.blocking_issues_count,
title: __('Blocking issues'),
dataTestId: 'blocking-issues',
- href: `${this.issuable.web_url}#related-issues`,
+ href: setUrlFragment(this.webUrl, 'related-issues'),
icon: 'issue-block',
},
{
@@ -197,7 +203,7 @@ export default {
value: this.issuable.user_notes_count,
title: __('Comments'),
dataTestId: 'notes-count',
- href: `${this.issuable.web_url}#notes`,
+ href: setUrlFragment(this.webUrl, 'notes'),
class: { 'no-comments': !this.issuable.user_notes_count, 'issuable-comments': true },
icon: 'comments',
},
@@ -252,7 +258,7 @@ export default {
:class="{ today: issueCreatedToday, closed: isClosed }"
:data-id="issuable.id"
:data-labels="labelIdsString"
- :data-url="issuable.web_url"
+ :data-url="webUrl"
data-qa-selector="issue_container"
:data-qa-issue-title="issuable.title"
>
@@ -284,13 +290,14 @@ export default {
:aria-label="$options.confidentialTooltipText"
/>
<gl-link
- :href="issuable.web_url"
+ :href="webUrl"
:target="linkTarget"
data-testid="issuable-title"
data-qa-selector="issue_link"
- >{{ issuable.title
- }}<gl-icon
- v-if="isJiraIssue"
+ >
+ {{ issuable.title }}
+ <gl-icon
+ v-if="isIssuableUrlExternal"
name="external-link"
class="gl-vertical-align-text-bottom gl-ml-2"
/>
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
new file mode 100644
index 00000000000..8d00d337bac
--- /dev/null
+++ b/app/assets/javascripts/issues_list/components/issue_card_time_info.vue
@@ -0,0 +1,124 @@
+<script>
+import { GlLink, GlIcon, GlTooltipDirective } from '@gitlab/ui';
+import {
+ dateInWords,
+ getTimeRemainingInWords,
+ isInFuture,
+ isInPast,
+ isToday,
+} from '~/lib/utils/datetime_utility';
+import { convertToCamelCase } from '~/lib/utils/text_utility';
+import { __ } from '~/locale';
+
+export default {
+ components: {
+ GlLink,
+ GlIcon,
+ IssueHealthStatus: () =>
+ import('ee_component/related_items_tree/components/issue_health_status.vue'),
+ WeightCount: () => import('ee_component/issues/components/weight_count.vue'),
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ inject: {
+ hasIssuableHealthStatusFeature: {
+ default: false,
+ },
+ },
+ 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');
+ },
+ dueDate() {
+ return this.issue.dueDate && dateInWords(new Date(this.issue.dueDate), true);
+ },
+ isDueDateInPast() {
+ return isInPast(new Date(this.issue.dueDate));
+ },
+ timeEstimate() {
+ return this.issue.timeStats?.humanTimeEstimate;
+ },
+ showHealthStatus() {
+ return this.hasIssuableHealthStatusFeature && this.issue.healthStatus;
+ },
+ healthStatus() {
+ return convertToCamelCase(this.issue.healthStatus);
+ },
+ },
+ 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-display-none gl-sm-display-inline-block! gl-mr-3"
+ data-testid="issuable-milestone"
+ >
+ <gl-link v-gl-tooltip :href="issue.milestone.webUrl" :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-display-none gl-sm-display-inline-block! gl-mr-3"
+ :class="{ 'gl-text-red-500': isDueDateInPast }"
+ :title="__('Due date')"
+ data-testid="issuable-due-date"
+ >
+ <gl-icon name="calendar" />
+ {{ dueDate }}
+ </span>
+ <span
+ v-if="timeEstimate"
+ v-gl-tooltip
+ class="gl-display-none gl-sm-display-inline-block! gl-mr-3"
+ :title="__('Estimate')"
+ data-testid="time-estimate"
+ >
+ <gl-icon name="timer" />
+ {{ timeEstimate }}
+ </span>
+ <weight-count
+ class="gl-display-none gl-sm-display-inline-block gl-mr-3"
+ :weight="issue.weight"
+ />
+ <issue-health-status
+ v-if="showHealthStatus"
+ class="gl-display-none gl-sm-display-inline-block"
+ :health-status="healthStatus"
+ />
+ </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
new file mode 100644
index 00000000000..c57fa5a82fa
--- /dev/null
+++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue
@@ -0,0 +1,143 @@
+<script>
+import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
+import { toNumber } from 'lodash';
+import createFlash from '~/flash';
+import IssuableList from '~/issuable_list/components/issuable_list_root.vue';
+import { IssuableStatus } from '~/issue_show/constants';
+import { PAGE_SIZE } from '~/issues_list/constants';
+import axios from '~/lib/utils/axios_utils';
+import { convertObjectPropsToCamelCase, getParameterByName } from '~/lib/utils/common_utils';
+import { __ } from '~/locale';
+import IssueCardTimeInfo from './issue_card_time_info.vue';
+
+export default {
+ PAGE_SIZE,
+ components: {
+ GlIcon,
+ IssuableList,
+ IssueCardTimeInfo,
+ BlockingIssuesCount: () => import('ee_component/issues/components/blocking_issues_count.vue'),
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ inject: {
+ endpoint: {
+ default: '',
+ },
+ fullPath: {
+ default: '',
+ },
+ },
+ data() {
+ return {
+ currentPage: toNumber(getParameterByName('page')) || 1,
+ isLoading: false,
+ issues: [],
+ totalIssues: 0,
+ };
+ },
+ computed: {
+ urlParams() {
+ return {
+ page: this.currentPage,
+ state: IssuableStatus.Open,
+ };
+ },
+ },
+ mounted() {
+ this.fetchIssues();
+ },
+ methods: {
+ fetchIssues(pageToFetch) {
+ this.isLoading = true;
+
+ return axios
+ .get(this.endpoint, {
+ params: {
+ page: pageToFetch || this.currentPage,
+ per_page: this.$options.PAGE_SIZE,
+ state: IssuableStatus.Open,
+ with_labels_details: true,
+ },
+ })
+ .then(({ data, headers }) => {
+ this.currentPage = Number(headers['x-page']);
+ this.totalIssues = Number(headers['x-total']);
+ this.issues = data.map((issue) => convertObjectPropsToCamelCase(issue, { deep: true }));
+ })
+ .catch(() => {
+ createFlash({ message: __('An error occurred while loading issues') });
+ })
+ .finally(() => {
+ this.isLoading = false;
+ });
+ },
+ handlePageChange(page) {
+ this.fetchIssues(page);
+ },
+ },
+};
+</script>
+
+<template>
+ <issuable-list
+ :namespace="fullPath"
+ recent-searches-storage-key="issues"
+ :search-input-placeholder="__('Search or filter results…')"
+ :search-tokens="[]"
+ :sort-options="[]"
+ :issuables="issues"
+ :tabs="[]"
+ current-tab=""
+ :issuables-loading="isLoading"
+ :show-pagination-controls="true"
+ :total-items="totalIssues"
+ :current-page="currentPage"
+ :previous-page="currentPage - 1"
+ :next-page="currentPage + 1"
+ :url-params="urlParams"
+ @page-change="handlePageChange"
+ >
+ <template #timeframe="{ issuable = {} }">
+ <issue-card-time-info :issue="issuable" />
+ </template>
+ <template #statistics="{ issuable = {} }">
+ <li
+ v-if="issuable.mergeRequestsCount"
+ v-gl-tooltip
+ class="gl-display-none gl-sm-display-block"
+ :title="__('Related merge requests')"
+ data-testid="issuable-mr"
+ >
+ <gl-icon name="merge-request" />
+ {{ issuable.mergeRequestsCount }}
+ </li>
+ <li
+ v-if="issuable.upvotes"
+ v-gl-tooltip
+ class="gl-display-none gl-sm-display-block"
+ :title="__('Upvotes')"
+ data-testid="issuable-upvotes"
+ >
+ <gl-icon name="thumb-up" />
+ {{ issuable.upvotes }}
+ </li>
+ <li
+ v-if="issuable.downvotes"
+ v-gl-tooltip
+ class="gl-display-none gl-sm-display-block"
+ :title="__('Downvotes')"
+ data-testid="issuable-downvotes"
+ >
+ <gl-icon name="thumb-down" />
+ {{ issuable.downvotes }}
+ </li>
+ <blocking-issues-count
+ class="gl-display-none gl-sm-display-block"
+ :blocking-issues-count="issuable.blockingIssuesCount"
+ :is-list-item="true"
+ />
+ </template>
+ </issuable-list>
+</template>
diff --git a/app/assets/javascripts/issues_list/components/jira_issues_list_root.vue b/app/assets/javascripts/issues_list/components/jira_issues_import_status_app.vue
index 7396cfe27b3..ba0ca57523a 100644
--- a/app/assets/javascripts/issues_list/components/jira_issues_list_root.vue
+++ b/app/assets/javascripts/issues_list/components/jira_issues_import_status_app.vue
@@ -11,7 +11,7 @@ import { n__ } from '~/locale';
import getIssuesListDetailsQuery from '../queries/get_issues_list_details.query.graphql';
export default {
- name: 'JiraIssuesList',
+ name: 'JiraIssuesImportStatus',
components: {
GlAlert,
GlLabel,
@@ -89,13 +89,13 @@ export default {
</script>
<template>
- <div class="issuable-list-root">
+ <div 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-if="jiraImport.shouldShowFinishedAlert"
+ v-else-if="jiraImport.shouldShowFinishedAlert"
variant="success"
@dismiss="hideFinishedAlert"
>
diff --git a/app/assets/javascripts/issues_list/index.js b/app/assets/javascripts/issues_list/index.js
index 5c3910955bc..a283cbdc86b 100644
--- a/app/assets/javascripts/issues_list/index.js
+++ b/app/assets/javascripts/issues_list/index.js
@@ -1,12 +1,13 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
+import IssuesListApp from '~/issues_list/components/issues_list_app.vue';
import createDefaultClient from '~/lib/graphql';
-import { parseBoolean, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
import IssuablesListApp from './components/issuables_list_app.vue';
-import JiraIssuesListRoot from './components/jira_issues_list_root.vue';
+import JiraIssuesImportStatusRoot from './components/jira_issues_import_status_app.vue';
function mountJiraIssuesListApp() {
- const el = document.querySelector('.js-projects-issues-root');
+ const el = document.querySelector('.js-jira-issues-import-status');
if (!el) {
return false;
@@ -23,7 +24,7 @@ function mountJiraIssuesListApp() {
el,
apolloProvider,
render(createComponent) {
- return createComponent(JiraIssuesListRoot, {
+ return createComponent(JiraIssuesImportStatusRoot, {
props: {
canEdit: parseBoolean(el.dataset.canEdit),
isJiraConfigured: parseBoolean(el.dataset.isJiraConfigured),
@@ -36,7 +37,7 @@ function mountJiraIssuesListApp() {
}
function mountIssuablesListApp() {
- if (!gon.features?.vueIssuablesList && !gon.features?.jiraIssuesIntegration) {
+ if (!gon.features?.vueIssuablesList) {
return;
}
@@ -64,6 +65,37 @@ function mountIssuablesListApp() {
});
}
+export function initIssuesListApp() {
+ const el = document.querySelector('.js-issues-list');
+
+ if (!el) {
+ return false;
+ }
+
+ const {
+ endpoint,
+ fullPath,
+ hasBlockedIssuesFeature,
+ hasIssuableHealthStatusFeature,
+ hasIssueWeightsFeature,
+ } = el.dataset;
+
+ return new Vue({
+ el,
+ // Currently does not use Vue Apollo, but need to provide {} for now until the
+ // issue is fixed upstream in https://github.com/vuejs/vue-apollo/pull/1153
+ apolloProvider: {},
+ provide: {
+ endpoint,
+ fullPath,
+ hasBlockedIssuesFeature: parseBoolean(hasBlockedIssuesFeature),
+ hasIssuableHealthStatusFeature: parseBoolean(hasIssuableHealthStatusFeature),
+ hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature),
+ },
+ render: (createComponent) => createComponent(IssuesListApp),
+ });
+}
+
export default function initIssuablesList() {
mountJiraIssuesListApp();
mountIssuablesListApp();
diff --git a/app/assets/javascripts/issues_list/service_desk_helper.js b/app/assets/javascripts/issues_list/service_desk_helper.js
index 0a34b754377..f96567ef53b 100644
--- a/app/assets/javascripts/issues_list/service_desk_helper.js
+++ b/app/assets/javascripts/issues_list/service_desk_helper.js
@@ -1,4 +1,4 @@
-import { __ } from '~/locale';
+import { s__ } from '~/locale';
/**
* Generates empty state messages for Service Desk issues list.
@@ -15,23 +15,23 @@ export function generateMessages(emptyStateMeta) {
incomingEmailHelpPage,
} = emptyStateMeta;
- const serviceDeskSupportedTitle = __(
- 'Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab',
+ const serviceDeskSupportedTitle = s__(
+ 'ServiceDesk|Use Service Desk to connect with your users and offer customer support through email right inside GitLab',
);
- const serviceDeskSupportedMessage = __(
- 'Those emails automatically become issues (with the comments becoming the email conversation) listed here.',
+ const serviceDeskSupportedMessage = s__(
+ 'ServiceDesk|Issues created from Service Desk emails appear here. Each comment becomes part of the email conversation.',
);
const commonDescription = `
<span>${serviceDeskSupportedMessage}</span>
- <a href="${serviceDeskHelpPage}">${__('Read more')}</a>`;
+ <a href="${serviceDeskHelpPage}">${s__('Learn more.')}</a>`;
return {
serviceDeskEnabledAndCanEditProjectSettings: {
title: serviceDeskSupportedTitle,
svgPath,
- description: `<p>${__('Have your users email')}
+ description: `<p>${s__('ServiceDesk|Your users can send emails to this address:')}
<code>${serviceDeskAddress}</code>
</p>
${commonDescription}`,
@@ -46,7 +46,7 @@ export function generateMessages(emptyStateMeta) {
svgPath,
description: commonDescription,
primaryLink: editProjectPage,
- primaryText: __('Turn on Service Desk'),
+ primaryText: s__('ServiceDesk|Enable Service Desk'),
},
serviceDeskDisabledAndCannotEditProjectSettings: {
title: serviceDeskSupportedTitle,
@@ -54,19 +54,19 @@ export function generateMessages(emptyStateMeta) {
description: commonDescription,
},
serviceDeskIsNotSupported: {
- title: __('Service Desk is not supported'),
+ title: s__('ServiceDesk|Service Desk is not supported'),
svgPath,
- description: __(
- 'In order to enable Service Desk for your instance, you must first set up incoming email.',
+ description: s__(
+ 'ServiceDesk|To enable Service Desk on this instance, an instance administrator must first set up incoming email.',
),
primaryLink: incomingEmailHelpPage,
- primaryText: __('More information'),
+ primaryText: s__('Learn more.'),
},
serviceDeskIsNotEnabled: {
- title: __('Service Desk is not enabled'),
+ title: s__('ServiceDesk|Service Desk is not enabled'),
svgPath,
- description: __(
- 'For help setting up the Service Desk for your instance, please contact an administrator.',
+ description: s__(
+ 'ServiceDesk|For help setting up the Service Desk for your instance, please contact an administrator.',
),
},
};