Commit ceffcc4c authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '205596-empty-state-for-code-review-analytics' into 'master'

Resolve "Empty state for Code Review Analytics"

Closes #205596

See merge request gitlab-org/gitlab!25793
parents 0ddeea1d 40e29c4b
---
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,12 +54,33 @@ export default { ...@@ -46,12 +54,33 @@ export default {
<template> <template>
<div class="mt-2"> <div class="mt-2">
<gl-loading-icon v-show="isLoading" size="md" class="mt-3" />
<template v-if="!isLoading">
<gl-empty-state
v-if="!totalItems"
:title="__(`You don't have any open merge requests`)"
:primary-button-text="__('New merge request')"
:primary-button-link="newMergeRequestUrl"
:svg-path="emptyStateSvgPath"
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> <div>
<span class="font-weight-bold">{{ __('Merge Requests in Review') }}</span> <span class="font-weight-bold">{{ __('Merge Requests in Review') }}</span>
<gl-badge v-show="showMrCount" pill>{{ totalItems }}</gl-badge> <gl-badge pill>{{ totalItems }}</gl-badge>
</div> </div>
<gl-loading-icon v-show="isLoading" size="md" class="mt-3" />
<template v-if="!isLoading">
<merge-request-table /> <merge-request-table />
<gl-pagination <gl-pagination
v-model="currentPage" v-model="currentPage"
...@@ -61,5 +90,6 @@ export default { ...@@ -61,5 +90,6 @@ export default {
class="w-100" 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,6 +89,37 @@ describe('CodeReviewAnalyticsApp component', () => { ...@@ -86,6 +89,37 @@ describe('CodeReviewAnalyticsApp component', () => {
}); });
describe('when finished loading', () => { describe('when finished loading', () => {
describe('and there are no merge requests', () => {
beforeEach(() => {
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);
});
});
describe('and there are merge requests', () => {
beforeEach(() => { beforeEach(() => {
vuexStore = createStore({ isLoading: false, pageInfo }, { showMrCount: () => true }); vuexStore = createStore({ isLoading: false, pageInfo }, { showMrCount: () => true });
wrapper = createComponent(vuexStore); wrapper = createComponent(vuexStore);
...@@ -96,8 +130,12 @@ describe('CodeReviewAnalyticsApp component', () => { ...@@ -96,8 +130,12 @@ describe('CodeReviewAnalyticsApp component', () => {
}); });
it('should show the badge containing the MR count', () => { it('should show the badge containing the MR count', () => {
expect(findBadge().isVisible()).toBe(true); expect(findBadge().exists()).toBe(true);
expect(findBadge().text()).toEqual(`${50}`); 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', () => { it('should render the merge requests table', () => {
...@@ -109,6 +147,7 @@ describe('CodeReviewAnalyticsApp component', () => { ...@@ -109,6 +147,7 @@ describe('CodeReviewAnalyticsApp component', () => {
}); });
}); });
}); });
});
describe('changing the page', () => { describe('changing the page', () => {
beforeEach(() => { beforeEach(() => {
......
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 ""
...@@ -22468,6 +22471,9 @@ msgstr "" ...@@ -22468,6 +22471,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