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>2020-12-22 15:10:09 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-22 15:10:09 +0300
commit5393d9eb26fb71826e048798806c05906c169e61 (patch)
treeda351541f962a10c7faf7b546946424ddc549c43 /app/assets/javascripts/boards
parent081e00122e3f8d55f04a0a34485256d82a9b8933 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/boards')
-rw-r--r--app/assets/javascripts/boards/components/board_card_layout.vue3
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue50
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner_deprecated.vue245
-rw-r--r--app/assets/javascripts/boards/components/issue_time_estimate.vue28
-rw-r--r--app/assets/javascripts/boards/components/issue_time_estimate_deprecated.vue48
-rw-r--r--app/assets/javascripts/boards/index.js18
-rw-r--r--app/assets/javascripts/boards/stores/actions.js17
-rw-r--r--app/assets/javascripts/boards/stores/mutations.js5
8 files changed, 360 insertions, 54 deletions
diff --git a/app/assets/javascripts/boards/components/board_card_layout.vue b/app/assets/javascripts/boards/components/board_card_layout.vue
index f796acd2303..551390e9acf 100644
--- a/app/assets/javascripts/boards/components/board_card_layout.vue
+++ b/app/assets/javascripts/boards/components/board_card_layout.vue
@@ -1,11 +1,12 @@
<script>
import IssueCardInner from './issue_card_inner.vue';
+import IssueCardInnerDeprecated from './issue_card_inner_deprecated.vue';
import boardsStore from '../stores/boards_store';
export default {
name: 'BoardsIssueCard',
components: {
- IssueCardInner,
+ IssueCardInner: gon.features?.graphqlBoardLists ? IssueCardInner : IssueCardInnerDeprecated,
},
props: {
list: {
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index ddd20ff281c..a635eb012cc 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -1,6 +1,6 @@
<script>
import { sortBy } from 'lodash';
-import { mapState } from 'vuex';
+import { mapActions, mapState } from 'vuex';
import { GlLabel, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import issueCardInner from 'ee_else_ce/boards/mixins/issue_card_inner';
import { sprintf, __, n__ } from '~/locale';
@@ -8,9 +8,10 @@ import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import IssueDueDate from './issue_due_date.vue';
import IssueTimeEstimate from './issue_time_estimate.vue';
-import boardsStore from '../stores/boards_store';
+import eventHub from '../eventhub';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { ListType } from '../constants';
+import { updateHistory } from '~/lib/utils/url_utility';
export default {
components: {
@@ -42,7 +43,7 @@ export default {
default: false,
},
},
- inject: ['groupId', 'rootPath'],
+ inject: ['groupId', 'rootPath', 'scopedLabelsAvailable'],
data() {
return {
limitBeforeCounter: 2,
@@ -52,6 +53,16 @@ export default {
},
computed: {
...mapState(['isShowingLabels']),
+ cappedAssignees() {
+ // e.g. maxRender is 4,
+ // Render up to all 4 assignees if there are only 4 assigness
+ // Otherwise render up to the limitBeforeCounter
+ if (this.issue.assignees.length <= this.maxRender) {
+ return this.issue.assignees.slice(0, this.maxRender);
+ }
+
+ return this.issue.assignees.slice(0, this.limitBeforeCounter);
+ },
numberOverLimit() {
return this.issue.assignees.length - this.limitBeforeCounter;
},
@@ -98,19 +109,10 @@ export default {
},
},
methods: {
+ ...mapActions(['performSearch']),
isIndexLessThanlimit(index) {
return index < this.limitBeforeCounter;
},
- shouldRenderAssignee(index) {
- // Eg. maxRender is 4,
- // Render up to all 4 assignees if there are only 4 assigness
- // Otherwise render up to the limitBeforeCounter
- if (this.issue.assignees.length <= this.maxRender) {
- return index < this.maxRender;
- }
-
- return index < this.limitBeforeCounter;
- },
assigneeUrl(assignee) {
if (!assignee) return '';
return `${this.rootPath}${assignee.username}`;
@@ -118,6 +120,9 @@ export default {
avatarUrlTitle(assignee) {
return sprintf(__(`Avatar for %{assigneeName}`), { assigneeName: assignee.name });
},
+ avatarUrl(assignee) {
+ return assignee.avatarUrl || assignee.avatar || gon.default_avatar_url;
+ },
showLabel(label) {
if (!label.id) return false;
return true;
@@ -133,13 +138,19 @@ export default {
},
filterByLabel(label) {
if (!this.updateFilters) return;
- const labelTitle = encodeURIComponent(label.title);
- const filter = `label_name[]=${labelTitle}`;
+ const filterPath = window.location.search ? `${window.location.search}&` : '?';
+ const filter = `label_name[]=${encodeURIComponent(label.title)}`;
- boardsStore.toggleFilter(filter);
+ if (!filterPath.includes(filter)) {
+ updateHistory({
+ url: `${filterPath}${filter}`,
+ });
+ this.performSearch();
+ eventHub.$emit('updateTokens');
+ }
},
showScopedLabel(label) {
- return boardsStore.scopedLabels.enabled && isScopedLabel(label);
+ return this.scopedLabelsAvailable && isScopedLabel(label);
},
},
};
@@ -222,12 +233,11 @@ export default {
</div>
<div class="board-card-assignee gl-display-flex">
<user-avatar-link
- v-for="(assignee, index) in issue.assignees"
- v-if="shouldRenderAssignee(index)"
+ v-for="assignee in cappedAssignees"
:key="assignee.id"
:link-href="assigneeUrl(assignee)"
:img-alt="avatarUrlTitle(assignee)"
- :img-src="assignee.avatarUrl || assignee.avatar || assignee.avatar_url"
+ :img-src="avatarUrl(assignee)"
:img-size="24"
class="js-no-trigger"
tooltip-placement="bottom"
diff --git a/app/assets/javascripts/boards/components/issue_card_inner_deprecated.vue b/app/assets/javascripts/boards/components/issue_card_inner_deprecated.vue
new file mode 100644
index 00000000000..ac0b16914ef
--- /dev/null
+++ b/app/assets/javascripts/boards/components/issue_card_inner_deprecated.vue
@@ -0,0 +1,245 @@
+<script>
+import { sortBy } from 'lodash';
+import { mapState } from 'vuex';
+import { GlLabel, GlTooltipDirective, GlIcon } from '@gitlab/ui';
+import issueCardInner from 'ee_else_ce/boards/mixins/issue_card_inner';
+import { sprintf, __, n__ } from '~/locale';
+import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
+import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
+import IssueDueDate from './issue_due_date.vue';
+import IssueTimeEstimate from './issue_time_estimate_deprecated.vue';
+import boardsStore from '../stores/boards_store';
+import { isScopedLabel } from '~/lib/utils/common_utils';
+
+export default {
+ components: {
+ GlLabel,
+ GlIcon,
+ UserAvatarLink,
+ TooltipOnTruncate,
+ IssueDueDate,
+ IssueTimeEstimate,
+ IssueCardWeight: () => import('ee_component/boards/components/issue_card_weight.vue'),
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ mixins: [issueCardInner],
+ props: {
+ issue: {
+ type: Object,
+ required: true,
+ },
+ list: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ updateFilters: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ inject: ['groupId', 'rootPath'],
+ data() {
+ return {
+ limitBeforeCounter: 2,
+ maxRender: 3,
+ maxCounter: 99,
+ };
+ },
+ computed: {
+ ...mapState(['isShowingLabels']),
+ numberOverLimit() {
+ return this.issue.assignees.length - this.limitBeforeCounter;
+ },
+ assigneeCounterTooltip() {
+ const { numberOverLimit, maxCounter } = this;
+ const count = numberOverLimit > maxCounter ? maxCounter : numberOverLimit;
+ return sprintf(__('%{count} more assignees'), { count });
+ },
+ assigneeCounterLabel() {
+ if (this.numberOverLimit > this.maxCounter) {
+ return `${this.maxCounter}+`;
+ }
+
+ return `+${this.numberOverLimit}`;
+ },
+ shouldRenderCounter() {
+ if (this.issue.assignees.length <= this.maxRender) {
+ return false;
+ }
+
+ return this.issue.assignees.length > this.numberOverLimit;
+ },
+ issueId() {
+ if (this.issue.iid) {
+ return `#${this.issue.iid}`;
+ }
+ return false;
+ },
+ showLabelFooter() {
+ return this.isShowingLabels && this.issue.labels.find(this.showLabel);
+ },
+ issueReferencePath() {
+ const { referencePath, groupId } = this.issue;
+ return !groupId ? referencePath.split('#')[0] : null;
+ },
+ orderedLabels() {
+ return sortBy(this.issue.labels.filter(this.isNonListLabel), 'title');
+ },
+ blockedLabel() {
+ if (this.issue.blockedByCount) {
+ return n__(`Blocked by %d issue`, `Blocked by %d issues`, this.issue.blockedByCount);
+ }
+ return __('Blocked issue');
+ },
+ },
+ methods: {
+ isIndexLessThanlimit(index) {
+ return index < this.limitBeforeCounter;
+ },
+ shouldRenderAssignee(index) {
+ // Eg. maxRender is 4,
+ // Render up to all 4 assignees if there are only 4 assigness
+ // Otherwise render up to the limitBeforeCounter
+ if (this.issue.assignees.length <= this.maxRender) {
+ return index < this.maxRender;
+ }
+
+ return index < this.limitBeforeCounter;
+ },
+ assigneeUrl(assignee) {
+ if (!assignee) return '';
+ return `${this.rootPath}${assignee.username}`;
+ },
+ avatarUrlTitle(assignee) {
+ return sprintf(__(`Avatar for %{assigneeName}`), { assigneeName: assignee.name });
+ },
+ showLabel(label) {
+ if (!label.id) return false;
+ return true;
+ },
+ isNonListLabel(label) {
+ return label.id && !(this.list.type === 'label' && this.list.title === label.title);
+ },
+ filterByLabel(label) {
+ if (!this.updateFilters) return;
+ const labelTitle = encodeURIComponent(label.title);
+ const filter = `label_name[]=${labelTitle}`;
+
+ boardsStore.toggleFilter(filter);
+ },
+ showScopedLabel(label) {
+ return boardsStore.scopedLabels.enabled && isScopedLabel(label);
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <div class="gl-display-flex" dir="auto">
+ <h4 class="board-card-title gl-mb-0 gl-mt-0">
+ <gl-icon
+ v-if="issue.blocked"
+ v-gl-tooltip
+ name="issue-block"
+ :title="blockedLabel"
+ class="issue-blocked-icon gl-mr-2"
+ :aria-label="blockedLabel"
+ data-testid="issue-blocked-icon"
+ />
+ <gl-icon
+ v-if="issue.confidential"
+ v-gl-tooltip
+ name="eye-slash"
+ :title="__('Confidential')"
+ class="confidential-icon gl-mr-2"
+ :aria-label="__('Confidential')"
+ />
+ <a
+ :href="issue.path || issue.webUrl || ''"
+ :title="issue.title"
+ class="js-no-trigger"
+ @mousemove.stop
+ >{{ issue.title }}</a
+ >
+ </h4>
+ </div>
+ <div v-if="showLabelFooter" class="board-card-labels gl-mt-2 gl-display-flex gl-flex-wrap">
+ <template v-for="label in orderedLabels">
+ <gl-label
+ :key="label.id"
+ :background-color="label.color"
+ :title="label.title"
+ :description="label.description"
+ size="sm"
+ :scoped="showScopedLabel(label)"
+ @click="filterByLabel(label)"
+ />
+ </template>
+ </div>
+ <div
+ class="board-card-footer gl-display-flex gl-justify-content-space-between gl-align-items-flex-end"
+ >
+ <div
+ class="gl-display-flex align-items-start flex-wrap-reverse board-card-number-container gl-overflow-hidden js-board-card-number-container"
+ >
+ <span
+ v-if="issue.referencePath"
+ class="board-card-number gl-overflow-hidden gl-display-flex gl-mr-3 gl-mt-3"
+ >
+ <tooltip-on-truncate
+ v-if="issueReferencePath"
+ :title="issueReferencePath"
+ placement="bottom"
+ class="board-issue-path gl-text-truncate gl-font-weight-bold"
+ >{{ issueReferencePath }}</tooltip-on-truncate
+ >
+ #{{ issue.iid }}
+ </span>
+ <span class="board-info-items gl-mt-3 gl-display-inline-block">
+ <issue-due-date
+ v-if="issue.dueDate"
+ :date="issue.dueDate"
+ :closed="issue.closed || Boolean(issue.closedAt)"
+ />
+ <issue-time-estimate v-if="issue.timeEstimate" :estimate="issue.timeEstimate" />
+ <issue-card-weight
+ v-if="validIssueWeight"
+ :weight="issue.weight"
+ @click="filterByWeight(issue.weight)"
+ />
+ </span>
+ </div>
+ <div class="board-card-assignee gl-display-flex">
+ <user-avatar-link
+ v-for="(assignee, index) in issue.assignees"
+ v-if="shouldRenderAssignee(index)"
+ :key="assignee.id"
+ :link-href="assigneeUrl(assignee)"
+ :img-alt="avatarUrlTitle(assignee)"
+ :img-src="assignee.avatarUrl || assignee.avatar || assignee.avatar_url"
+ :img-size="24"
+ class="js-no-trigger"
+ tooltip-placement="bottom"
+ >
+ <span class="js-assignee-tooltip">
+ <span class="gl-font-weight-bold gl-display-block">{{ __('Assignee') }}</span>
+ {{ assignee.name }}
+ <span class="text-white-50">@{{ assignee.username }}</span>
+ </span>
+ </user-avatar-link>
+ <span
+ v-if="shouldRenderCounter"
+ v-gl-tooltip
+ :title="assigneeCounterTooltip"
+ class="avatar-counter"
+ data-placement="bottom"
+ >{{ assigneeCounterLabel }}</span
+ >
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/boards/components/issue_time_estimate.vue b/app/assets/javascripts/boards/components/issue_time_estimate.vue
index fe56833016e..6c16005e7f4 100644
--- a/app/assets/javascripts/boards/components/issue_time_estimate.vue
+++ b/app/assets/javascripts/boards/components/issue_time_estimate.vue
@@ -1,9 +1,12 @@
<script>
import { GlTooltip, GlIcon } from '@gitlab/ui';
+import { __ } from '~/locale';
import { parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
-import boardsStore from '../stores/boards_store';
export default {
+ i18n: {
+ timeEstimate: __('Time estimate'),
+ },
components: {
GlIcon,
GlTooltip,
@@ -14,17 +17,18 @@ export default {
required: true,
},
},
- data() {
- return {
- limitToHours: boardsStore.timeTracking.limitToHours,
- };
- },
+ inject: ['timeTrackingLimitToHours'],
computed: {
title() {
- return stringifyTime(parseSeconds(this.estimate, { limitToHours: this.limitToHours }), true);
+ return stringifyTime(
+ parseSeconds(this.estimate, { limitToHours: this.timeTrackingLimitToHours }),
+ true,
+ );
},
timeEstimate() {
- return stringifyTime(parseSeconds(this.estimate, { limitToHours: this.limitToHours }));
+ return stringifyTime(
+ parseSeconds(this.estimate, { limitToHours: this.timeTrackingLimitToHours }),
+ );
},
},
};
@@ -33,16 +37,16 @@ export default {
<template>
<span>
<span ref="issueTimeEstimate" class="board-card-info card-number">
- <gl-icon name="hourglass" class="board-card-info-icon" /><time class="board-card-info-text">{{
- timeEstimate
- }}</time>
+ <gl-icon name="hourglass" class="board-card-info-icon" />
+ <time class="board-card-info-text">{{ timeEstimate }}</time>
</span>
<gl-tooltip
:target="() => $refs.issueTimeEstimate"
placement="bottom"
class="js-issue-time-estimate"
>
- <span class="bold d-block">{{ __('Time estimate') }}</span> {{ title }}
+ <span class="gl-font-weight-bold gl-display-block">{{ $options.i18n.timeEstimate }}</span>
+ {{ title }}
</gl-tooltip>
</span>
</template>
diff --git a/app/assets/javascripts/boards/components/issue_time_estimate_deprecated.vue b/app/assets/javascripts/boards/components/issue_time_estimate_deprecated.vue
new file mode 100644
index 00000000000..fe56833016e
--- /dev/null
+++ b/app/assets/javascripts/boards/components/issue_time_estimate_deprecated.vue
@@ -0,0 +1,48 @@
+<script>
+import { GlTooltip, GlIcon } from '@gitlab/ui';
+import { parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
+import boardsStore from '../stores/boards_store';
+
+export default {
+ components: {
+ GlIcon,
+ GlTooltip,
+ },
+ props: {
+ estimate: {
+ type: Number,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ limitToHours: boardsStore.timeTracking.limitToHours,
+ };
+ },
+ computed: {
+ title() {
+ return stringifyTime(parseSeconds(this.estimate, { limitToHours: this.limitToHours }), true);
+ },
+ timeEstimate() {
+ return stringifyTime(parseSeconds(this.estimate, { limitToHours: this.limitToHours }));
+ },
+ },
+};
+</script>
+
+<template>
+ <span>
+ <span ref="issueTimeEstimate" class="board-card-info card-number">
+ <gl-icon name="hourglass" class="board-card-info-icon" /><time class="board-card-info-text">{{
+ timeEstimate
+ }}</time>
+ </span>
+ <gl-tooltip
+ :target="() => $refs.issueTimeEstimate"
+ placement="bottom"
+ class="js-issue-time-estimate"
+ >
+ <span class="bold d-block">{{ __('Time estimate') }}</span> {{ title }}
+ </gl-tooltip>
+ </span>
+</template>
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 64a4f246735..37248f0400b 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -117,16 +117,9 @@ export default () => {
},
},
created() {
- const endpoints = {
- boardsEndpoint: this.boardsEndpoint,
- recentBoardsEndpoint: this.recentBoardsEndpoint,
- listsEndpoint: this.listsEndpoint,
- bulkUpdatePath: this.bulkUpdatePath,
+ this.setInitialBoardData({
boardId: $boardApp.dataset.boardId,
fullPath: $boardApp.dataset.fullPath,
- };
- this.setInitialBoardData({
- ...endpoints,
boardType: this.parent,
disabled: this.disabled,
boardConfig: {
@@ -141,7 +134,14 @@ export default () => {
: null,
},
});
- boardsStore.setEndpoints(endpoints);
+ boardsStore.setEndpoints({
+ boardsEndpoint: this.boardsEndpoint,
+ recentBoardsEndpoint: this.recentBoardsEndpoint,
+ listsEndpoint: this.listsEndpoint,
+ bulkUpdatePath: this.bulkUpdatePath,
+ boardId: $boardApp.dataset.boardId,
+ fullPath: $boardApp.dataset.fullPath,
+ });
boardsStore.rootPath = this.boardsEndpoint;
eventHub.$on('updateTokens', this.updateTokens);
diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js
index 59b97eba9fe..4aa2869795f 100644
--- a/app/assets/javascripts/boards/stores/actions.js
+++ b/app/assets/javascripts/boards/stores/actions.js
@@ -78,8 +78,7 @@ export default {
},
fetchLists: ({ commit, state, dispatch }) => {
- const { endpoints, boardType, filterParams } = state;
- const { fullPath, boardId } = endpoints;
+ const { boardType, filterParams, fullPath, boardId } = state;
const variables = {
fullPath,
@@ -106,7 +105,7 @@ export default {
},
createList: ({ state, commit, dispatch }, { backlog, labelId, milestoneId, assigneeId }) => {
- const { boardId } = state.endpoints;
+ const { boardId } = state;
gqlClient
.mutate({
@@ -135,8 +134,7 @@ export default {
},
fetchLabels: ({ state, commit }, searchTerm) => {
- const { endpoints, boardType } = state;
- const { fullPath } = endpoints;
+ const { fullPath, boardType } = state;
const variables = {
fullPath,
@@ -227,8 +225,7 @@ export default {
fetchIssuesForList: ({ state, commit }, { listId, fetchNext = false }) => {
commit(types.REQUEST_ISSUES_FOR_LIST, { listId, fetchNext });
- const { endpoints, boardType, filterParams } = state;
- const { fullPath, boardId } = endpoints;
+ const { fullPath, boardId, boardType, filterParams } = state;
const variables = {
fullPath,
@@ -271,7 +268,7 @@ export default {
const originalIndex = fromList.indexOf(Number(issueId));
commit(types.MOVE_ISSUE, { originalIssue, fromListId, toListId, moveBeforeId, moveAfterId });
- const { boardId } = state.endpoints;
+ const { boardId } = state;
const [fullProjectPath] = issuePath.split(/[#]/);
gqlClient
@@ -357,9 +354,9 @@ export default {
createNewIssue: ({ commit, state }, issueInput) => {
const input = issueInput;
- const { boardType, endpoints } = state;
+ const { boardType, fullPath } = state;
if (boardType === BoardType.project) {
- input.projectPath = endpoints.fullPath;
+ input.projectPath = fullPath;
}
return gqlClient
diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js
index 8c4e514710f..66a2190ebd8 100644
--- a/app/assets/javascripts/boards/stores/mutations.js
+++ b/app/assets/javascripts/boards/stores/mutations.js
@@ -32,8 +32,9 @@ export const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfter
export default {
[mutationTypes.SET_INITIAL_BOARD_DATA](state, data) {
- const { boardType, disabled, boardConfig, ...endpoints } = data;
- state.endpoints = endpoints;
+ const { boardType, disabled, boardId, fullPath, boardConfig } = data;
+ state.boardId = boardId;
+ state.fullPath = fullPath;
state.boardType = boardType;
state.disabled = disabled;
state.boardConfig = boardConfig;