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