Commit 27b66c4c authored by Kushal Pandya's avatar Kushal Pandya

Merge branch...

Merge branch '222800-allow-users-to-create-new-issues-within-their-board-when-swimlanes-are-enabled' into 'master'

Swimlanes - Sticky headers and epic titles

See merge request gitlab-org/gitlab!36388
parents 034f2824 cccf4a69
......@@ -77,6 +77,7 @@ export default {
:can-admin-list="canAdminList"
:disabled="disabled"
:board-id="boardId"
:group-id="groupId"
/>
</div>
</template>
......@@ -162,7 +162,7 @@ export default {
'has-border': list.label && list.label.color,
'gl-relative': list.isExpanded,
'gl-h-full': !list.isExpanded,
'board-inner gl-rounded-base': isSwimlanesHeader,
'board-inner gl-rounded-top-left-base gl-rounded-top-right-base': isSwimlanesHeader,
}"
:style="{ borderTopColor: list.label && list.label.color ? list.label.color : null }"
class="board-header gl-relative"
......@@ -184,7 +184,7 @@ export default {
:aria-label="chevronTooltip"
:title="chevronTooltip"
:icon="chevronIcon"
class="board-title-caret no-drag gl-cursor-pointer "
class="board-title-caret no-drag gl-cursor-pointer"
variant="link"
@click="toggleExpanded"
/>
......
<script>
import { GlIcon, GlLink, GlPopover, GlTooltipDirective } from '@gitlab/ui';
import { GlButton, GlIcon, GlLink, GlPopover, GlTooltipDirective } from '@gitlab/ui';
import { __, n__, sprintf } from '~/locale';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { formatDate } from '~/lib/utils/datetime_utility';
......@@ -8,6 +8,7 @@ import IssuesLaneList from './issues_lane_list.vue';
export default {
components: {
GlButton,
GlIcon,
GlLink,
GlPopover,
......@@ -27,10 +28,21 @@ export default {
required: true,
},
},
data() {
return {
isExpanded: true,
};
},
computed: {
isOpen() {
return this.epic.state === statusType.open;
},
chevronTooltip() {
return this.isExpanded ? __('Collapse') : __('Expand');
},
chevronIcon() {
return this.isExpanded ? 'chevron-down' : 'chevron-right';
},
stateText() {
return this.isOpen ? __('Opened') : __('Closed');
},
......@@ -66,31 +78,45 @@ export default {
Boolean(listIssues.find(listIssue => String(listIssue.iid) === epicIssue.iid)),
);
},
toggleExpanded() {
this.isExpanded = !this.isExpanded;
},
},
};
</script>
<template>
<div>
<div class="board-epic-lane gl-py-5 gl-px-3 gl-display-flex gl-align-items-center">
<div class="board-epic-lane gl-sticky gl-left-0 gl-display-inline-block gl-max-w-full">
<div class="gl-py-5 gl-px-3 gl-display-flex gl-align-items-center">
<gl-button
v-gl-tooltip.hover.right
:aria-label="chevronTooltip"
:title="chevronTooltip"
:icon="chevronIcon"
class="gl-mr-2 gl-cursor-pointer"
variant="link"
data-testid="epic-lane-chevron"
@click="toggleExpanded"
/>
<gl-icon
class="gl-mr-2 gl-flex-shrink-0"
:class="stateIconClass"
:name="epicIcon"
:aria-label="stateText"
/>
<span
<h4
ref="epicTitle"
class="gl-mr-3 gl-font-weight-bold gl-white-space-nowrap gl-text-overflow-ellipsis gl-overflow-hidden"
class="gl-mr-3 gl-font-weight-bold gl-font-base gl-white-space-nowrap gl-text-overflow-ellipsis gl-overflow-hidden"
>
{{ epic.title }}
</span>
</h4>
<gl-popover :target="() => $refs.epicTitle" triggers="hover" placement="top">
<template #title
>{{ epic.title }} &middot; {{ epic.reference }}</template
>
<p class="gl-m-0">{{ epicTimeAgoString }}</p>
<p class="gl-mb-2">{{ epicDateString }}</p>
<div>{{ epicTimeAgoString }}</div>
<div class="gl-mb-2">{{ epicDateString }}</div>
<gl-link :href="epic.webUrl" class="gl-font-sm">{{ __('Go to epic') }}</gl-link>
</gl-popover>
<span
......@@ -105,7 +131,8 @@ export default {
<span aria-hidden="true">{{ issuesCount }}</span>
</span>
</div>
<div class="gl-display-flex">
</div>
<div v-if="isExpanded" class="gl-display-flex">
<issues-lane-list
v-for="list in lists"
:key="`${list.id}-issues`"
......
......@@ -34,6 +34,11 @@ export default {
required: false,
default: false,
},
groupId: {
type: Number,
required: false,
default: 0,
},
},
computed: {
...mapState(['epics']),
......@@ -49,14 +54,14 @@ export default {
<template>
<div
class="board-swimlanes gl-white-space-nowrap gl-py-5 gl-px-3"
class="board-swimlanes gl-white-space-nowrap gl-pb-5 gl-px-3"
data_qa_selector="board_epics_swimlanes"
>
<div class="board-swimlanes-headers gl-sticky gl-pt-5 gl-bg-white gl-top-0 gl-z-index-3">
<div
v-for="list in lists"
:key="list.id"
:class="{
'is-expandable': list.isExpandable,
'is-collapsed': !list.isExpanded,
}"
class="board gl-px-3 gl-vertical-align-top gl-white-space-normal"
......@@ -69,11 +74,11 @@ export default {
:is-swimlanes-header="true"
/>
</div>
</div>
<div class="board-epics-swimlanes">
<epic-lane v-for="epic in epics" :key="epic.id" :epic="epic" :lists="lists" />
<div
class="board-lane-unassigned-issues gl-py-5 gl-px-3 gl-display-flex gl-align-items-center"
>
<div class="board-lane-unassigned-issues gl-sticky gl-display-inline-block gl-left-0">
<div class="gl-left-0 gl-py-5 gl-px-3 gl-display-flex gl-align-items-center">
<span
class="gl-mr-3 gl-font-weight-bold gl-white-space-nowrap gl-text-overflow-ellipsis gl-overflow-hidden"
>
......@@ -91,12 +96,15 @@ export default {
<span aria-hidden="true">{{ issuesCount }}</span>
</span>
</div>
</div>
<div class="gl-display-flex">
<issues-lane-list
v-for="list in lists"
:key="`${list.id}-issues`"
:list="list"
:issues="list.issues"
:group-id="groupId"
:is-unassigned-issues-lane="true"
/>
</div>
</div>
......
<script>
import eventHub from '~/boards/eventhub';
import BoardCard from '~/boards/components/board_card.vue';
import BoardNewIssue from '~/boards/components/board_new_issue.vue';
export default {
components: {
BoardCard,
BoardNewIssue,
},
props: {
list: {
......@@ -14,16 +17,50 @@ export default {
type: Array,
required: true,
},
groupId: {
type: Number,
required: false,
default: 0,
},
isUnassignedIssuesLane: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
showIssueForm: false,
};
},
created() {
eventHub.$on(`toggle-issue-form-${this.list.id}`, this.toggleForm);
},
beforeDestroy() {
eventHub.$off(`toggle-issue-form-${this.list.id}`, this.toggleForm);
},
methods: {
toggleForm() {
this.showIssueForm = !this.showIssueForm;
if (this.showIssueForm && this.isUnassignedIssuesLane) {
this.$el.scrollIntoView(false);
}
},
},
};
</script>
<template>
<div
class="board gl-px-3 gl-vertical-align-top gl-white-space-normal gl-display-flex gl-flex-shrink-0 is-expandable"
class="board gl-px-3 gl-vertical-align-top gl-white-space-normal gl-display-flex gl-flex-shrink-0"
:class="{ 'is-collapsed': !list.isExpanded }"
>
<div class="board-inner gl-p-2 gl-rounded-base gl-relative gl-w-full">
<board-new-issue
v-if="list.type !== 'closed' && showIssueForm && isUnassignedIssuesLane"
:group-id="groupId"
:list="list"
/>
<ul v-if="list.isExpanded" class="gl-p-0 gl-m-0">
<board-card
v-for="(issue, index) in issues"
......
......@@ -66,5 +66,17 @@ describe('EpicLane', () => {
it('renders one IssuesLaneList component per list passed in props', () => {
expect(wrapper.findAll(IssuesLaneList)).toHaveLength(wrapper.props('lists').length);
});
it('hides issues when collapsing', () => {
expect(wrapper.findAll(IssuesLaneList)).toHaveLength(wrapper.props('lists').length);
expect(wrapper.vm.isExpanded).toBe(true);
wrapper.find('[data-testid="epic-lane-chevron"]').vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.findAll(IssuesLaneList)).toHaveLength(0);
expect(wrapper.vm.isExpanded).toBe(false);
});
});
});
});
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