Commit f1bdd682 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '322679-epic-board-display-epic-reference-in-card' into 'master'

Epic board - Display epic reference on card [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!55028
parents 36b60c36 d773d574
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import IssueCardInner from './issue_card_inner.vue';
import BoardCardInner from './board_card_inner.vue';
export default {
name: 'BoardCard',
components: {
IssueCardInner,
BoardCardInner,
},
props: {
list: {
......@@ -76,6 +76,6 @@ export default {
class="board-card gl-p-5 gl-rounded-base"
@mouseup="toggleIssue($event)"
>
<issue-card-inner :list="list" :issue="issue" :update-filters="true" />
<board-card-inner :list="list" :item="issue" :update-filters="true" />
</li>
</template>
......@@ -2,7 +2,7 @@
import { GlLabel, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { sortBy } from 'lodash';
import { mapActions, mapState } from 'vuex';
import issueCardInner from 'ee_else_ce/boards/mixins/issue_card_inner';
import boardCardInner from 'ee_else_ce/boards/mixins/board_card_inner';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { updateHistory } from '~/lib/utils/url_utility';
import { sprintf, __, n__ } from '~/locale';
......@@ -26,10 +26,10 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [issueCardInner],
mixins: [boardCardInner],
inject: ['groupId', 'rootPath', 'scopedLabelsAvailable'],
props: {
issue: {
item: {
type: Object,
required: true,
},
......@@ -52,19 +52,19 @@ export default {
};
},
computed: {
...mapState(['isShowingLabels']),
...mapState(['isShowingLabels', 'isEpicBoard']),
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);
if (this.item.assignees.length <= this.maxRender) {
return this.item.assignees.slice(0, this.maxRender);
}
return this.issue.assignees.slice(0, this.limitBeforeCounter);
return this.item.assignees.slice(0, this.limitBeforeCounter);
},
numberOverLimit() {
return this.issue.assignees.length - this.limitBeforeCounter;
return this.item.assignees.length - this.limitBeforeCounter;
},
assigneeCounterTooltip() {
const { numberOverLimit, maxCounter } = this;
......@@ -79,31 +79,35 @@ export default {
return `+${this.numberOverLimit}`;
},
shouldRenderCounter() {
if (this.issue.assignees.length <= this.maxRender) {
if (this.item.assignees.length <= this.maxRender) {
return false;
}
return this.issue.assignees.length > this.numberOverLimit;
return this.item.assignees.length > this.numberOverLimit;
},
issueId() {
if (this.issue.iid) {
return `#${this.issue.iid}`;
itemPrefix() {
return this.isEpicBoard ? '&' : '#';
},
itemId() {
if (this.item.iid) {
return `${this.itemPrefix}${this.item.iid}`;
}
return false;
},
showLabelFooter() {
return this.isShowingLabels && this.issue.labels.find(this.showLabel);
return this.isShowingLabels && this.item.labels.find(this.showLabel);
},
issueReferencePath() {
const { referencePath, groupId } = this.issue;
return !groupId ? referencePath.split('#')[0] : null;
itemReferencePath() {
const { referencePath } = this.item;
return referencePath.split(this.itemPrefix)[0];
},
orderedLabels() {
return sortBy(this.issue.labels.filter(this.isNonListLabel), 'title');
return sortBy(this.item.labels.filter(this.isNonListLabel), 'title');
},
blockedLabel() {
if (this.issue.blockedByCount) {
return n__(`Blocked by %d issue`, `Blocked by %d issues`, this.issue.blockedByCount);
if (this.item.blockedByCount) {
return n__(`Blocked by %d issue`, `Blocked by %d issues`, this.item.blockedByCount);
}
return __('Blocked issue');
},
......@@ -160,7 +164,7 @@ export default {
<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-if="item.blocked"
v-gl-tooltip
name="issue-block"
:title="blockedLabel"
......@@ -169,7 +173,7 @@ export default {
data-testid="issue-blocked-icon"
/>
<gl-icon
v-if="issue.confidential"
v-if="item.confidential"
v-gl-tooltip
name="eye-slash"
:title="__('Confidential')"
......@@ -177,11 +181,11 @@ export default {
:aria-label="__('Confidential')"
/>
<a
:href="issue.path || issue.webUrl || ''"
:title="issue.title"
:href="item.path || item.webUrl || ''"
:title="item.title"
class="js-no-trigger"
@mousemove.stop
>{{ issue.title }}</a
>{{ item.title }}</a
>
</h4>
</div>
......@@ -205,29 +209,30 @@ export default {
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"
v-if="item.referencePath"
class="board-card-number gl-overflow-hidden gl-display-flex gl-mr-3 gl-mt-3"
:class="{ 'gl-font-base': isEpicBoard }"
>
<tooltip-on-truncate
v-if="issueReferencePath"
:title="issueReferencePath"
v-if="itemReferencePath"
:title="itemReferencePath"
placement="bottom"
class="board-issue-path gl-text-truncate gl-font-weight-bold"
>{{ issueReferencePath }}</tooltip-on-truncate
class="board-item-path gl-text-truncate gl-font-weight-bold"
>{{ itemReferencePath }}</tooltip-on-truncate
>
#{{ issue.iid }}
{{ itemId }}
</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)"
v-if="item.dueDate"
:date="item.dueDate"
:closed="item.closed || Boolean(item.closedAt)"
/>
<issue-time-estimate v-if="issue.timeEstimate" :estimate="issue.timeEstimate" />
<issue-time-estimate v-if="item.timeEstimate" :estimate="item.timeEstimate" />
<issue-card-weight
v-if="validIssueWeight"
:weight="issue.weight"
@click="filterByWeight(issue.weight)"
v-if="validIssueWeight(item)"
:weight="item.weight"
@click="filterByWeight(item.weight)"
/>
</span>
</div>
......
......@@ -3,13 +3,12 @@ import { mapActions, mapGetters } from 'vuex';
import { ISSUABLE } from '~/boards/constants';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import boardsStore from '../stores/boards_store';
import IssueCardInner from './issue_card_inner.vue';
import IssueCardInnerDeprecated from './issue_card_inner_deprecated.vue';
export default {
name: 'BoardCardLayout',
components: {
IssueCardInner: gon.features?.graphqlBoardLists ? IssueCardInner : IssueCardInnerDeprecated,
IssueCardInner: IssueCardInnerDeprecated,
},
mixins: [glFeatureFlagMixin()],
props: {
......
......@@ -2,7 +2,7 @@
import { GlLabel, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { sortBy } from 'lodash';
import { mapState } from 'vuex';
import issueCardInner from 'ee_else_ce/boards/mixins/issue_card_inner';
import boardCardInner from 'ee_else_ce/boards/mixins/board_card_inner';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { sprintf, __, n__ } from '~/locale';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
......@@ -24,7 +24,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [issueCardInner],
mixins: [boardCardInner],
inject: ['groupId', 'rootPath'],
props: {
issue: {
......@@ -207,7 +207,7 @@ export default {
/>
<issue-time-estimate v-if="issue.timeEstimate" :estimate="issue.timeEstimate" />
<issue-card-weight
v-if="validIssueWeight"
v-if="validIssueWeight(issue)"
:weight="issue.weight"
@click="filterByWeight(issue.weight)"
/>
......
......@@ -2,11 +2,11 @@
import { GlIcon } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import ModalStore from '../../stores/modal_store';
import IssueCardInner from '../issue_card_inner.vue';
import BoardCardInner from '../board_card_inner.vue';
export default {
components: {
IssueCardInner,
BoardCardInner,
GlIcon,
},
props: {
......@@ -126,7 +126,7 @@ export default {
class="board-card position-relative p-3 rounded"
@click="toggleIssue($event, issue)"
>
<issue-card-inner :issue="issue" />
<board-card-inner :item="issue" />
<gl-icon
v-if="issue.selected"
:aria-label="'Issue #' + issue.id + ' selected'"
......
export default {
computed: {
methods: {
validIssueWeight() {
return false;
},
},
methods: {
filterByWeight() {},
},
};
......@@ -515,7 +515,7 @@
}
}
.board-issue-path.js-show-tooltip {
.board-item-path.js-show-tooltip {
cursor: help;
}
......
......@@ -18,6 +18,8 @@ query ListEpics(
node {
...EpicNode
relativePosition
referencePath: reference(full: true)
confidential
labels {
nodes {
...Label
......
import { isNumber } from 'lodash';
export default {
computed: {
validIssueWeight() {
if (this.issue && isNumber(this.issue.weight)) {
return this.issue.weight >= 0;
methods: {
validIssueWeight(issue) {
if (issue && isNumber(issue.weight)) {
return issue.weight >= 0;
}
return false;
},
},
methods: {
filterByWeight(weight) {
if (!this.updateFilters) return;
......
import { GlLabel } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import IssueCardWeight from 'ee/boards/components/issue_card_weight.vue';
import IssueCardInner from '~/boards/components/issue_card_inner.vue';
import BoardCardInner from '~/boards/components/board_card_inner.vue';
import defaultStore from '~/boards/stores';
describe('Issue card component', () => {
describe('Board card component', () => {
let wrapper;
let issue;
let list;
const createComponent = (props = {}, store = defaultStore) => {
wrapper = shallowMount(IssueCardInner, {
wrapper = shallowMount(BoardCardInner, {
store,
propsData: {
list,
issue,
item: issue,
...props,
},
provide: {
......
import { GlLabel } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { range } from 'lodash';
import IssueCardInner from '~/boards/components/issue_card_inner.vue';
import BoardCardInner from '~/boards/components/board_card_inner.vue';
import eventHub from '~/boards/eventhub';
import defaultStore from '~/boards/stores';
import { updateHistory } from '~/lib/utils/url_utility';
......@@ -10,7 +10,7 @@ import { mockLabelList } from './mock_data';
jest.mock('~/lib/utils/url_utility');
jest.mock('~/boards/eventhub');
describe('Issue card component', () => {
describe('Board card component', () => {
const user = {
id: 1,
name: 'testing 123',
......@@ -31,11 +31,11 @@ describe('Issue card component', () => {
let list;
const createWrapper = (props = {}, store = defaultStore) => {
wrapper = mount(IssueCardInner, {
wrapper = mount(BoardCardInner, {
store,
propsData: {
list,
issue,
item: issue,
...props,
},
stubs: {
......@@ -63,7 +63,7 @@ describe('Issue card component', () => {
weight: 1,
};
createWrapper({ issue, list });
createWrapper({ item: issue, list });
});
afterEach(() => {
......@@ -103,8 +103,8 @@ describe('Issue card component', () => {
describe('confidential issue', () => {
beforeEach(() => {
wrapper.setProps({
issue: {
...wrapper.props('issue'),
item: {
...wrapper.props('item'),
confidential: true,
},
});
......@@ -119,8 +119,8 @@ describe('Issue card component', () => {
describe('with avatar', () => {
beforeEach(() => {
wrapper.setProps({
issue: {
...wrapper.props('issue'),
item: {
...wrapper.props('item'),
assignees: [user],
updateData(newData) {
Object.assign(this, newData);
......@@ -146,8 +146,8 @@ describe('Issue card component', () => {
});
it('renders the avatar using avatarUrl property', async () => {
wrapper.props('issue').updateData({
...wrapper.props('issue'),
wrapper.props('item').updateData({
...wrapper.props('item'),
assignees: [
{
id: '1',
......@@ -172,8 +172,8 @@ describe('Issue card component', () => {
global.gon.default_avatar_url = 'default_avatar';
wrapper.setProps({
issue: {
...wrapper.props('issue'),
item: {
...wrapper.props('item'),
assignees: [
{
id: 1,
......@@ -201,8 +201,8 @@ describe('Issue card component', () => {
describe('multiple assignees', () => {
beforeEach(() => {
wrapper.setProps({
issue: {
...wrapper.props('issue'),
item: {
...wrapper.props('item'),
assignees: [
{
id: 2,
......@@ -233,7 +233,7 @@ describe('Issue card component', () => {
describe('more than three assignees', () => {
beforeEach(() => {
const { assignees } = wrapper.props('issue');
const { assignees } = wrapper.props('item');
assignees.push({
id: 5,
name: 'user5',
......@@ -242,8 +242,8 @@ describe('Issue card component', () => {
});
wrapper.setProps({
issue: {
...wrapper.props('issue'),
item: {
...wrapper.props('item'),
assignees,
},
});
......@@ -259,7 +259,7 @@ describe('Issue card component', () => {
it('renders 99+ avatar counter', async () => {
const assignees = [
...wrapper.props('issue').assignees,
...wrapper.props('item').assignees,
...range(5, 103).map((i) => ({
id: i,
name: 'name',
......@@ -268,8 +268,8 @@ describe('Issue card component', () => {
})),
];
wrapper.setProps({
issue: {
...wrapper.props('issue'),
item: {
...wrapper.props('item'),
assignees,
},
});
......@@ -283,7 +283,7 @@ describe('Issue card component', () => {
describe('labels', () => {
beforeEach(() => {
wrapper.setProps({ issue: { ...issue, labels: [list.label, label1] } });
wrapper.setProps({ item: { ...issue, labels: [list.label, label1] } });
});
it('does not render list label but renders all other labels', () => {
......@@ -295,7 +295,7 @@ describe('Issue card component', () => {
});
it('does not render label if label does not have an ID', async () => {
wrapper.setProps({ issue: { ...issue, labels: [label1, { title: 'closed' }] } });
wrapper.setProps({ item: { ...issue, labels: [label1, { title: 'closed' }] } });
await wrapper.vm.$nextTick();
......@@ -307,8 +307,8 @@ describe('Issue card component', () => {
describe('blocked', () => {
beforeEach(() => {
wrapper.setProps({
issue: {
...wrapper.props('issue'),
item: {
...wrapper.props('item'),
blocked: true,
},
});
......
......@@ -7,7 +7,7 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import BoardCardDeprecated from '~/boards/components/board_card_deprecated.vue';
import issueCardInner from '~/boards/components/issue_card_inner.vue';
import issueCardInner from '~/boards/components/issue_card_inner_deprecated.vue';
import eventHub from '~/boards/eventhub';
import store from '~/boards/stores';
import boardsStore from '~/boards/stores/boards_store';
......
......@@ -11,7 +11,7 @@ import '~/boards/models/label';
import '~/boards/models/assignee';
import '~/boards/models/list';
import BoardCardLayout from '~/boards/components/board_card_layout_deprecated.vue';
import issueCardInner from '~/boards/components/issue_card_inner.vue';
import issueCardInner from '~/boards/components/issue_card_inner_deprecated.vue';
import { ISSUABLE } from '~/boards/constants';
import boardsVuexStore from '~/boards/stores';
import boardsStore from '~/boards/stores/boards_store';
......
......@@ -2,7 +2,7 @@ import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import BoardCard from '~/boards/components/board_card.vue';
import IssueCardInner from '~/boards/components/issue_card_inner.vue';
import BoardCardInner from '~/boards/components/board_card_inner.vue';
import { inactiveId } from '~/boards/constants';
import { mockLabelList, mockIssue } from '../mock_data';
......@@ -38,7 +38,7 @@ describe('Board card layout', () => {
wrapper = shallowMount(BoardCard, {
localVue,
stubs: {
IssueCardInner,
BoardCardInner,
},
store,
propsData: {
......
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