Commit 52bdc609 authored by Phil Hughes's avatar Phil Hughes

Merge branch '7513-fix-epic-block-view-on-mobile' into 'master'

Fix sidebar in epics block view on mobile

Closes #7513

See merge request gitlab-org/gitlab-ee!7350
parents ea7da4a4 ead6120f
......@@ -4,6 +4,7 @@
import tooltip from '~/vue_shared/directives/tooltip';
import loadingButton from '~/vue_shared/components/loading_button.vue';
import { s__ } from '~/locale';
import eventHub from '../../event_hub';
export default {
name: 'EpicHeader',
......@@ -43,6 +44,9 @@
this.$emit('deleteEpic');
}
},
toggleSidebar() {
eventHub.$emit('toggleSidebar');
},
},
};
</script>
......@@ -71,5 +75,14 @@
container-class="btn btn-remove btn-inverted flex-right"
@click="deleteEpic"
/>
<button
:aria-label="__('toggle collapse')"
class="btn btn-default float-right d-block d-sm-none
gutter-toggle issuable-gutter-toggle js-sidebar-toggle"
type="button"
@click="toggleSidebar"
>
<i class="fa fa-angle-double-left"></i>
</button>
</div>
</template>
......@@ -195,7 +195,7 @@
</script>
<template>
<div>
<div class="epic-page-container">
<epic-header
:author="author"
:created="created"
......
import Vue from 'vue';
import Cookies from 'js-cookie';
import bp from '~/breakpoints';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import EpicShowApp from './components/epic_show_app.vue';
......@@ -7,6 +9,12 @@ export default () => {
const metaData = convertObjectPropsToCamelCase(JSON.parse(el.dataset.meta));
const initialData = JSON.parse(el.dataset.initial);
// Collapse the sidebar on mobile screens by default
const bpBreakpoint = bp.getBreakpointSize();
if (bpBreakpoint === 'xs' || bpBreakpoint === 'sm') {
Cookies.set('collapsed_gutter', true);
}
const props = Object.assign({}, initialData, metaData, el.dataset);
return new Vue({
......
import $ from 'jquery';
import Mousetrap from 'mousetrap';
import Cookies from 'js-cookie';
import bp from '~/breakpoints';
export default class SidebarContext {
constructor() {
......@@ -33,6 +35,14 @@ export default class SidebarContext {
setTimeout(() => $block.find('.js-label-select').trigger('click'), 0);
}
});
window.addEventListener('beforeunload', () => {
// collapsed_gutter cookie hides the sidebar
const bpBreakpoint = bp.getBreakpointSize();
if (bpBreakpoint === 'xs' || bpBreakpoint === 'sm') {
Cookies.set('collapsed_gutter', true);
}
});
}
static openSidebarDropdown($block) {
......
import Vue from 'vue';
export default new Vue();
......@@ -11,6 +11,7 @@ import SidebarTodo from '~/sidebar/components/todo_toggle/todo.vue';
import SidebarCollapsedGroupedDatePicker from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue';
import ToggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue';
import SidebarLabelsSelect from '~/vue_shared/components/sidebar/labels_select/base.vue';
import eventHub from '../../event_hub';
import SidebarDatePicker from './sidebar_date_picker.vue';
import SidebarParticipants from './sidebar_participants.vue';
import SidebarSubscriptions from './sidebar_subscriptions.vue';
......@@ -214,6 +215,12 @@ export default {
: this.store.dueDateTimeFromMilestones;
},
},
mounted() {
eventHub.$on('toggleSidebar', this.toggleSidebar);
},
beforeDestroy() {
eventHub.$off('toggleSidebar', this.toggleSidebar);
},
methods: {
getDateValidity(startDate, endDate) {
// If both dates are defined
......@@ -425,7 +432,7 @@ export default {
/>
</div>
<div
v-if="collapsed"
v-if="collapsed && isUserSignedIn"
class="block todo"
>
<sidebar-todo
......@@ -456,6 +463,7 @@ export default {
block-class="start-date"
@saveDate="saveStartDate"
@toggleDateType="changeStartDateType"
@toggleCollapse="toggleSidebar"
/>
<sidebar-date-picker
v-if="!collapsed"
......
......@@ -7,6 +7,7 @@ import popover from '~/vue_shared/directives/popover';
import Icon from '~/vue_shared/components/icon.vue';
import DatePicker from '~/vue_shared/components/pikaday.vue';
import CollapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
import ToggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue';
const label = __('Date picker');
const pickerLabel = __('Fixed date');
......@@ -20,6 +21,7 @@ export default {
Icon,
DatePicker,
CollapsedCalendarIcon,
ToggleSidebar,
},
props: {
blockClass: {
......@@ -32,6 +34,11 @@ export default {
required: false,
default: true,
},
showToggleSidebar: {
type: Boolean,
required: false,
default: false,
},
isLoading: {
type: Boolean,
required: false,
......@@ -159,6 +166,9 @@ export default {
toggleDateType(dateTypeFixed) {
this.$emit('toggleDateType', dateTypeFixed);
},
toggleSidebar() {
this.$emit('toggleCollapse');
},
},
};
</script>
......@@ -193,6 +203,11 @@ export default {
>
{{ __('Edit') }}
</button>
<toggle-sidebar
v-if="showToggleSidebar"
:collapsed="collapsed"
@toggle="toggleSidebar"
/>
</div>
</div>
<div class="value">
......
......@@ -34,6 +34,12 @@
}
}
@include media-breakpoint-down(xs) {
.epic-page-container .detail-page-header {
display: flex;
}
}
.tooltip .tooltip-inner .milestone-date-range {
color: $gl-text-color-tertiary;
}
......@@ -16,24 +16,19 @@ module EpicsHelper
todo_exists: todo.present?,
todo_path: group_todos_path(group),
start_date: epic.start_date,
due_date: epic.due_date,
end_date: epic.end_date
}
epic_meta[:todo_delete_path] = dashboard_todo_path(todo) if todo.present?
if Ability.allowed?(current_user, :update_epic, epic.group)
epic_meta.merge!(
start_date_fixed: epic.start_date_fixed,
start_date_is_fixed: epic.start_date_is_fixed?,
start_date_fixed: epic.start_date_fixed,
start_date_from_milestones: epic.start_date_from_milestones,
start_date_sourcing_milestone_title: epic.start_date_sourcing_milestone&.title,
due_date_fixed: epic.due_date_fixed,
due_date: epic.due_date,
due_date_is_fixed: epic.due_date_is_fixed?,
due_date_fixed: epic.due_date_fixed,
due_date_from_milestones: epic.due_date_from_milestones,
due_date_sourcing_milestone_title: epic.due_date_sourcing_milestone&.title
)
end
due_date_sourcing_milestone_title: epic.due_date_sourcing_milestone&.title,
end_date: epic.end_date
}
epic_meta[:todo_delete_path] = dashboard_todo_path(todo) if todo.present?
participants = UserSerializer.new.represent(epic.participants)
initial = opts[:initial].merge(labels: epic.labels,
......
......@@ -5,31 +5,7 @@ describe EpicsHelper do
describe '#epic_show_app_data' do
let(:user) { create(:user) }
let!(:epic) { create(:epic, author: user) }
before do
allow(helper).to receive(:current_user).and_return(user)
stub_licensed_features(epics: true)
end
it 'returns the correct json' do
data = helper.epic_show_app_data(epic, initial: {}, author_icon: 'icon_path')
meta_data = JSON.parse(data[:meta])
expected_keys = %i(initial meta namespace labels_path toggle_subscription_path labels_web_url epics_web_url)
expect(data.keys).to match_array(expected_keys)
expect(meta_data.keys).to match_array(%w[created author start_date end_date due_date epic_id todo_exists todo_path])
expect(meta_data['author']).to eq({
'name' => user.name,
'url' => "/#{user.username}",
'username' => "@#{user.username}",
'src' => 'icon_path'
})
end
context 'when user has edit permission' do
let(:milestone) { create(:milestone, title: 'make me a sandwich') }
let!(:epic) do
create(
:epic,
......@@ -42,25 +18,33 @@ describe EpicsHelper do
end
before do
epic.group.add_developer(user)
allow(helper).to receive(:current_user).and_return(user)
stub_licensed_features(epics: true)
end
it 'returns extra date fields if user can edit' do
it 'returns the correct json' do
data = helper.epic_show_app_data(epic, initial: {}, author_icon: 'icon_path')
meta_data = JSON.parse(data[:meta])
expected_keys = %i(initial meta namespace labels_path toggle_subscription_path labels_web_url epics_web_url)
expect(data.keys).to match_array(expected_keys)
expect(meta_data.keys).to match_array(%w[
created author epic_id todo_exists todo_path
start_date start_date_fixed start_date_is_fixed start_date_from_milestones start_date_sourcing_milestone_title
end_date due_date due_date_fixed due_date_is_fixed due_date_from_milestones due_date_sourcing_milestone_title
])
expect(meta_data['author']).to eq({
'name' => user.name,
'url' => "/#{user.username}",
'username' => "@#{user.username}",
'src' => 'icon_path'
})
expect(meta_data['start_date']).to eq('2000-01-01')
expect(meta_data['start_date_sourcing_milestone_title']).to eq(milestone.title)
expect(meta_data['due_date']).to eq('2000-01-02')
expect(meta_data['due_date_sourcing_milestone_title']).to eq(milestone.title)
end
end
end
describe '#epic_endpoint_query_params' do
it 'it includes epic specific options in JSON format' do
......
......@@ -34,6 +34,10 @@ describe('epicHeader', () => {
);
});
it('should render sidebar toggle button', () => {
expect(vm.$el.querySelector('button.js-sidebar-toggle')).not.toBe(null);
});
describe('canDelete', () => {
it('should not show loading button by default', () => {
expect(vm.$el.querySelector('.btn-remove')).toBeNull();
......
......@@ -177,6 +177,14 @@ describe('SidebarParticipants', () => {
expect(vm.$emit).toHaveBeenCalledWith('toggleDateType', true);
});
});
describe('toggleSidebar', () => {
it('emits `toggleCollapse` event on component', () => {
spyOn(vm, '$emit');
vm.toggleSidebar();
expect(vm.$emit).toHaveBeenCalledWith('toggleCollapse');
});
});
});
describe('template', () => {
......@@ -188,6 +196,16 @@ describe('SidebarParticipants', () => {
expect(vm.$el.querySelector('.sidebar-collapsed-icon')).not.toBe(null);
});
it('renders collapse button when `showToggleSidebar` prop is `true`', done => {
vm.showToggleSidebar = true;
Vue.nextTick()
.then(() => {
expect(vm.$el.querySelector('button.btn-sidebar-action')).not.toBe(null);
})
.then(done)
.catch(done.fail);
});
it('renders title element', () => {
expect(vm.$el.querySelector('.title')).not.toBe(null);
});
......
......@@ -9173,6 +9173,9 @@ msgstr ""
msgid "to help your contributors communicate effectively!"
msgstr ""
msgid "toggle collapse"
msgstr ""
msgid "username"
msgstr ""
......
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