Commit 638405ee authored by Eulyeon Ko's avatar Eulyeon Ko

Add blocking_issues_count field

Switch out the bootstrap utility classes with gl-utility classes
(to the extent possible).

Update rspec for issues api endpoint

Update issuable spec

Add localized string

Remove trailing whitespace

Re-adjusted utility classes

Add important directive to gl-ml-2

Remove unused class after resolving conflict

Add link to blocking issues count

Consolidate issuable meta icons

Put all of issuable meta icons into one array
Then iterate through them.
parent 7d14e142
......@@ -85,9 +85,6 @@ export default {
dueDateWords() {
return this.dueDate ? dateInWords(this.dueDate, true) : undefined;
},
hasNoComments() {
return !this.userNotesCount;
},
isOverdue() {
return this.dueDate ? this.dueDate < new Date() : false;
},
......@@ -148,32 +145,51 @@ export default {
time_ago: escape(getTimeago().format(this.issuable.updated_at)),
});
},
userNotesCount() {
return this.issuable.user_notes_count;
},
issuableMeta() {
return [
{
key: 'merge-requests',
visible: this.issuable.merge_requests_count > 0,
value: this.issuable.merge_requests_count,
title: __('Related merge requests'),
class: 'js-merge-requests',
dataTestId: 'merge-requests',
icon: 'merge-request',
},
{
key: 'upvotes',
visible: this.issuable.upvotes > 0,
value: this.issuable.upvotes,
title: __('Upvotes'),
class: 'js-upvotes',
dataTestId: 'upvotes',
icon: 'thumb-up',
},
{
key: 'downvotes',
visible: this.issuable.downvotes > 0,
value: this.issuable.downvotes,
title: __('Downvotes'),
class: 'js-downvotes',
dataTestId: '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: `${this.issuable.web_url}#related-issues`,
icon: 'issue-block',
},
{
key: 'comments-count',
visible: !this.isJiraIssue,
value: this.issuable.user_notes_count,
title: __('Comments'),
dataTestId: 'notes-count',
href: `${this.issuable.web_url}#notes`,
class: !this.issuable.user_notes_count ? 'no-comments' : '',
icon: 'comments',
},
];
},
},
......@@ -202,6 +218,9 @@ export default {
selected: ev.target.checked,
});
},
issuableMetaComponent(href) {
return href ? 'gl-link' : 'span';
},
},
confidentialTooltipText: __('Confidential'),
......@@ -216,9 +235,9 @@ export default {
:data-labels="labelIdsString"
:data-url="issuable.web_url"
>
<div class="d-flex">
<div class="gl-display-flex">
<!-- Bulk edit checkbox -->
<div v-if="isBulkEditing" class="mr-2">
<div v-if="isBulkEditing" class="gl-mr-3">
<input
:checked="selected"
class="selected-issuable"
......@@ -230,7 +249,7 @@ export default {
<!-- Issuable info container -->
<!-- Issuable main info -->
<div class="flex-grow-1">
<div class="gl-flex-grow-1">
<div class="title">
<span class="issue-title-text">
<gl-icon
......@@ -251,7 +270,10 @@ export default {
/>
</gl-link>
</span>
<span v-if="issuable.has_tasks" class="ml-1 task-status d-none d-sm-inline-block">
<span
v-if="issuable.has_tasks"
class="gl-ml-2 task-status gl-display-none d-sm-inline-block"
>
{{ issuable.task_status }}
</span>
</div>
......@@ -267,7 +289,7 @@ export default {
{{ referencePath }}
</span>
<span data-testid="openedByMessage" class="d-none d-sm-inline-block mr-1">
<span data-testid="openedByMessage" class="gl-display-none d-sm-inline-block gl-mr-2">
&middot;
<gl-sprintf
:message="isJiraIssue ? $options.i18n.openedAgoJira : $options.i18n.openedAgo"
......@@ -291,7 +313,7 @@ export default {
<gl-link
v-if="issuable.milestone"
v-gl-tooltip
class="d-none d-sm-inline-block mr-1 js-milestone"
class="gl-display-none d-sm-inline-block gl-mr-2 js-milestone"
:href="milestoneLink"
:title="milestoneTooltipText"
>
......@@ -302,7 +324,7 @@ export default {
<span
v-if="dueDate"
v-gl-tooltip
class="d-none d-sm-inline-block mr-1 js-due-date"
class="gl-display-none d-sm-inline-block gl-mr-2 js-due-date"
:class="{ cred: isOverdue }"
:title="__('Due date')"
>
......@@ -321,7 +343,7 @@ export default {
:title="label.name"
:scoped="isScoped(label)"
size="sm"
class="mr-1"
class="gl-mr-2"
>{{ label.name }}</gl-label
>
......@@ -329,7 +351,7 @@ export default {
v-if="hasWeight"
v-gl-tooltip
:title="__('Weight')"
class="d-none d-sm-inline-block js-weight"
class="gl-display-none d-sm-inline-block"
data-testid="weight"
>
<gl-icon name="weight" class="align-text-bottom" />
......@@ -339,43 +361,37 @@ export default {
</div>
<!-- Issuable meta -->
<div class="flex-shrink-0 d-flex flex-column align-items-end justify-content-center">
<div class="controls d-flex">
<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="align-items-center d-flex ml-2"
class="gl-align-items-center gl-display-flex gl-ml-3"
:icon-size="16"
img-css-classes="mr-1"
img-css-classes="gl-mr-2!"
:max-visible="4"
/>
<template v-for="meta in issuableMeta">
<span
v-if="meta.value"
v-if="meta.visible"
:key="meta.key"
v-gl-tooltip
:class="['d-none d-sm-inline-block ml-2 vertical-align-middle', meta.class]"
class="gl-display-none gl-display-sm-flex gl-align-items-center gl-ml-3"
:class="meta.class"
:data-testid="meta.dataTestId"
:title="meta.title"
>
<gl-icon v-if="meta.icon" :name="meta.icon" />
{{ meta.value }}
<component :is="issuableMetaComponent(meta.href)" :href="meta.href">
<gl-icon v-if="meta.icon" :name="meta.icon" />
{{ meta.value }}
</component>
</span>
</template>
<gl-link
v-if="!isJiraIssue"
v-gl-tooltip
class="ml-2 js-notes"
:href="`${issuable.web_url}#notes`"
:title="__('Comments')"
:class="{ 'no-comments': hasNoComments }"
>
<gl-icon name="comments" class="gl-vertical-align-text-bottom" />
{{ userNotesCount }}
</gl-link>
</div>
<div v-gl-tooltip class="issuable-updated-at" :title="updatedDateString">
{{ updatedDateAgo }}
......
......@@ -8,6 +8,10 @@ module EE
prepended do
expose :weight, if: ->(issue, _) { issue.supports_weight? }
expose(:blocking_issues_count) do |issue, options|
issuable_metadata.blocking_issues_count
end
end
end
end
......
......@@ -4,6 +4,7 @@
{ "$ref": "../../../../../../../spec/fixtures/api/schemas/public_api/v4/issues.json" },
{
"properties": {
"blocking_issues_count": { "type": ["integer", "null"] },
"weight": { "type": ["integer", "null"] },
"epic_iid": { "type": ["integer", "null"] },
"epic": {
......
......@@ -101,6 +101,25 @@ RSpec.describe API::Issues, :mailer do
expect(response).to match_response_schema('public_api/v4/issues', dir: 'ee')
end
context "blocking issues count" do
it 'returns a blocking issues count of 0 if there are no blocking issues' do
get api('/issues', user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.first).to include('blocking_issues_count' => 0)
end
it 'returns a blocking issues count of 1 if there exists a blocking issue' do
blocked_issue = build(:issue, author: user2, project: project, created_at: 1.day.ago)
create(:issue_link, source: issue, target: blocked_issue, link_type: IssueLink::TYPE_BLOCKS)
get api('/issues', user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.first).to include('blocking_issues_count' => 1)
end
end
context "filtering by weight" do
let!(:issue1) { create(:issue, author: user2, project: project, weight: 1, created_at: 3.days.ago) }
let!(:issue2) { create(:issue, author: user2, project: project, weight: 5, created_at: 2.days.ago) }
......
......@@ -3765,6 +3765,9 @@ msgstr ""
msgid "Blocked issue"
msgstr ""
msgid "Blocking issues"
msgstr ""
msgid "Blocks"
msgstr ""
......
......@@ -85,12 +85,13 @@ describe('Issuable component', () => {
const findMilestoneTooltip = () => findMilestone().attributes('title');
const findDueDate = () => wrapper.find('.js-due-date');
const findLabels = () => wrapper.findAll(GlLabel);
const findWeight = () => wrapper.find('.js-weight');
const findWeight = () => wrapper.find('[data-testid="weight"]');
const findAssignees = () => wrapper.find(IssueAssignees);
const findMergeRequestsCount = () => wrapper.find('.js-merge-requests');
const findUpvotes = () => wrapper.find('.js-upvotes');
const findDownvotes = () => wrapper.find('.js-downvotes');
const findNotes = () => wrapper.find('.js-notes');
const findBlockingIssuesCount = () => wrapper.find('[data-testid="blocking-issues"]');
const findMergeRequestsCount = () => wrapper.find('[data-testid="merge-requests"]');
const findUpvotes = () => wrapper.find('[data-testid="upvotes"]');
const findDownvotes = () => wrapper.find('[data-testid="downvotes"]');
const findNotes = () => wrapper.find('[data-testid="notes-count"]');
const findBulkCheckbox = () => wrapper.find('input.selected-issuable');
const findScopedLabels = () => findLabels().filter(w => isScopedLabel({ title: w.text() }));
const findUnscopedLabels = () => findLabels().filter(w => !isScopedLabel({ title: w.text() }));
......@@ -181,6 +182,7 @@ describe('Issuable component', () => {
${'due date'} | ${checkExists(findDueDate)}
${'labels'} | ${checkExists(findLabels)}
${'weight'} | ${checkExists(findWeight)}
${'blocking issues count'} | ${checkExists(findBlockingIssuesCount)}
${'merge request count'} | ${checkExists(findMergeRequestsCount)}
${'upvotes'} | ${checkExists(findUpvotes)}
${'downvotes'} | ${checkExists(findDownvotes)}
......@@ -430,11 +432,12 @@ describe('Issuable component', () => {
});
describe.each`
desc | key | finder
${'with merge requests count'} | ${'merge_requests_count'} | ${findMergeRequestsCount}
${'with upvote count'} | ${'upvotes'} | ${findUpvotes}
${'with downvote count'} | ${'downvotes'} | ${findDownvotes}
${'with notes count'} | ${'user_notes_count'} | ${findNotes}
desc | key | finder
${'with blocking issues count'} | ${'blocking_issues_count'} | ${findBlockingIssuesCount}
${'with merge requests count'} | ${'merge_requests_count'} | ${findMergeRequestsCount}
${'with upvote count'} | ${'upvotes'} | ${findUpvotes}
${'with downvote count'} | ${'downvotes'} | ${findDownvotes}
${'with notes count'} | ${'user_notes_count'} | ${findNotes}
`('$desc', ({ key, finder }) => {
beforeEach(() => {
issuable[key] = TEST_META_COUNT;
......@@ -442,7 +445,7 @@ describe('Issuable component', () => {
factory({ issuable });
});
it('renders merge requests count', () => {
it('renders correct count', () => {
expect(finder().exists()).toBe(true);
expect(finder().text()).toBe(TEST_META_COUNT.toString());
expect(finder().classes('no-comments')).toBe(false);
......
......@@ -18,6 +18,7 @@ export const simpleIssue = {
},
assignee: null,
user_notes_count: 0,
blocking_issues_count: 0,
merge_requests_count: 0,
upvotes: 0,
downvotes: 0,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment