Commit 40e29c4b authored by Martin Wortschack's avatar Martin Wortschack

Add empty state screen

- Display an empty state screen for code review analytics
parent f6e49d16
---
title: Empty state for Code Review Analytics
merge_request: 25793
author:
type: added
...@@ -5,7 +5,7 @@ import FilteredSearchCodeReviewAnalytics from './filtered_search_code_review_ana ...@@ -5,7 +5,7 @@ import FilteredSearchCodeReviewAnalytics from './filtered_search_code_review_ana
export default () => { export default () => {
const container = document.getElementById('js-code-review-analytics'); const container = document.getElementById('js-code-review-analytics');
const { projectId } = container.dataset; const { projectId, newMergeRequestUrl, emptyStateSvgPath } = container.dataset;
if (!container) return; if (!container) return;
...@@ -21,6 +21,8 @@ export default () => { ...@@ -21,6 +21,8 @@ export default () => {
return h(CodeAnalyticsApp, { return h(CodeAnalyticsApp, {
props: { props: {
projectId: Number(projectId), projectId: Number(projectId),
newMergeRequestUrl,
emptyStateSvgPath,
}, },
}); });
}, },
......
<script> <script>
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { GlBadge, GlLoadingIcon, GlPagination } from '@gitlab/ui'; import { GlBadge, GlLoadingIcon, GlEmptyState, GlPagination } from '@gitlab/ui';
import MergeRequestTable from './merge_request_table.vue'; import MergeRequestTable from './merge_request_table.vue';
export default { export default {
...@@ -8,6 +8,7 @@ export default { ...@@ -8,6 +8,7 @@ export default {
GlBadge, GlBadge,
GlLoadingIcon, GlLoadingIcon,
GlPagination, GlPagination,
GlEmptyState,
MergeRequestTable, MergeRequestTable,
}, },
props: { props: {
...@@ -15,6 +16,14 @@ export default { ...@@ -15,6 +16,14 @@ export default {
type: Number, type: Number,
required: true, required: true,
}, },
newMergeRequestUrl: {
type: String,
required: true,
},
emptyStateSvgPath: {
type: String,
required: true,
},
}, },
computed: { computed: {
...mapState({ ...mapState({
...@@ -23,7 +32,6 @@ export default { ...@@ -23,7 +32,6 @@ export default {
totalItems: state => state.pageInfo.total, totalItems: state => state.pageInfo.total,
page: state => state.pageInfo.page, page: state => state.pageInfo.page,
}), }),
...mapGetters(['showMrCount']),
currentPage: { currentPage: {
get() { get() {
return this.page; return this.page;
...@@ -46,20 +54,42 @@ export default { ...@@ -46,20 +54,42 @@ export default {
<template> <template>
<div class="mt-2"> <div class="mt-2">
<div>
<span class="font-weight-bold">{{ __('Merge Requests in Review') }}</span>
<gl-badge v-show="showMrCount" pill>{{ totalItems }}</gl-badge>
</div>
<gl-loading-icon v-show="isLoading" size="md" class="mt-3" /> <gl-loading-icon v-show="isLoading" size="md" class="mt-3" />
<template v-if="!isLoading"> <template v-if="!isLoading">
<merge-request-table /> <gl-empty-state
<gl-pagination v-if="!totalItems"
v-model="currentPage" :title="__(`You don't have any open merge requests`)"
:per-page="perPage" :primary-button-text="__('New merge request')"
:total-items="totalItems" :primary-button-link="newMergeRequestUrl"
align="center" :svg-path="emptyStateSvgPath"
class="w-100" class="container-message"
/> >
<template #description>
<div class="text-center">
<p>
{{
__(
'Code Review Analytics displays a table of open merge requests considered to be in code review. There are currently no merge requests in review for this project and/or filters.',
)
}}
</p>
</div>
</template>
</gl-empty-state>
<template v-else>
<div>
<span class="font-weight-bold">{{ __('Merge Requests in Review') }}</span>
<gl-badge pill>{{ totalItems }}</gl-badge>
</div>
<merge-request-table />
<gl-pagination
v-model="currentPage"
:per-page="perPage"
:total-items="totalItems"
align="center"
class="w-100"
/>
</template>
</template> </template>
</div> </div>
</template> </template>
// eslint-disable-next-line import/prefer-default-export
export const showMrCount = state => !state.isLoading && !state.errorCode;
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import * as actions from './actions'; import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations'; import mutations from './mutations';
import state from './state'; import state from './state';
...@@ -11,7 +10,6 @@ const createStore = () => ...@@ -11,7 +10,6 @@ const createStore = () =>
new Vuex.Store({ new Vuex.Store({
state: state(), state: state(),
actions, actions,
getters,
mutations, mutations,
}); });
......
...@@ -6,4 +6,4 @@ ...@@ -6,4 +6,4 @@
= _('Code Review') = _('Code Review')
%span.text-secondary= _('Review time is defined as the time it takes from first comment until merged.') %span.text-secondary= _('Review time is defined as the time it takes from first comment until merged.')
= render 'shared/issuable/search_bar', type: :issues_analytics, show_sorting_dropdown: false = render 'shared/issuable/search_bar', type: :issues_analytics, show_sorting_dropdown: false
#js-code-review-analytics{ data: { project_id: @project.id } } #js-code-review-analytics{ data: { project_id: @project.id, new_merge_request_url: namespace_project_new_merge_request_path(@project.namespace), empty_state_svg_path: image_path('illustrations/merge_requests.svg') } }
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { GlLoadingIcon, GlBadge, GlPagination } from '@gitlab/ui'; import { GlLoadingIcon, GlEmptyState, GlBadge, GlPagination } from '@gitlab/ui';
import CodeReviewAnalyticsApp from 'ee/analytics/code_review_analytics/components/app.vue'; import CodeReviewAnalyticsApp from 'ee/analytics/code_review_analytics/components/app.vue';
import MergeRequestTable from 'ee/analytics/code_review_analytics/components/merge_request_table.vue'; import MergeRequestTable from 'ee/analytics/code_review_analytics/components/merge_request_table.vue';
import createState from 'ee/analytics/code_review_analytics/store/state'; import createState from 'ee/analytics/code_review_analytics/store/state';
...@@ -44,6 +44,8 @@ describe('CodeReviewAnalyticsApp component', () => { ...@@ -44,6 +44,8 @@ describe('CodeReviewAnalyticsApp component', () => {
store, store,
propsData: { propsData: {
projectId: 1, projectId: 1,
newMergeRequestUrl: 'new_merge_request',
emptyStateSvgPath: 'svg',
}, },
}); });
...@@ -56,6 +58,7 @@ describe('CodeReviewAnalyticsApp component', () => { ...@@ -56,6 +58,7 @@ describe('CodeReviewAnalyticsApp component', () => {
wrapper.destroy(); wrapper.destroy();
}); });
const findEmptyState = () => wrapper.find(GlEmptyState);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon); const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findBadge = () => wrapper.find(GlBadge); const findBadge = () => wrapper.find(GlBadge);
const findMrTable = () => wrapper.find(MergeRequestTable); const findMrTable = () => wrapper.find(MergeRequestTable);
...@@ -73,7 +76,7 @@ describe('CodeReviewAnalyticsApp component', () => { ...@@ -73,7 +76,7 @@ describe('CodeReviewAnalyticsApp component', () => {
}); });
it('should not show the badge containing the MR count', () => { it('should not show the badge containing the MR count', () => {
expect(findBadge().isVisible()).toBe(false); expect(findBadge().exists()).toBe(false);
}); });
it('should not render the merge requests table', () => { it('should not render the merge requests table', () => {
...@@ -86,26 +89,62 @@ describe('CodeReviewAnalyticsApp component', () => { ...@@ -86,26 +89,62 @@ describe('CodeReviewAnalyticsApp component', () => {
}); });
describe('when finished loading', () => { describe('when finished loading', () => {
beforeEach(() => { describe('and there are no merge requests', () => {
vuexStore = createStore({ isLoading: false, pageInfo }, { showMrCount: () => true }); beforeEach(() => {
wrapper = createComponent(vuexStore); vuexStore = createStore(
{ isLoading: false, pageInfo: { page: 0, perPage: 0, total: 0 } },
{ showMrCount: () => true },
);
wrapper = createComponent(vuexStore);
});
it('should hide the loading indicator', () => {
expect(findLoadingIcon().isVisible()).toBe(false);
});
it('should show the empty state screen', () => {
expect(findEmptyState().exists()).toBe(true);
});
it('should not show the badge containing the MR count', () => {
expect(findBadge().exists()).toBe(false);
});
it('should not render the merge requests table', () => {
expect(findMrTable().exists()).toBe(false);
});
it('should not render the pagination', () => {
expect(findPagination().exists()).toBe(false);
});
}); });
it('should hide the loading indicator', () => { describe('and there are merge requests', () => {
expect(findLoadingIcon().isVisible()).toBe(false); beforeEach(() => {
}); vuexStore = createStore({ isLoading: false, pageInfo }, { showMrCount: () => true });
wrapper = createComponent(vuexStore);
});
it('should show the badge containing the MR count', () => { it('should hide the loading indicator', () => {
expect(findBadge().isVisible()).toBe(true); expect(findLoadingIcon().isVisible()).toBe(false);
expect(findBadge().text()).toEqual(`${50}`); });
});
it('should render the merge requests table', () => { it('should show the badge containing the MR count', () => {
expect(findMrTable().exists()).toBe(true); expect(findBadge().exists()).toBe(true);
}); expect(findBadge().text()).toBe('50');
});
it('should not render the empty state screen', () => {
expect(findEmptyState().exists()).toBe(false);
});
it('should render the merge requests table', () => {
expect(findMrTable().exists()).toBe(true);
});
it('should render the pagination', () => { it('should render the pagination', () => {
expect(findPagination().exists()).toBe(true); expect(findPagination().exists()).toBe(true);
});
}); });
}); });
}); });
......
import createState from 'ee/analytics/code_review_analytics/store/state';
import * as getters from 'ee/analytics/code_review_analytics/store/getters';
describe('Code review analytics getteers', () => {
let state;
beforeEach(() => {
state = createState();
});
describe('showMrCount', () => {
it('returns false when is loading', () => {
state = { isLoading: true, errorCode: null };
expect(getters.showMrCount(state)).toBe(false);
});
it('returns true when not loading and no error', () => {
state = { isLoading: false, errorCode: null };
expect(getters.showMrCount(state)).toBe(true);
});
});
});
...@@ -4820,6 +4820,9 @@ msgstr "" ...@@ -4820,6 +4820,9 @@ msgstr ""
msgid "Code Review" msgid "Code Review"
msgstr "" msgstr ""
msgid "Code Review Analytics displays a table of open merge requests considered to be in code review. There are currently no merge requests in review for this project and/or filters."
msgstr ""
msgid "Code owner approval is required" msgid "Code owner approval is required"
msgstr "" msgstr ""
...@@ -22474,6 +22477,9 @@ msgstr "" ...@@ -22474,6 +22477,9 @@ msgstr ""
msgid "You don't have any deployments right now." msgid "You don't have any deployments right now."
msgstr "" msgstr ""
msgid "You don't have any open merge requests"
msgstr ""
msgid "You don't have any projects available." msgid "You don't have any projects available."
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