Commit 389d54da authored by Phil Hughes's avatar Phil Hughes

Merge branch '7327-add-roadmap-within-epic' into 'master'

Add Roadmap to Epic page

See merge request gitlab-org/gitlab-ee!10488
parents a220e0a6 8b506324
...@@ -595,7 +595,6 @@ ...@@ -595,7 +595,6 @@
color: $gl-text-color; color: $gl-text-color;
} }
.git-merge-container { .git-merge-container {
justify-content: space-between; justify-content: space-between;
flex: 1; flex: 1;
...@@ -805,7 +804,8 @@ ...@@ -805,7 +804,8 @@
} }
} }
.merge-request-tabs-holder { .merge-request-tabs-holder,
.epic-tabs-holder {
top: $header-height; top: $header-height;
z-index: 250; z-index: 250;
background-color: $white-light; background-color: $white-light;
...@@ -823,11 +823,6 @@ ...@@ -823,11 +823,6 @@
@include media-breakpoint-down(xs) { @include media-breakpoint-down(xs) {
right: 0; right: 0;
} }
.merge-request-tabs-container {
padding-left: $gl-padding;
padding-right: $gl-padding;
}
} }
.nav-links { .nav-links {
...@@ -835,11 +830,21 @@ ...@@ -835,11 +830,21 @@
} }
} }
.with-performance-bar .merge-request-tabs-holder { .merge-request-tabs-holder.affix .merge-request-tabs-container,
.epic-tabs-holder.affix .epic-tabs-container {
padding-left: $gl-padding;
padding-right: $gl-padding;
}
.with-performance-bar {
.merge-request-tabs-holder,
.epic-tabs-holder {
top: $header-height + $performance-bar-height; top: $header-height + $performance-bar-height;
}
} }
.merge-request-tabs { .merge-request-tabs,
.epic-tabs {
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
margin-bottom: 0; margin-bottom: 0;
...@@ -847,7 +852,8 @@ ...@@ -847,7 +852,8 @@
} }
.limit-container-width { .limit-container-width {
.merge-request-tabs-container { .merge-request-tabs-container,
.epic-tabs-container {
max-width: $limited-layout-width; max-width: $limited-layout-width;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
...@@ -860,7 +866,8 @@ ...@@ -860,7 +866,8 @@
} }
} }
.merge-request-tabs-container { .merge-request-tabs-container,
.epic-tabs-container {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
...@@ -878,11 +885,10 @@ ...@@ -878,11 +885,10 @@
} }
.limit-container-width:not(.container-limited) { .limit-container-width:not(.container-limited) {
.merge-request-tabs-holder:not(.affix) { .merge-request-tabs-holder:not(.affix) .merge-request-tabs-container,
.merge-request-tabs-container { .epic-tabs-holder:not(.affix) .epic-tabs-container {
max-width: $limited-layout-width - ($gl-padding * 2); max-width: $limited-layout-width - ($gl-padding * 2);
} }
}
} }
.mr-memory-usage { .mr-memory-usage {
......
import $ from 'jquery';
import initRoadmap from 'ee/roadmap/index';
export default class EpicTabs {
constructor() {
this.wrapper = document.querySelector('.content-wrapper .container-fluid:not(.breadcrumbs)');
this.epicTabs = this.wrapper.querySelector('.js-epic-tabs-container');
this.discussionFilterContainer = this.epicTabs.querySelector('.js-discussion-filter-container');
this.roadmapTabLoaded = false;
this.bindEvents();
}
bindEvents() {
const $roadmapTab = $('#roadmap-tab', this.epicTabs);
$roadmapTab.on('show.bs.tab', this.onRoadmapShow.bind(this));
$roadmapTab.on('hide.bs.tab', this.onRoadmapHide.bind(this));
}
onRoadmapShow() {
this.wrapper.classList.remove('container-limited');
this.discussionFilterContainer.classList.add('hidden');
if (!this.roadmapTabLoaded) {
initRoadmap();
this.roadmapTabLoaded = true;
}
}
onRoadmapHide() {
this.wrapper.classList.add('container-limited');
this.discussionFilterContainer.classList.remove('hidden');
}
}
import ZenMode from '~/zen_mode'; import ZenMode from '~/zen_mode';
import ShortcutsEpic from 'ee/behaviors/shortcuts/shortcuts_epic'; import ShortcutsEpic from 'ee/behaviors/shortcuts/shortcuts_epic';
import EpicTabs from 'ee/epic/epic_tabs';
import initEpicApp from 'ee/epic/epic_bundle'; import initEpicApp from 'ee/epic/epic_bundle';
import '~/notes/index'; import '~/notes/index';
...@@ -7,4 +8,5 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -7,4 +8,5 @@ document.addEventListener('DOMContentLoaded', () => {
new ZenMode(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new
initEpicApp(); initEpicApp();
new ShortcutsEpic(); // eslint-disable-line no-new new ShortcutsEpic(); // eslint-disable-line no-new
new EpicTabs(); // eslint-disable-line no-new
}); });
...@@ -46,6 +46,7 @@ export default { ...@@ -46,6 +46,7 @@ export default {
'epicsFetchForTimeframeInProgress', 'epicsFetchForTimeframeInProgress',
'epicsFetchResultEmpty', 'epicsFetchResultEmpty',
'epicsFetchFailure', 'epicsFetchFailure',
'isChildEpics',
]), ]),
timeframeStart() { timeframeStart() {
return this.timeframe[0]; return this.timeframe[0];
...@@ -171,6 +172,7 @@ export default { ...@@ -171,6 +172,7 @@ export default {
:has-filters-applied="hasFiltersApplied" :has-filters-applied="hasFiltersApplied"
:new-epic-endpoint="newEpicEndpoint" :new-epic-endpoint="newEpicEndpoint"
:empty-state-illustration-path="emptyStateIllustrationPath" :empty-state-illustration-path="emptyStateIllustrationPath"
:is-child-epics="isChildEpics"
/> />
</div> </div>
</template> </template>
...@@ -32,6 +32,11 @@ export default { ...@@ -32,6 +32,11 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
isChildEpics: {
type: Boolean,
required: false,
default: false,
},
}, },
computed: { computed: {
timeframeRange() { timeframeRange() {
...@@ -78,6 +83,20 @@ export default { ...@@ -78,6 +83,20 @@ export default {
return s__('GroupRoadmap|The roadmap shows the progress of your epics along a timeline'); return s__('GroupRoadmap|The roadmap shows the progress of your epics along a timeline');
}, },
subMessage() { subMessage() {
if (this.isChildEpics) {
return sprintf(
s__(
'GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}.',
),
{
linkStart:
'<a href="https://docs.gitlab.com/ee/user/group/epics/#multi-level-child-epics" target="_blank" rel="noopener noreferrer nofollow">',
linkEnd: '</a>',
},
false,
);
}
if (this.hasFiltersApplied) { if (this.hasFiltersApplied) {
return sprintf(emptyStateWithFilters, { return sprintf(emptyStateWithFilters, {
startDate: this.timeframeRange.startDate, startDate: this.timeframeRange.startDate,
......
<script> <script>
import { mapState } from 'vuex';
import { GlSkeletonLoading } from '@gitlab/ui'; import { GlSkeletonLoading } from '@gitlab/ui';
import { isInViewport } from '~/lib/utils/common_utils'; import { isInViewport } from '~/lib/utils/common_utils';
...@@ -41,6 +42,7 @@ export default { ...@@ -41,6 +42,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(['defaultInnerHeight']),
containerStyles() { containerStyles() {
return { return {
width: `${this.shellWidth}px`, width: `${this.shellWidth}px`,
...@@ -73,7 +75,7 @@ export default { ...@@ -73,7 +75,7 @@ export default {
// before setting shellWidth // before setting shellWidth
// see https://vuejs.org/v2/api/#Vue-nextTick // see https://vuejs.org/v2/api/#Vue-nextTick
if (this.$el.parentElement) { if (this.$el.parentElement) {
this.shellHeight = window.innerHeight - this.$el.offsetTop; this.shellHeight = (this.defaultInnerHeight || window.innerHeight) - this.$el.offsetTop;
this.noScroll = this.shellHeight > EPIC_ITEM_HEIGHT * (this.epics.length + 1); this.noScroll = this.shellHeight > EPIC_ITEM_HEIGHT * (this.epics.length + 1);
this.shellWidth = this.getShellWidth(this.noScroll); this.shellWidth = this.getShellWidth(this.noScroll);
......
...@@ -81,7 +81,7 @@ export default { ...@@ -81,7 +81,7 @@ export default {
}, },
handleEpicsListScroll() { handleEpicsListScroll() {
const indicatorX = this.$el.getBoundingClientRect().x; const indicatorX = this.$el.getBoundingClientRect().x;
const rootOffsetLeft = this.$root.$el.offsetLeft; const rootOffsetLeft = this.$root.$el.parentElement.offsetLeft;
// 3px to compensate size of bubble on top of Indicator // 3px to compensate size of bubble on top of Indicator
this.todayBarReady = indicatorX - rootOffsetLeft >= EPIC_DETAILS_CELL_WIDTH + 3; this.todayBarReady = indicatorX - rootOffsetLeft >= EPIC_DETAILS_CELL_WIDTH + 3;
......
...@@ -63,6 +63,8 @@ export default () => { ...@@ -63,6 +63,8 @@ export default () => {
return { return {
emptyStateIllustrationPath: dataset.emptyStateIllustration, emptyStateIllustrationPath: dataset.emptyStateIllustration,
hasFiltersApplied: parseBoolean(dataset.hasFiltersApplied), hasFiltersApplied: parseBoolean(dataset.hasFiltersApplied),
defaultInnerHeight: Number(dataset.innerHeight),
isChildEpics: parseBoolean(dataset.childEpics),
currentGroupId: parseInt(dataset.groupId, 0), currentGroupId: parseInt(dataset.groupId, 0),
newEpicEndpoint: dataset.newEpicEndpoint, newEpicEndpoint: dataset.newEpicEndpoint,
epicsState: dataset.epicsState, epicsState: dataset.epicsState,
...@@ -83,6 +85,8 @@ export default () => { ...@@ -83,6 +85,8 @@ export default () => {
basePath: this.basePath, basePath: this.basePath,
filterQueryString: this.filterQueryString, filterQueryString: this.filterQueryString,
initialEpicsPath: this.initialEpicsPath, initialEpicsPath: this.initialEpicsPath,
defaultInnerHeight: this.defaultInnerHeight,
isChildEpics: this.isChildEpics,
}); });
}, },
methods: { methods: {
......
...@@ -15,6 +15,8 @@ export default () => ({ ...@@ -15,6 +15,8 @@ export default () => ({
sortedBy: '', sortedBy: '',
// UI Flags // UI Flags
defaultInnerHeight: 0,
isChildEpics: false,
epicsFetchInProgress: false, epicsFetchInProgress: false,
epicsFetchForTimeframeInProgress: false, epicsFetchForTimeframeInProgress: false,
epicsFetchFailure: false, epicsFetchFailure: false,
......
...@@ -399,9 +399,11 @@ export const getEpicsPathForPreset = ({ ...@@ -399,9 +399,11 @@ export const getEpicsPathForPreset = ({
end.setDate(end.getDate() + 6); end.setDate(end.getDate() + 6);
} }
epicsPath += epicsPath.indexOf('?') === -1 ? '?' : '&';
const startDate = `${start.getFullYear()}-${start.getMonth() + 1}-${start.getDate()}`; const startDate = `${start.getFullYear()}-${start.getMonth() + 1}-${start.getDate()}`;
const endDate = `${end.getFullYear()}-${end.getMonth() + 1}-${end.getDate()}`; const endDate = `${end.getFullYear()}-${end.getMonth() + 1}-${end.getDate()}`;
epicsPath += `?state=${epicsState}&start_date=${startDate}&end_date=${endDate}`; epicsPath += `state=${epicsState}&start_date=${startDate}&end_date=${endDate}`;
if (filterQueryString) { if (filterQueryString) {
epicsPath += `&${filterQueryString}`; epicsPath += `&${filterQueryString}`;
......
...@@ -43,6 +43,21 @@ ...@@ -43,6 +43,21 @@
} }
} }
.epic-tabs-content {
.roadmap-container {
min-height: 600px;
.roadmap-shell {
border: 1px solid $white-dark;
border-top: 0;
}
.empty-state {
margin-top: 0;
}
}
}
@include media-breakpoint-down(xs) { @include media-breakpoint-down(xs) {
.epic-page-container { .epic-page-container {
.detail-page-header { .detail-page-header {
......
...@@ -53,3 +53,10 @@ ...@@ -53,3 +53,10 @@
.add-issuable-form-actions { .add-issuable-form-actions {
margin-top: $gl-padding; margin-top: $gl-padding;
} }
.limit-container-width {
.epic-page-container .issuable-details,
.emoji-list-container {
@include fixed-width-container;
}
}
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
class EpicsFinder < IssuableFinder class EpicsFinder < IssuableFinder
def self.scalar_params def self.scalar_params
@scalar_params ||= %i[ @scalar_params ||= %i[
parent_id
author_id author_id
author_username author_username
label_name label_name
......
...@@ -4,4 +4,8 @@ module RoadmapsHelper ...@@ -4,4 +4,8 @@ module RoadmapsHelper
def roadmap_layout def roadmap_layout
(current_user&.roadmap_layout || params[:layout].presence || EE::User::DEFAULT_ROADMAP_LAYOUT).upcase (current_user&.roadmap_layout || params[:layout].presence || EE::User::DEFAULT_ROADMAP_LAYOUT).upcase
end end
def roadmap_sort_order
current_user&.user_preference&.roadmaps_sort || sort_value_start_date_soon
end
end end
...@@ -13,12 +13,42 @@ ...@@ -13,12 +13,42 @@
#epic-app-root{ data: epic_show_app_data(@epic, author_icon: avatar_icon_for_user(@epic.author), initial: issuable_initial_data(@epic)) } #epic-app-root{ data: epic_show_app_data(@epic, author_icon: avatar_icon_for_user(@epic.author), initial: issuable_initial_data(@epic)) }
.content-block.emoji-block .content-block.content-block-small.emoji-list-container.js-noteable-awards
.row
.col-sm-6.col-md-8.js-noteable-awards
= render 'award_emoji/awards_block', awardable: @epic, inline: true = render 'award_emoji/awards_block', awardable: @epic, inline: true
.col-sm-6.col-md-4.d-flex.justify-content-sm-end.align-items-md-top.mt-2.mt-sm-1
#js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@epic), notes_filters: UserPreference.notes_filters.to_json } }
%section.issuable-discussion .epic-tabs-holder
.epic-tabs-container.js-epic-tabs-container
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
%ul.epic-tabs.nav-tabs.nav.nav-links.scrolling-tabs
%li.notes-tab.qa-notes-tab
%a#discussion-tab.active{ href: '#discussion', data: { toggle: 'tab' } }
= _('Discussion')
%li.roadmap-tab
%a#roadmap-tab{ href: '#roadmap', data: { toggle: 'tab' } }
= _('Roadmap')
.d-inline-flex.flex-wrap
#js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@epic),
notes_filters: UserPreference.notes_filters.to_json } }
.tab-content.epic-tabs-content.js-epic-tabs-content
#discussion.tab-pane.show.active
.row
%section.col-md-12
%script.js-notes-data{ type: "application/json" }= initial_notes_data(true).to_json.html_safe
.issuable-discussion
= render 'discussion' = render 'discussion'
#roadmap.tab-pane
.row
%section.col-md-12
#js-roadmap{ data: { epics_path: group_epics_path(@group, parent_id: @epic.id, format: :json),
group_id: @group.id,
empty_state_illustration: image_path('illustrations/epics/roadmap.svg'),
has_filters_applied: 'false',
new_epic_endpoint: group_epics_path(@group),
preset_type: roadmap_layout,
epics_state: 'all',
sorted_by: roadmap_sort_order,
inner_height: '600',
child_epics: 'true' } }
---
title: Add Roadmap to Epic page
merge_request: 10488
author:
type: added
...@@ -148,6 +148,21 @@ describe Groups::EpicsController do ...@@ -148,6 +148,21 @@ describe Groups::EpicsController do
expect(item['web_url']).to eq(group_epic_path(group, epic)) expect(item['web_url']).to eq(group_epic_path(group, epic))
end end
context 'with parent_id filter' do
let(:parent_epic) { create(:epic, group: group) }
it 'returns child epics of the given parent' do
child_epics = create_list(:epic, 2, group: group, parent: parent_epic)
# descendant epic that should not be included
create(:epic, group: group, parent: child_epics.first)
get :index, params: { group_id: group, parent_id: parent_epic.id }, format: :json
expect(json_response.size).to eq(2)
expect(json_response.map { |e| e['id'] }).to match_array(child_epics.map(&:id))
end
end
context 'using label_name filter' do context 'using label_name filter' do
let(:label) { create(:label) } let(:label) { create(:label) }
let!(:labeled_epic) { create(:labeled_epic, group: group, labels: [label]) } let!(:labeled_epic) { create(:labeled_epic, group: group, labels: [label]) }
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Epic show', :js do
let(:user) { create(:user, name: 'Rick Sanchez', username: 'rick.sanchez') }
let(:group) { create(:group, :public) }
let(:label) { create(:group_label, group: group, title: 'bug') }
let(:note_text) { 'Contemnit enim disserendi elegantiam.' }
let(:epic_title) { 'Sample epic' }
let(:markdown) do
<<-MARKDOWN.strip_heredoc
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nos commodius agimus.
Ex rebus enim timiditas, non ex vocabulis nascitur.
Ita prorsus, inquam; Duo Reges: constructio interrete.
MARKDOWN
end
let(:epic) { create(:epic, group: group, title: epic_title, description: markdown, author: user) }
let!(:child_epic_a) { create(:epic, group: group, title: 'Child epic A', description: markdown, parent: epic, start_date: 50.days.ago, end_date: 10.days.ago) }
let!(:child_epic_b) { create(:epic, group: group, title: 'Child epic B', description: markdown, parent: epic, start_date: 100.days.ago, end_date: 20.days.ago) }
before do
group.add_developer(user)
stub_licensed_features(epics: true)
sign_in(user)
visit group_epic_path(group, epic)
end
describe 'Epic metadata' do
it 'shows epic status, date and author in header' do
page.within('.epic-page-container .detail-page-header-body') do
expect(find('.issuable-status-box > span')).to have_content('Open')
expect(find('.issuable-meta')).to have_content('Opened just now by')
expect(find('.issuable-meta .js-user-avatar-link-username')).to have_content('Rick Sanchez')
end
end
it 'shows epic title and description' do
page.within('.epic-page-container .detail-page-description') do
expect(find('.title-container .title')).to have_content(epic_title)
expect(find('.description .md')).to have_content(markdown)
end
end
it 'shows epic tabs' do
page.within('.js-epic-tabs-container') do
expect(find('.epic-tabs #discussion-tab')).to have_content('Discussion')
expect(find('.epic-tabs #roadmap-tab')).to have_content('Roadmap')
end
end
it 'shows epic discussion filter dropdown' do
page.within('.js-epic-tabs-container') do
expect(find('.js-discussion-filter-container #discussion-filter-dropdown')).to have_content('Show all activity')
end
end
end
describe 'Epic child epics' do
it 'shows child epics list' do
page.within('.js-related-epics-block') do
expect(find('.issue-count-badge-count')).to have_content('2')
expect(find('.js-related-issues-token-list-item:nth-child(1) .sortable-link')).to have_content('Child epic B')
expect(find('.js-related-issues-token-list-item:nth-child(2) .sortable-link')).to have_content('Child epic A')
end
end
end
describe 'Roadmap tab' do
before do
find('.js-epic-tabs-container #roadmap-tab').click
wait_for_requests
end
it 'shows Roadmap timeline with child epics' do
page.within('.js-epic-tabs-content #roadmap') do
expect(page).to have_selector('.roadmap-container .roadmap-shell')
page.within('.roadmap-shell .epics-list-section') do
expect(find('.epics-list-item:nth-child(1) .epic-title a')).to have_content('Child epic B')
expect(find('.epics-list-item:nth-child(2) .epic-title a')).to have_content('Child epic A')
end
end
end
it 'does not show discussion filter dropdown' do
expect(find('.js-epic-tabs-container')).to have_selector('.js-discussion-filter-container', visible: false)
end
it 'has no limit on container width' do
expect(find('.content-wrapper .container-fluid:not(.breadcrumbs)')[:class]).not_to include('container-limited')
end
end
end
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
require 'spec_helper' require 'spec_helper'
describe RoadmapsHelper do describe RoadmapsHelper do
describe '#roadmap_layout' do
before do before do
allow(helper).to receive(:current_user) { user } allow(helper).to receive(:current_user) { user }
end end
describe '#roadmap_layout' do
context 'guest' do context 'guest' do
let(:user) { nil } let(:user) { nil }
...@@ -34,4 +34,40 @@ describe RoadmapsHelper do ...@@ -34,4 +34,40 @@ describe RoadmapsHelper do
end end
end end
end end
describe '#roadmap_sort_order' do
let(:user_preference) { double(:user_preference) }
before do
allow(user).to receive(:user_preference).and_return(user_preference)
end
context 'guest' do
let(:user) { nil }
it 'returns default sort order' do
expect(helper.roadmap_sort_order).to eq('start_date_asc')
end
end
context 'user without preferences set' do
let(:user) { double(:user) }
it 'returns default sort order' do
expect(user_preference).to receive(:roadmaps_sort).and_return(nil)
expect(helper.roadmap_sort_order).to eq('start_date_asc')
end
end
context 'user with preference set' do
let(:user) { double(:user) }
it 'returns saved user preference' do
expect(user_preference).to receive(:roadmaps_sort).and_return('end_date_asc')
expect(helper.roadmap_sort_order).to eq('end_date_asc')
end
end
end
end end
...@@ -157,6 +157,20 @@ describe('EpicsListEmptyComponent', () => { ...@@ -157,6 +157,20 @@ describe('EpicsListEmptyComponent', () => {
.catch(done.fail); .catch(done.fail);
}); });
}); });
describe('with child epics context', () => {
it('returns empty state sub-message when `isChildEpics` is set to `true`', done => {
vm.isChildEpics = true;
Vue.nextTick()
.then(() => {
expect(vm.subMessage).toBe(
'To view the roadmap, add a start or due date to one of the <a href="https://docs.gitlab.com/ee/user/group/epics/#multi-level-child-epics" target="_blank" rel="noopener noreferrer nofollow">child epics</a>.',
);
})
.then(done)
.catch(done.fail);
});
});
}); });
describe('timeframeRange', () => { describe('timeframeRange', () => {
......
import Vue from 'vue'; import Vue from 'vue';
import roadmapShellComponent from 'ee/roadmap/components/roadmap_shell.vue'; import roadmapShellComponent from 'ee/roadmap/components/roadmap_shell.vue';
import createStore from 'ee/roadmap/store';
import eventHub from 'ee/roadmap/event_hub'; import eventHub from 'ee/roadmap/event_hub';
import { getTimeframeForMonthsView } from 'ee/roadmap/utils/roadmap_utils'; import { getTimeframeForMonthsView } from 'ee/roadmap/utils/roadmap_utils';
import { PRESET_TYPES } from 'ee/roadmap/constants'; import { PRESET_TYPES } from 'ee/roadmap/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { mockEpic, mockTimeframeInitialDate, mockGroupId } from '../mock_data'; import { mockEpic, mockTimeframeInitialDate, mockGroupId } from '../mock_data';
const mockTimeframeMonths = getTimeframeForMonthsView(mockTimeframeInitialDate); const mockTimeframeMonths = getTimeframeForMonthsView(mockTimeframeInitialDate);
const createComponent = ( const createComponent = (
{ epics = [mockEpic], timeframe = mockTimeframeMonths, currentGroupId = mockGroupId }, {
epics = [mockEpic],
timeframe = mockTimeframeMonths,
currentGroupId = mockGroupId,
defaultInnerHeight = 0,
},
el, el,
) => { ) => {
const Component = Vue.extend(roadmapShellComponent); const Component = Vue.extend(roadmapShellComponent);
return mountComponent( const store = createStore();
Component, store.dispatch('setInitialData', {
{ defaultInnerHeight,
});
return mountComponentWithStore(Component, {
el,
store,
props: {
presetType: PRESET_TYPES.MONTHS, presetType: PRESET_TYPES.MONTHS,
epics, epics,
timeframe, timeframe,
currentGroupId, currentGroupId,
}, },
el, });
);
}; };
describe('RoadmapShellComponent', () => { describe('RoadmapShellComponent', () => {
......
...@@ -22,6 +22,7 @@ describe('Roadmap Store Mutations', () => { ...@@ -22,6 +22,7 @@ describe('Roadmap Store Mutations', () => {
currentGroupId: mockGroupId, currentGroupId: mockGroupId,
sortedBy: mockSortedBy, sortedBy: mockSortedBy,
initialEpicsPath: epicsPath, initialEpicsPath: epicsPath,
defaultInnerHeight: 600,
extendedTimeframe: [], extendedTimeframe: [],
filterQueryString: '', filterQueryString: '',
epicsState: 'all', epicsState: 'all',
......
...@@ -5346,6 +5346,9 @@ msgstr "" ...@@ -5346,6 +5346,9 @@ msgstr ""
msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline" msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timeline"
msgstr "" msgstr ""
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of the %{linkStart}child epics%{linkEnd}."
msgstr ""
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}." msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
msgstr "" 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