Commit 52d8c768 authored by sstern's avatar sstern Committed by Paul Slaughter

Fix top position on board sidebar

- Uses VuePortal so that we don't have to
  programatically calculate the header height
- https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62051#note_583904593

Changelog: fixed
parent c1e45452
<script>
import { GlDrawer } from '@gitlab/ui';
import { MountingPortal } from 'portal-vue';
import { mapState, mapActions, mapGetters } from 'vuex';
import SidebarDropdownWidget from 'ee_else_ce/sidebar/components/sidebar_dropdown_widget.vue';
import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardSidebarTimeTracker from '~/boards/components/sidebar/board_sidebar_time_tracker.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { ISSUABLE } from '~/boards/constants';
import { contentTop } from '~/lib/utils/common_utils';
import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue';
import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue';
......@@ -14,7 +14,6 @@ import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sideb
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
headerHeight: `${contentTop()}px`,
components: {
GlDrawer,
BoardSidebarTitle,
......@@ -25,6 +24,7 @@ export default {
BoardSidebarLabelsSelect,
SidebarSubscriptionsWidget,
SidebarDropdownWidget,
MountingPortal,
BoardSidebarWeightInput: () =>
import('ee_component/boards/components/sidebar/board_sidebar_weight_input.vue'),
IterationSidebarDropdownWidget: () =>
......@@ -45,6 +45,7 @@ export default {
default: false,
},
},
inheritAttrs: false,
computed: {
...mapGetters([
'isSidebarOpen',
......@@ -73,85 +74,88 @@ export default {
</script>
<template>
<gl-drawer
v-if="showSidebar"
:open="isSidebarOpen"
:header-height="$options.headerHeight"
@close="handleClose"
>
<template #header>{{ __('Issue details') }}</template>
<template #default>
<board-sidebar-title />
<sidebar-assignees-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
:initial-assignees="activeBoardItem.assignees"
:allow-multiple-assignees="multipleAssigneesFeatureAvailable"
@assignees-updated="setAssignees"
/>
<sidebar-dropdown-widget
v-if="epicFeatureAvailable"
:iid="activeBoardItem.iid"
issuable-attribute="epic"
:workspace-path="projectPathForActiveIssue"
:attr-workspace-path="groupPathForActiveIssue"
:issuable-type="issuableType"
data-testid="sidebar-epic"
/>
<div>
<mounting-portal mount-to="#js-right-sidebar-portal" name="board-content-sidebar" append>
<gl-drawer
v-if="showSidebar"
v-bind="$attrs"
:open="isSidebarOpen"
class="gl-absolute"
@close="handleClose"
>
<template #header>{{ __('Issue details') }}</template>
<template #default>
<board-sidebar-title />
<sidebar-assignees-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
:initial-assignees="activeBoardItem.assignees"
:allow-multiple-assignees="multipleAssigneesFeatureAvailable"
@assignees-updated="setAssignees"
/>
<sidebar-dropdown-widget
v-if="epicFeatureAvailable"
:iid="activeBoardItem.iid"
issuable-attribute="milestone"
issuable-attribute="epic"
:workspace-path="projectPathForActiveIssue"
:attr-workspace-path="projectPathForActiveIssue"
:attr-workspace-path="groupPathForActiveIssue"
:issuable-type="issuableType"
data-testid="sidebar-milestones"
data-testid="sidebar-epic"
/>
<template v-if="!glFeatures.iterationCadences">
<div>
<sidebar-dropdown-widget
v-if="iterationFeatureAvailable"
:iid="activeBoardItem.iid"
issuable-attribute="iteration"
:workspace-path="projectPathForActiveIssue"
:attr-workspace-path="groupPathForActiveIssue"
:issuable-type="issuableType"
class="gl-mt-5"
data-testid="iteration-edit"
/>
</template>
<template v-else>
<iteration-sidebar-dropdown-widget
v-if="iterationFeatureAvailable"
:iid="activeBoardItem.iid"
issuable-attribute="milestone"
:workspace-path="projectPathForActiveIssue"
:attr-workspace-path="groupPathForActiveIssue"
:attr-workspace-path="projectPathForActiveIssue"
:issuable-type="issuableType"
class="gl-mt-5"
data-testid="iteration-edit"
data-testid="sidebar-milestones"
/>
</template>
</div>
<board-sidebar-time-tracker class="swimlanes-sidebar-time-tracker" />
<sidebar-date-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
:issuable-type="issuableType"
data-testid="sidebar-due-date"
/>
<board-sidebar-labels-select class="labels" />
<board-sidebar-weight-input v-if="weightFeatureAvailable" class="weight" />
<sidebar-confidentiality-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
:issuable-type="issuableType"
@confidentialityUpdated="setActiveItemConfidential($event)"
/>
<sidebar-subscriptions-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
:issuable-type="issuableType"
data-testid="sidebar-notifications"
/>
</template>
</gl-drawer>
<template v-if="!glFeatures.iterationCadences">
<sidebar-dropdown-widget
v-if="iterationFeatureAvailable"
:iid="activeBoardItem.iid"
issuable-attribute="iteration"
:workspace-path="projectPathForActiveIssue"
:attr-workspace-path="groupPathForActiveIssue"
:issuable-type="issuableType"
class="gl-mt-5"
data-testid="iteration-edit"
/>
</template>
<template v-else>
<iteration-sidebar-dropdown-widget
v-if="iterationFeatureAvailable"
:iid="activeBoardItem.iid"
:workspace-path="projectPathForActiveIssue"
:attr-workspace-path="groupPathForActiveIssue"
:issuable-type="issuableType"
class="gl-mt-5"
data-testid="iteration-edit"
/>
</template>
</div>
<board-sidebar-time-tracker class="swimlanes-sidebar-time-tracker" />
<sidebar-date-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
:issuable-type="issuableType"
data-testid="sidebar-due-date"
/>
<board-sidebar-labels-select class="labels" />
<board-sidebar-weight-input v-if="weightFeatureAvailable" class="weight" />
<sidebar-confidentiality-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
:issuable-type="issuableType"
@confidentialityUpdated="setActiveItemConfidential($event)"
/>
<sidebar-subscriptions-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
:issuable-type="issuableType"
data-testid="sidebar-notifications"
/>
</template>
</gl-drawer>
</mounting-portal>
</template>
<script>
import { GlButton, GlDrawer, GlLabel } from '@gitlab/ui';
import { MountingPortal } from 'portal-vue';
import { mapActions, mapState, mapGetters } from 'vuex';
import { LIST, ListType, ListTypeTitles } from '~/boards/constants';
import boardsStore from '~/boards/stores/boards_store';
......@@ -9,14 +10,13 @@ import eventHub from '~/sidebar/event_hub';
import Tracking from '~/tracking';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
// NOTE: need to revisit how we handle headerHeight, because we have so many different header and footer options.
export default {
headerHeight: process.env.NODE_ENV === 'development' ? '75px' : '40px',
listSettingsText: __('List settings'),
components: {
GlButton,
GlDrawer,
GlLabel,
MountingPortal,
BoardSettingsSidebarWipLimit: () =>
import('ee_component/boards/components/board_settings_wip_limit.vue'),
BoardSettingsListTypes: () =>
......@@ -24,6 +24,7 @@ export default {
},
mixins: [glFeatureFlagMixin(), Tracking.mixin()],
inject: ['canAdminList'],
inheritAttrs: false,
data() {
return {
ListType,
......@@ -86,43 +87,45 @@ export default {
</script>
<template>
<gl-drawer
v-if="showSidebar"
class="js-board-settings-sidebar"
:open="isSidebarOpen"
:header-height="$options.headerHeight"
@close="unsetActiveId"
>
<template #header>{{ $options.listSettingsText }}</template>
<template v-if="isSidebarOpen">
<div v-if="boardListType === ListType.label">
<label class="js-list-label gl-display-block">{{ listTypeTitle }}</label>
<gl-label
:title="activeListLabel.title"
:background-color="activeListLabel.color"
:scoped="showScopedLabels(activeListLabel)"
/>
</div>
<mounting-portal mount-to="#js-right-sidebar-portal" name="board-settings-sidebar" append>
<gl-drawer
v-if="showSidebar"
v-bind="$attrs"
class="js-board-settings-sidebar gl-absolute"
:open="isSidebarOpen"
@close="unsetActiveId"
>
<template #header>{{ $options.listSettingsText }}</template>
<template v-if="isSidebarOpen">
<div v-if="boardListType === ListType.label">
<label class="js-list-label gl-display-block">{{ listTypeTitle }}</label>
<gl-label
:title="activeListLabel.title"
:background-color="activeListLabel.color"
:scoped="showScopedLabels(activeListLabel)"
/>
</div>
<board-settings-list-types
v-else
:active-list="activeList"
:board-list-type="boardListType"
/>
<board-settings-sidebar-wip-limit
v-if="isWipLimitsOn"
:max-issue-count="activeList.maxIssueCount"
/>
<div v-if="canAdminList && !activeList.preset && activeList.id" class="gl-mt-4">
<gl-button
variant="danger"
category="secondary"
icon="remove"
data-testid="remove-list"
@click.stop="deleteBoard"
>{{ __('Remove list') }}
</gl-button>
</div>
</template>
</gl-drawer>
<board-settings-list-types
v-else
:active-list="activeList"
:board-list-type="boardListType"
/>
<board-settings-sidebar-wip-limit
v-if="isWipLimitsOn"
:max-issue-count="activeList.maxIssueCount"
/>
<div v-if="canAdminList && !activeList.preset && activeList.id" class="gl-mt-4">
<gl-button
variant="danger"
category="secondary"
icon="remove"
data-testid="remove-list"
@click.stop="deleteBoard"
>{{ __('Remove list') }}
</gl-button>
</div>
</template>
</gl-drawer>
</mounting-portal>
</template>
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import PortalVue from 'portal-vue';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { mapActions, mapGetters } from 'vuex';
......@@ -41,6 +42,7 @@ import boardConfigToggle from './config_toggle';
import mountMultipleBoardsSwitcher from './mount_multiple_boards_switcher';
Vue.use(VueApollo);
Vue.use(PortalVue);
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
......
......@@ -28,5 +28,6 @@
= render "layouts/flash", extra_flash_class: 'limit-container-width'
= yield :before_content
= yield
= yield :after_content
= render "layouts/nav/top_nav_responsive", class: 'layout-page content-wrapper-margin'
......@@ -2,6 +2,7 @@
- group = local_assigns.fetch(:group, false)
- @no_breadcrumb_container = true
- @no_container = true
- @content_wrapper_class = "#{@content_wrapper_class} gl-relative"
- @content_class = "issue-boards-content js-focus-mode-board"
- if board.to_type == "EpicBoard"
- breadcrumb_title _("Epic Boards")
......@@ -9,6 +10,9 @@
- breadcrumb_title _("Issue Boards")
= render 'shared/alerts/positioning_disabled'
= content_for :after_content do
#js-right-sidebar-portal
- page_title("#{board.name}", _("Boards"))
- add_page_specific_style 'page_bundles/boards'
......
<script>
import { GlDrawer } from '@gitlab/ui';
import { MountingPortal } from 'portal-vue';
import { mapState, mapActions, mapGetters } from 'vuex';
import SidebarAncestorsWidget from 'ee_component/sidebar/components/ancestors_tree/sidebar_ancestors_widget.vue';
import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { ISSUABLE } from '~/boards/constants';
import { contentTop } from '~/lib/utils/common_utils';
import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue';
import SidebarParticipantsWidget from '~/sidebar/components/participants/sidebar_participants_widget.vue';
import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue';
export default {
headerHeight: `${contentTop()}px`,
components: {
GlDrawer,
BoardSidebarLabelsSelect,
......@@ -22,7 +21,9 @@ export default {
SidebarParticipantsWidget,
SidebarSubscriptionsWidget,
SidebarAncestorsWidget,
MountingPortal,
},
inheritAttrs: false,
computed: {
...mapGetters(['isSidebarOpen', 'activeBoardItem']),
...mapState(['sidebarType', 'fullPath', 'issuableType']),
......@@ -43,51 +44,54 @@ export default {
</script>
<template>
<gl-drawer
v-if="showSidebar"
:open="isSidebarOpen"
:header-height="$options.headerHeight"
@close="handleClose"
>
<template #header>{{ __('Epic details') }}</template>
<template #default>
<board-sidebar-title data-testid="sidebar-title" />
<sidebar-date-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
date-type="startDate"
:issuable-type="issuableType"
:can-inherit="true"
/>
<sidebar-date-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
date-type="dueDate"
:issuable-type="issuableType"
:can-inherit="true"
/>
<board-sidebar-labels-select class="labels" />
<sidebar-confidentiality-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
:issuable-type="issuableType"
@confidentialityUpdated="setActiveItemConfidential($event)"
/>
<sidebar-ancestors-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
issuable-type="epic"
/>
<sidebar-participants-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
issuable-type="epic"
/>
<sidebar-subscriptions-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
:issuable-type="issuableType"
/>
</template>
</gl-drawer>
<mounting-portal mount-to="#js-right-sidebar-portal" name="epic-board-sidebar" append>
<gl-drawer
v-if="showSidebar"
v-bind="$attrs"
class="gl-absolute"
:open="isSidebarOpen"
@close="handleClose"
>
<template #header>{{ __('Epic details') }}</template>
<template #default>
<board-sidebar-title data-testid="sidebar-title" />
<sidebar-date-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
date-type="startDate"
:issuable-type="issuableType"
:can-inherit="true"
/>
<sidebar-date-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
date-type="dueDate"
:issuable-type="issuableType"
:can-inherit="true"
/>
<board-sidebar-labels-select class="labels" />
<sidebar-confidentiality-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
:issuable-type="issuableType"
@confidentialityUpdated="setActiveItemConfidential($event)"
/>
<sidebar-ancestors-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
issuable-type="epic"
/>
<sidebar-participants-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
issuable-type="epic"
/>
<sidebar-subscriptions-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
:issuable-type="issuableType"
/>
</template>
</gl-drawer>
</mounting-portal>
</template>
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ee/BoardContentSidebar matches the snapshot 1`] = `
<div>
<div
class="gl-absolute"
>
Issue details
<boardsidebartitle-stub />
......
......@@ -31,7 +31,15 @@ describe('ee/BoardContentSidebar', () => {
});
};
const setPortalAnchorPoint = () => {
const el = document.createElement('div');
el.setAttribute('id', 'js-right-sidebar-portal');
document.body.appendChild(el);
};
const createComponent = () => {
setPortalAnchorPoint();
/*
Dynamically imported components (in our case ee imports)
aren't stubbed automatically when using shallow mount in VTU v1:
......@@ -63,6 +71,7 @@ describe('ee/BoardContentSidebar', () => {
SidebarSubscriptionsWidget: true,
BoardSidebarWeightInput: true,
SidebarDropdownWidget: true,
MountingPortal: true,
},
});
};
......@@ -78,6 +87,6 @@ describe('ee/BoardContentSidebar', () => {
});
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
expect(wrapper.find(GlDrawer).element).toMatchSnapshot();
});
});
import { GlDrawer } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { MountingPortal } from 'portal-vue';
import Vuex from 'vuex';
import EpicBoardContentSidebar from 'ee_component/boards/components/epic_board_content_sidebar.vue';
import SidebarAncestorsWidget from 'ee_component/sidebar/components/ancestors_tree/sidebar_ancestors_widget.vue';
......@@ -66,6 +67,14 @@ describe('EpicBoardContentSidebar', () => {
expect(wrapper.findComponent(GlDrawer).exists()).toBe(true);
});
it('confirms we render MountingPortal', () => {
expect(wrapper.find(MountingPortal).props()).toMatchObject({
mountTo: '#js-right-sidebar-portal',
append: true,
name: 'epic-board-sidebar',
});
});
it('does not render GlDrawer when isSidebarOpen is false', () => {
createStore({ mockGetters: { isSidebarOpen: () => false } });
createComponent();
......
import { GlDrawer } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { MountingPortal } from 'portal-vue';
import Vuex from 'vuex';
import SidebarDropdownWidget from 'ee_else_ce/sidebar/components/sidebar_dropdown_widget.vue';
import { stubComponent } from 'helpers/stub_component';
......@@ -90,6 +91,14 @@ describe('BoardContentSidebar', () => {
expect(wrapper.findComponent(GlDrawer).exists()).toBe(true);
});
it('confirms we render MountingPortal', () => {
expect(wrapper.find(MountingPortal).props()).toMatchObject({
mountTo: '#js-right-sidebar-portal',
append: true,
name: 'board-content-sidebar',
});
});
it('does not render GlDrawer when isSidebarOpen is false', () => {
createStore({ mockGetters: { isSidebarOpen: () => false } });
createComponent();
......
......@@ -3,6 +3,7 @@ import { GlDrawer, GlLabel } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { MountingPortal } from 'portal-vue';
import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue';
......@@ -51,6 +52,16 @@ describe('BoardSettingsSidebar', () => {
wrapper.destroy();
});
it('finds a MountingPortal component', () => {
createComponent();
expect(wrapper.find(MountingPortal).props()).toMatchObject({
mountTo: '#js-right-sidebar-portal',
append: true,
name: 'board-settings-sidebar',
});
});
describe('when sidebarType is "list"', () => {
it('finds a GlDrawer component', () => {
createComponent();
......
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