Commit 14ad4429 authored by Kushal Pandya's avatar Kushal Pandya

Show correct copy on view based on `hasFiltersApplied` prop, add actions

parent c94dc098
...@@ -26,6 +26,10 @@ ...@@ -26,6 +26,10 @@
type: Boolean, type: Boolean,
required: true, required: true,
}, },
newEpicEndpoint: {
type: String,
required: true,
},
emptyStateIllustrationPath: { emptyStateIllustrationPath: {
type: String, type: String,
required: true, required: true,
...@@ -113,7 +117,10 @@ ...@@ -113,7 +117,10 @@
</script> </script>
<template> <template>
<div class="roadmap-container"> <div
class="roadmap-container"
:class="{ 'overflow-reset': isEpicsListEmpty }"
>
<loading-icon <loading-icon
class="loading-animation prepend-top-20 append-bottom-20" class="loading-animation prepend-top-20 append-bottom-20"
size="2" size="2"
...@@ -131,6 +138,7 @@ ...@@ -131,6 +138,7 @@
:timeframe-start="timeframeStart" :timeframe-start="timeframeStart"
:timeframe-end="timeframeEnd" :timeframe-end="timeframeEnd"
:has-filters-applied="hasFiltersApplied" :has-filters-applied="hasFiltersApplied"
:new-epic-endpoint="newEpicEndpoint"
:empty-state-illustration-path="emptyStateIllustrationPath" :empty-state-illustration-path="emptyStateIllustrationPath"
/> />
</div> </div>
......
...@@ -2,7 +2,12 @@ ...@@ -2,7 +2,12 @@
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import { dateInWords } from '~/lib/utils/datetime_utility'; import { dateInWords } from '~/lib/utils/datetime_utility';
import NewEpic from '../../epics/new_epic/components/new_epic.vue';
export default { export default {
components: {
NewEpic,
},
props: { props: {
timeframeStart: { timeframeStart: {
type: Date, type: Date,
...@@ -12,6 +17,14 @@ ...@@ -12,6 +17,14 @@
type: Date, type: Date,
required: true, required: true,
}, },
hasFiltersApplied: {
type: Boolean,
required: true,
},
newEpicEndpoint: {
type: String,
required: true,
},
emptyStateIllustrationPath: { emptyStateIllustrationPath: {
type: String, type: String,
required: true, required: true,
...@@ -32,9 +45,18 @@ ...@@ -32,9 +45,18 @@
}; };
}, },
message() { message() {
return s__('GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort'); if (this.hasFiltersApplied) {
return s__('GroupRoadmap|Sorry, no epics matched your search');
}
return s__('GroupRoadmap|The roadmap shows the progress of your epics along a timeline');
}, },
subMessage() { subMessage() {
if (this.hasFiltersApplied) {
return sprintf(s__('GroupRoadmap|To widen your search, change or remove filters. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}.'), {
startDate: this.timeframeRange.startDate,
endDate: this.timeframeRange.endDate,
});
}
return sprintf(s__('GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}.'), { return sprintf(s__('GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from %{startDate} to %{endDate}.'), {
startDate: this.timeframeRange.startDate, startDate: this.timeframeRange.startDate,
endDate: this.timeframeRange.endDate, endDate: this.timeframeRange.endDate,
...@@ -57,6 +79,17 @@ ...@@ -57,6 +79,17 @@
<div class="text-content"> <div class="text-content">
<h4>{{ message }}</h4> <h4>{{ message }}</h4>
<p v-html="subMessage"></p> <p v-html="subMessage"></p>
<new-epic
v-if="!hasFiltersApplied"
:endpoint="newEpicEndpoint"
/>
<a
class="btn btn-default"
:title="__('List')"
:href="newEpicEndpoint"
>
<span>{{ s__('View epics list') }}</span>
</a>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -52,6 +52,7 @@ export default () => { ...@@ -52,6 +52,7 @@ export default () => {
store, store,
service, service,
hasFiltersApplied, hasFiltersApplied,
newEpicEndpoint: dataset.newEpicEndpoint,
emptyStateIllustrationPath: dataset.emptyStateIllustration, emptyStateIllustrationPath: dataset.emptyStateIllustration,
}; };
}, },
...@@ -61,6 +62,7 @@ export default () => { ...@@ -61,6 +62,7 @@ export default () => {
store: this.store, store: this.store,
service: this.service, service: this.service,
hasFiltersApplied: this.hasFiltersApplied, hasFiltersApplied: this.hasFiltersApplied,
newEpicEndpoint: this.newEpicEndpoint,
emptyStateIllustrationPath: this.emptyStateIllustrationPath, emptyStateIllustrationPath: this.emptyStateIllustrationPath,
}, },
}); });
......
...@@ -20,6 +20,10 @@ $column-right-gradient: linear-gradient(to right, rgba(0, 0, 0, 0.15) 0%, rgba(2 ...@@ -20,6 +20,10 @@ $column-right-gradient: linear-gradient(to right, rgba(0, 0, 0, 0.15) 0%, rgba(2
.roadmap-container { .roadmap-container {
overflow: hidden; overflow: hidden;
&.overflow-reset {
overflow: initial;
}
} }
.roadmap-shell { .roadmap-shell {
......
...@@ -9,6 +9,6 @@ ...@@ -9,6 +9,6 @@
- if @epics_count != 0 - if @epics_count != 0
= render 'shared/epic/search_bar', type: :epics, hide_sort_dropdown: true = render 'shared/epic/search_bar', type: :epics, hide_sort_dropdown: true
#js-roadmap{ data: { epics_path: group_epics_path(@group, format: :json), group_id: @group.id, empty_state_illustration: image_path('illustrations/epics/roadmap.svg'), has_filters_applied: "#{has_filters_applied}" } } #js-roadmap{ data: { epics_path: group_epics_path(@group, format: :json), group_id: @group.id, empty_state_illustration: image_path('illustrations/epics/roadmap.svg'), has_filters_applied: "#{has_filters_applied}", new_epic_endpoint: group_epics_path(@group) } }
- else - else
= render 'shared/empty_states/roadmap' = render 'shared/empty_states/roadmap'
...@@ -8,7 +8,7 @@ import RoadmapStore from 'ee/roadmap/store/roadmap_store'; ...@@ -8,7 +8,7 @@ import RoadmapStore from 'ee/roadmap/store/roadmap_store';
import RoadmapService from 'ee/roadmap/service/roadmap_service'; import RoadmapService from 'ee/roadmap/service/roadmap_service';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframe, mockGroupId, epicsPath, rawEpics, mockSvgPath } from '../mock_data'; import { mockTimeframe, mockGroupId, epicsPath, mockNewEpicEndpoint, rawEpics, mockSvgPath } from '../mock_data';
const createComponent = () => { const createComponent = () => {
const Component = Vue.extend(appComponent); const Component = Vue.extend(appComponent);
...@@ -21,6 +21,7 @@ const createComponent = () => { ...@@ -21,6 +21,7 @@ const createComponent = () => {
store, store,
service, service,
hasFiltersApplied: true, hasFiltersApplied: true,
newEpicEndpoint: mockNewEpicEndpoint,
emptyStateIllustrationPath: mockSvgPath, emptyStateIllustrationPath: mockSvgPath,
}); });
}; };
...@@ -180,5 +181,16 @@ describe('AppComponent', () => { ...@@ -180,5 +181,16 @@ describe('AppComponent', () => {
it('renders roadmap container with class `roadmap-container`', () => { it('renders roadmap container with class `roadmap-container`', () => {
expect(vm.$el.classList.contains('roadmap-container')).toBe(true); expect(vm.$el.classList.contains('roadmap-container')).toBe(true);
}); });
it('renders roadmap container with classes `roadmap-container overflow-reset` when isEpicsListEmpty prop is true', (done) => {
vm.isEpicsListEmpty = true;
Vue.nextTick()
.then(() => {
expect(vm.$el.classList.contains('roadmap-container')).toBe(true);
expect(vm.$el.classList.contains('overflow-reset')).toBe(true);
})
.then(done)
.catch(done.fail);
});
}); });
}); });
...@@ -3,15 +3,17 @@ import Vue from 'vue'; ...@@ -3,15 +3,17 @@ import Vue from 'vue';
import epicsListEmptyComponent from 'ee/roadmap/components/epics_list_empty.vue'; import epicsListEmptyComponent from 'ee/roadmap/components/epics_list_empty.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframe, mockSvgPath } from '../mock_data'; import { mockTimeframe, mockSvgPath, mockNewEpicEndpoint } from '../mock_data';
const createComponent = () => { const createComponent = (hasFiltersApplied = false) => {
const Component = Vue.extend(epicsListEmptyComponent); const Component = Vue.extend(epicsListEmptyComponent);
return mountComponent(Component, { return mountComponent(Component, {
timeframeStart: mockTimeframe[0], timeframeStart: mockTimeframe[0],
timeframeEnd: mockTimeframe[mockTimeframe.length - 1], timeframeEnd: mockTimeframe[mockTimeframe.length - 1],
emptyStateIllustrationPath: mockSvgPath, emptyStateIllustrationPath: mockSvgPath,
newEpicEndpoint: mockNewEpicEndpoint,
hasFiltersApplied,
}); });
}; };
...@@ -28,15 +30,35 @@ describe('EpicsListEmptyComponent', () => { ...@@ -28,15 +30,35 @@ describe('EpicsListEmptyComponent', () => {
describe('computed', () => { describe('computed', () => {
describe('message', () => { describe('message', () => {
it('returns correct empty state message', () => { it('returns default empty state message', () => {
expect(vm.message).toBe('Epics let you manage your portfolio of projects more efficiently and with less effort'); expect(vm.message).toBe('The roadmap shows the progress of your epics along a timeline');
});
it('returns empty state message when `hasFiltersApplied` prop is true', (done) => {
vm.hasFiltersApplied = true;
Vue.nextTick()
.then(() => {
expect(vm.message).toBe('Sorry, no epics matched your search');
})
.then(done)
.catch(done.fail);
}); });
}); });
describe('subMessage', () => { describe('subMessage', () => {
it('returns correct empty state sub-message', () => { it('returns default empty state sub-message', () => {
expect(vm.subMessage).toBe('To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from Nov 1, 2017 to Apr 30, 2018.'); expect(vm.subMessage).toBe('To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown &ndash; from Nov 1, 2017 to Apr 30, 2018.');
}); });
it('returns empty state sub-message when `hasFiltersApplied` prop is true', done => {
vm.hasFiltersApplied = true;
Vue.nextTick()
.then(() => {
expect(vm.subMessage).toBe('To widen your search, change or remove filters. Only epics in the past 3 months and the next 3 months are shown &ndash; from Nov 1, 2017 to Apr 30, 2018.');
})
.then(done)
.catch(done.fail);
});
}); });
describe('timeframeRange', () => { describe('timeframeRange', () => {
...@@ -51,5 +73,30 @@ describe('EpicsListEmptyComponent', () => { ...@@ -51,5 +73,30 @@ describe('EpicsListEmptyComponent', () => {
it('renders empty state illustration in image element with provided `emptyStateIllustrationPath`', () => { it('renders empty state illustration in image element with provided `emptyStateIllustrationPath`', () => {
expect(vm.$el.querySelector('.svg-content img').getAttribute('src')).toBe(vm.emptyStateIllustrationPath); expect(vm.$el.querySelector('.svg-content img').getAttribute('src')).toBe(vm.emptyStateIllustrationPath);
}); });
it('renders new epic button element', () => {
const newEpicBtnEl = vm.$el.querySelector('.new-epic-dropdown');
expect(newEpicBtnEl).not.toBeNull();
expect(newEpicBtnEl.querySelector('button.btn-new').innerText.trim()).toBe('New epic');
});
it('does not render new epic button element when `hasFiltersApplied` prop is true', done => {
vm.hasFiltersApplied = true;
Vue.nextTick()
.then(() => {
expect(vm.$el.querySelector('.new-epic-dropdown')).toBeNull();
})
.then(done)
.catch(done.fail);
});
it('renders view epics list link element', () => {
const viewEpicsListEl = vm.$el.querySelector('a.btn');
expect(viewEpicsListEl).not.toBeNull();
expect(viewEpicsListEl.getAttribute('href')).toBe(mockNewEpicEndpoint);
expect(viewEpicsListEl.querySelector('span').innerText.trim()).toBe('View epics list');
});
}); });
}); });
...@@ -11,6 +11,8 @@ export const mockItemWidth = 180; ...@@ -11,6 +11,8 @@ export const mockItemWidth = 180;
export const epicsPath = '/groups/gitlab-org/-/epics.json?start_date=2017-11-1&end_date=2018-4-30'; export const epicsPath = '/groups/gitlab-org/-/epics.json?start_date=2017-11-1&end_date=2018-4-30';
export const mockNewEpicEndpoint = '/groups/gitlab-org/-/epics';
export const mockSvgPath = '/foo/bar.svg'; export const mockSvgPath = '/foo/bar.svg';
export const mockTimeframe = getTimeframeWindow(TIMEFRAME_LENGTH, new Date(2018, 1, 1)); export const mockTimeframe = getTimeframeWindow(TIMEFRAME_LENGTH, new Date(2018, 1, 1));
......
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