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> <script>
import { GlDrawer } from '@gitlab/ui'; import { GlDrawer } from '@gitlab/ui';
import { MountingPortal } from 'portal-vue';
import { mapState, mapActions, mapGetters } from 'vuex'; import { mapState, mapActions, mapGetters } from 'vuex';
import SidebarDropdownWidget from 'ee_else_ce/sidebar/components/sidebar_dropdown_widget.vue'; import SidebarDropdownWidget from 'ee_else_ce/sidebar/components/sidebar_dropdown_widget.vue';
import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue'; import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardSidebarTimeTracker from '~/boards/components/sidebar/board_sidebar_time_tracker.vue'; import BoardSidebarTimeTracker from '~/boards/components/sidebar/board_sidebar_time_tracker.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue'; import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { ISSUABLE } from '~/boards/constants'; import { ISSUABLE } from '~/boards/constants';
import { contentTop } from '~/lib/utils/common_utils';
import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue'; import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue';
import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue'; import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue'; import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue';
...@@ -14,7 +14,6 @@ import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sideb ...@@ -14,7 +14,6 @@ import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sideb
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default { export default {
headerHeight: `${contentTop()}px`,
components: { components: {
GlDrawer, GlDrawer,
BoardSidebarTitle, BoardSidebarTitle,
...@@ -25,6 +24,7 @@ export default { ...@@ -25,6 +24,7 @@ export default {
BoardSidebarLabelsSelect, BoardSidebarLabelsSelect,
SidebarSubscriptionsWidget, SidebarSubscriptionsWidget,
SidebarDropdownWidget, SidebarDropdownWidget,
MountingPortal,
BoardSidebarWeightInput: () => BoardSidebarWeightInput: () =>
import('ee_component/boards/components/sidebar/board_sidebar_weight_input.vue'), import('ee_component/boards/components/sidebar/board_sidebar_weight_input.vue'),
IterationSidebarDropdownWidget: () => IterationSidebarDropdownWidget: () =>
...@@ -45,6 +45,7 @@ export default { ...@@ -45,6 +45,7 @@ export default {
default: false, default: false,
}, },
}, },
inheritAttrs: false,
computed: { computed: {
...mapGetters([ ...mapGetters([
'isSidebarOpen', 'isSidebarOpen',
...@@ -73,10 +74,12 @@ export default { ...@@ -73,10 +74,12 @@ export default {
</script> </script>
<template> <template>
<mounting-portal mount-to="#js-right-sidebar-portal" name="board-content-sidebar" append>
<gl-drawer <gl-drawer
v-if="showSidebar" v-if="showSidebar"
v-bind="$attrs"
:open="isSidebarOpen" :open="isSidebarOpen"
:header-height="$options.headerHeight" class="gl-absolute"
@close="handleClose" @close="handleClose"
> >
<template #header>{{ __('Issue details') }}</template> <template #header>{{ __('Issue details') }}</template>
...@@ -154,4 +157,5 @@ export default { ...@@ -154,4 +157,5 @@ export default {
/> />
</template> </template>
</gl-drawer> </gl-drawer>
</mounting-portal>
</template> </template>
<script> <script>
import { GlButton, GlDrawer, GlLabel } from '@gitlab/ui'; import { GlButton, GlDrawer, GlLabel } from '@gitlab/ui';
import { MountingPortal } from 'portal-vue';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import { LIST, ListType, ListTypeTitles } from '~/boards/constants'; import { LIST, ListType, ListTypeTitles } from '~/boards/constants';
import boardsStore from '~/boards/stores/boards_store'; import boardsStore from '~/boards/stores/boards_store';
...@@ -9,14 +10,13 @@ import eventHub from '~/sidebar/event_hub'; ...@@ -9,14 +10,13 @@ import eventHub from '~/sidebar/event_hub';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; 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 { export default {
headerHeight: process.env.NODE_ENV === 'development' ? '75px' : '40px',
listSettingsText: __('List settings'), listSettingsText: __('List settings'),
components: { components: {
GlButton, GlButton,
GlDrawer, GlDrawer,
GlLabel, GlLabel,
MountingPortal,
BoardSettingsSidebarWipLimit: () => BoardSettingsSidebarWipLimit: () =>
import('ee_component/boards/components/board_settings_wip_limit.vue'), import('ee_component/boards/components/board_settings_wip_limit.vue'),
BoardSettingsListTypes: () => BoardSettingsListTypes: () =>
...@@ -24,6 +24,7 @@ export default { ...@@ -24,6 +24,7 @@ export default {
}, },
mixins: [glFeatureFlagMixin(), Tracking.mixin()], mixins: [glFeatureFlagMixin(), Tracking.mixin()],
inject: ['canAdminList'], inject: ['canAdminList'],
inheritAttrs: false,
data() { data() {
return { return {
ListType, ListType,
...@@ -86,11 +87,12 @@ export default { ...@@ -86,11 +87,12 @@ export default {
</script> </script>
<template> <template>
<mounting-portal mount-to="#js-right-sidebar-portal" name="board-settings-sidebar" append>
<gl-drawer <gl-drawer
v-if="showSidebar" v-if="showSidebar"
class="js-board-settings-sidebar" v-bind="$attrs"
class="js-board-settings-sidebar gl-absolute"
:open="isSidebarOpen" :open="isSidebarOpen"
:header-height="$options.headerHeight"
@close="unsetActiveId" @close="unsetActiveId"
> >
<template #header>{{ $options.listSettingsText }}</template> <template #header>{{ $options.listSettingsText }}</template>
...@@ -125,4 +127,5 @@ export default { ...@@ -125,4 +127,5 @@ export default {
</div> </div>
</template> </template>
</gl-drawer> </gl-drawer>
</mounting-portal>
</template> </template>
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import PortalVue from 'portal-vue';
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
...@@ -41,6 +42,7 @@ import boardConfigToggle from './config_toggle'; ...@@ -41,6 +42,7 @@ import boardConfigToggle from './config_toggle';
import mountMultipleBoardsSwitcher from './mount_multiple_boards_switcher'; import mountMultipleBoardsSwitcher from './mount_multiple_boards_switcher';
Vue.use(VueApollo); Vue.use(VueApollo);
Vue.use(PortalVue);
const fragmentMatcher = new IntrospectionFragmentMatcher({ const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData, introspectionQueryResultData,
......
...@@ -28,5 +28,6 @@ ...@@ -28,5 +28,6 @@
= render "layouts/flash", extra_flash_class: 'limit-container-width' = render "layouts/flash", extra_flash_class: 'limit-container-width'
= yield :before_content = yield :before_content
= yield = yield
= yield :after_content
= render "layouts/nav/top_nav_responsive", class: 'layout-page content-wrapper-margin' = render "layouts/nav/top_nav_responsive", class: 'layout-page content-wrapper-margin'
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
- group = local_assigns.fetch(:group, false) - group = local_assigns.fetch(:group, false)
- @no_breadcrumb_container = true - @no_breadcrumb_container = true
- @no_container = true - @no_container = true
- @content_wrapper_class = "#{@content_wrapper_class} gl-relative"
- @content_class = "issue-boards-content js-focus-mode-board" - @content_class = "issue-boards-content js-focus-mode-board"
- if board.to_type == "EpicBoard" - if board.to_type == "EpicBoard"
- breadcrumb_title _("Epic Boards") - breadcrumb_title _("Epic Boards")
...@@ -9,6 +10,9 @@ ...@@ -9,6 +10,9 @@
- breadcrumb_title _("Issue Boards") - breadcrumb_title _("Issue Boards")
= render 'shared/alerts/positioning_disabled' = render 'shared/alerts/positioning_disabled'
= content_for :after_content do
#js-right-sidebar-portal
- page_title("#{board.name}", _("Boards")) - page_title("#{board.name}", _("Boards"))
- add_page_specific_style 'page_bundles/boards' - add_page_specific_style 'page_bundles/boards'
......
<script> <script>
import { GlDrawer } from '@gitlab/ui'; import { GlDrawer } from '@gitlab/ui';
import { MountingPortal } from 'portal-vue';
import { mapState, mapActions, mapGetters } from 'vuex'; import { mapState, mapActions, mapGetters } from 'vuex';
import SidebarAncestorsWidget from 'ee_component/sidebar/components/ancestors_tree/sidebar_ancestors_widget.vue'; 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 BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue'; import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { ISSUABLE } from '~/boards/constants'; import { ISSUABLE } from '~/boards/constants';
import { contentTop } from '~/lib/utils/common_utils';
import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue'; import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue'; import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue';
import SidebarParticipantsWidget from '~/sidebar/components/participants/sidebar_participants_widget.vue'; import SidebarParticipantsWidget from '~/sidebar/components/participants/sidebar_participants_widget.vue';
import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue'; import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue';
export default { export default {
headerHeight: `${contentTop()}px`,
components: { components: {
GlDrawer, GlDrawer,
BoardSidebarLabelsSelect, BoardSidebarLabelsSelect,
...@@ -22,7 +21,9 @@ export default { ...@@ -22,7 +21,9 @@ export default {
SidebarParticipantsWidget, SidebarParticipantsWidget,
SidebarSubscriptionsWidget, SidebarSubscriptionsWidget,
SidebarAncestorsWidget, SidebarAncestorsWidget,
MountingPortal,
}, },
inheritAttrs: false,
computed: { computed: {
...mapGetters(['isSidebarOpen', 'activeBoardItem']), ...mapGetters(['isSidebarOpen', 'activeBoardItem']),
...mapState(['sidebarType', 'fullPath', 'issuableType']), ...mapState(['sidebarType', 'fullPath', 'issuableType']),
...@@ -43,10 +44,12 @@ export default { ...@@ -43,10 +44,12 @@ export default {
</script> </script>
<template> <template>
<mounting-portal mount-to="#js-right-sidebar-portal" name="epic-board-sidebar" append>
<gl-drawer <gl-drawer
v-if="showSidebar" v-if="showSidebar"
v-bind="$attrs"
class="gl-absolute"
:open="isSidebarOpen" :open="isSidebarOpen"
:header-height="$options.headerHeight"
@close="handleClose" @close="handleClose"
> >
<template #header>{{ __('Epic details') }}</template> <template #header>{{ __('Epic details') }}</template>
...@@ -90,4 +93,5 @@ export default { ...@@ -90,4 +93,5 @@ export default {
/> />
</template> </template>
</gl-drawer> </gl-drawer>
</mounting-portal>
</template> </template>
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ee/BoardContentSidebar matches the snapshot 1`] = ` exports[`ee/BoardContentSidebar matches the snapshot 1`] = `
<div> <div
class="gl-absolute"
>
Issue details Issue details
<boardsidebartitle-stub /> <boardsidebartitle-stub />
......
...@@ -31,7 +31,15 @@ describe('ee/BoardContentSidebar', () => { ...@@ -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 = () => { const createComponent = () => {
setPortalAnchorPoint();
/* /*
Dynamically imported components (in our case ee imports) Dynamically imported components (in our case ee imports)
aren't stubbed automatically when using shallow mount in VTU v1: aren't stubbed automatically when using shallow mount in VTU v1:
...@@ -63,6 +71,7 @@ describe('ee/BoardContentSidebar', () => { ...@@ -63,6 +71,7 @@ describe('ee/BoardContentSidebar', () => {
SidebarSubscriptionsWidget: true, SidebarSubscriptionsWidget: true,
BoardSidebarWeightInput: true, BoardSidebarWeightInput: true,
SidebarDropdownWidget: true, SidebarDropdownWidget: true,
MountingPortal: true,
}, },
}); });
}; };
...@@ -78,6 +87,6 @@ describe('ee/BoardContentSidebar', () => { ...@@ -78,6 +87,6 @@ describe('ee/BoardContentSidebar', () => {
}); });
it('matches the snapshot', () => { it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.find(GlDrawer).element).toMatchSnapshot();
}); });
}); });
import { GlDrawer } from '@gitlab/ui'; import { GlDrawer } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { MountingPortal } from 'portal-vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import EpicBoardContentSidebar from 'ee_component/boards/components/epic_board_content_sidebar.vue'; 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'; import SidebarAncestorsWidget from 'ee_component/sidebar/components/ancestors_tree/sidebar_ancestors_widget.vue';
...@@ -66,6 +67,14 @@ describe('EpicBoardContentSidebar', () => { ...@@ -66,6 +67,14 @@ describe('EpicBoardContentSidebar', () => {
expect(wrapper.findComponent(GlDrawer).exists()).toBe(true); 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', () => { it('does not render GlDrawer when isSidebarOpen is false', () => {
createStore({ mockGetters: { isSidebarOpen: () => false } }); createStore({ mockGetters: { isSidebarOpen: () => false } });
createComponent(); createComponent();
......
import { GlDrawer } from '@gitlab/ui'; import { GlDrawer } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { MountingPortal } from 'portal-vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import SidebarDropdownWidget from 'ee_else_ce/sidebar/components/sidebar_dropdown_widget.vue'; import SidebarDropdownWidget from 'ee_else_ce/sidebar/components/sidebar_dropdown_widget.vue';
import { stubComponent } from 'helpers/stub_component'; import { stubComponent } from 'helpers/stub_component';
...@@ -90,6 +91,14 @@ describe('BoardContentSidebar', () => { ...@@ -90,6 +91,14 @@ describe('BoardContentSidebar', () => {
expect(wrapper.findComponent(GlDrawer).exists()).toBe(true); 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', () => { it('does not render GlDrawer when isSidebarOpen is false', () => {
createStore({ mockGetters: { isSidebarOpen: () => false } }); createStore({ mockGetters: { isSidebarOpen: () => false } });
createComponent(); createComponent();
......
...@@ -3,6 +3,7 @@ import { GlDrawer, GlLabel } from '@gitlab/ui'; ...@@ -3,6 +3,7 @@ import { GlDrawer, GlLabel } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import axios from 'axios'; import axios from 'axios';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { MountingPortal } from 'portal-vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue'; import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue';
...@@ -51,6 +52,16 @@ describe('BoardSettingsSidebar', () => { ...@@ -51,6 +52,16 @@ describe('BoardSettingsSidebar', () => {
wrapper.destroy(); 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"', () => { describe('when sidebarType is "list"', () => {
it('finds a GlDrawer component', () => { it('finds a GlDrawer component', () => {
createComponent(); 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