Commit 011d95ed authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch 'ss/add-empty-state-iterations' into 'master'

When no iterations are present show empty state

See merge request gitlab-org/gitlab!79067
parents a582a43f 52aeccae
<script>
import { GlAlert, GlButton, GlLoadingIcon, GlPagination, GlTab, GlTabs } from '@gitlab/ui';
import { __ } from '~/locale';
import emptyStateSvg from '@gitlab/svgs/dist/illustrations/issues.svg';
import {
GlAlert,
GlButton,
GlLoadingIcon,
GlPagination,
GlTab,
GlTabs,
GlEmptyState,
} from '@gitlab/ui';
import { __, s__ } from '~/locale';
import { Namespace } from '../constants';
import IterationsQuery from '../queries/iterations.query.graphql';
import IterationsList from './iterations_list.vue';
......@@ -8,6 +17,14 @@ import IterationsList from './iterations_list.vue';
const pageSize = 20;
export default {
i18n: {
emptyStateDescription: s__(
'Iterations|Iterations are a way to track issues over a period of time, allowing teams to also track velocity and volatility metrics.',
),
newIteration: s__('Iterations|New iteration'),
noIterationsFound: s__('Iterations|No iterations found'),
},
emptySvgPath: `data:image/svg+xml;utf8,${encodeURIComponent(emptyStateSvg)}`,
components: {
IterationsList,
GlAlert,
......@@ -16,6 +33,7 @@ export default {
GlPagination,
GlTab,
GlTabs,
GlEmptyState,
},
props: {
fullPath: {
......@@ -113,6 +131,9 @@ export default {
nextPage() {
return Number(this.namespace.pageInfo.hasNextPage);
},
showEmptyState() {
return this.iterations.length === 0 && !this.loading;
},
},
methods: {
handlePageChange(page) {
......@@ -151,6 +172,15 @@ export default {
{{ error }}
</gl-alert>
</div>
<gl-empty-state
v-else-if="showEmptyState"
:svg-path="$options.emptySvgPath"
:title="$options.i18n.noIterationsFound"
:primary-button-text="$options.i18n.newIteration"
:primary-button-link="newIterationPath"
:description="$options.i18n.emptyStateDescription"
/>
<div v-else>
<iterations-list :iterations="iterations" :namespace-type="namespaceType" />
<gl-pagination
......
......@@ -48,7 +48,7 @@ export const iterationSelectTextMap = {
iterationSelectFail: __('Failed to set iteration on this issue. Please try again.'),
currentIterationFetchError: __('Failed to fetch the iteration for this issue. Please try again.'),
iterationsFetchError: __('Failed to fetch the iterations for the group. Please try again.'),
noIterationsFound: __('No iterations found'),
noIterationsFound: s__('Iterations|No iterations found'),
};
export const noIteration = null;
......
- page_title _("Iterations")
- iterations_path = @project&.group ? new_group_iteration_path(@project&.group) : ''
.js-iterations-list{ data: { full_path: @project.full_path } }
.js-iterations-list{ data: { full_path: @project.full_path, new_iteration_path: iterations_path } }
......@@ -92,7 +92,7 @@ RSpec.describe 'User views iteration' do
wait_for_requests
expect(page).to have_content('No iterations to show')
expect(page).to have_content('No iterations found')
expect(page).not_to have_content(iteration.title)
end
end
......
import { GlAlert, GlLoadingIcon, GlPagination, GlTab, GlTabs } from '@gitlab/ui';
import { GlAlert, GlEmptyState, GlLoadingIcon, GlPagination, GlTab, GlTabs } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import Iterations from 'ee/iterations/components/iterations.vue';
import IterationsList from 'ee/iterations/components/iterations_list.vue';
import { Namespace } from 'ee/iterations/constants';
import query from 'ee/iterations/queries/iterations.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { mockGroupIterations, mockGroupIterationsEmpty } from '../mock_data';
describe('Iterations', () => {
let wrapper;
let mockApollo;
const defaultProps = {
fullPath: 'gitlab-org',
};
const mountComponent = ({ props = defaultProps, loading = false } = {}) => {
const mountComponent = ({
props = defaultProps,
queryResponse = mockGroupIterations,
queryHandler = jest.fn().mockResolvedValue(queryResponse),
} = {}) => {
Vue.use(VueApollo);
mockApollo = createMockApollo([[query, queryHandler]]);
wrapper = shallowMount(Iterations, {
apolloProvider: mockApollo,
propsData: props,
mocks: {
$apollo: {
queries: { namespace: { loading } },
},
},
stubs: {
GlLoadingIcon,
GlTab,
......@@ -29,23 +39,22 @@ describe('Iterations', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('hides list while loading', () => {
mountComponent({
loading: true,
});
mountComponent();
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBeTruthy();
expect(wrapper.findComponent(IterationsList).exists()).toBeFalsy();
});
it('shows iterations list when not loading', () => {
it('shows iterations list after loading', async () => {
mountComponent({
loading: false,
props: { ...defaultProps, newIterationPath: 'iterations' },
});
await waitForPromises();
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBeFalsy();
expect(wrapper.findComponent(IterationsList).exists()).toBeTruthy();
});
......@@ -64,6 +73,26 @@ describe('Iterations', () => {
expect(wrapper.vm.state).toEqual('all');
});
describe('when loading is false and iterations are empty', () => {
beforeEach(async () => {
mountComponent({
props: {
...defaultProps,
newIterationPath: 'iterations',
},
queryResponse: mockGroupIterationsEmpty,
});
await waitForPromises();
});
it('renders GlEmptyState with the correct props', () => {
expect(wrapper.findComponent(GlEmptyState).props()).toEqual(
expect.objectContaining({ primaryButtonLink: 'iterations' }),
);
});
});
describe('pagination', () => {
const findPagination = () => wrapper.findComponent(GlPagination);
const setPage = async (page) => {
......@@ -71,22 +100,12 @@ describe('Iterations', () => {
await nextTick();
};
beforeEach(() => {
beforeEach(async () => {
mountComponent({
loading: false,
});
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
namespace: {
pageInfo: {
hasNextPage: true,
hasPreviousPage: false,
startCursor: 'first-item',
endCursor: 'last-item',
},
},
queryResponse: mockGroupIterations,
});
await waitForPromises();
});
it('passes prev, next, and current page props', () => {
......@@ -184,19 +203,22 @@ describe('Iterations', () => {
});
describe('error', () => {
beforeEach(() => {
beforeEach(async () => {
mountComponent({
loading: false,
});
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
error: 'Oh no!',
queryHandler: jest.fn().mockRejectedValue({
data: {
group: {
errors: ['oh no'],
},
},
}),
});
await waitForPromises();
});
it('tab shows error in alert', () => {
expect(wrapper.findComponent(GlAlert).text()).toContain('Oh no!');
expect(wrapper.findComponent(GlAlert).text()).toContain('Error loading iterations');
});
});
});
......@@ -10,6 +10,7 @@ export const mockIterationNode = {
state: iterationStates.upcoming,
title: 'top-level-iteration',
webPath: '/groups/top-level-group/-/iterations/4',
scopedPath: '/groups/top-level-group/-/iterations/4',
__typename: 'Iteration',
};
......@@ -29,6 +30,33 @@ export const mockGroupIterations = {
id: 'gid://gitlab/Group/114',
iterations: {
nodes: [mockIterationNode],
pageInfo: {
hasNextPage: true,
hasPreviousPage: true,
startCursor: 'first-item',
endCursor: 'last-item',
__typename: 'PageInfo',
},
__typename: 'IterationConnection',
},
__typename: 'Group',
},
},
};
export const mockGroupIterationsEmpty = {
data: {
group: {
id: 'gid://gitlab/Group/114',
iterations: {
nodes: [],
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: '',
endCursor: '',
__typename: 'PageInfo',
},
__typename: 'IterationConnection',
},
__typename: 'Group',
......
......@@ -21000,6 +21000,9 @@ msgstr ""
msgid "Iterations|Iteration scheduling will be handled automatically"
msgstr ""
msgid "Iterations|Iterations are a way to track issues over a period of time, allowing teams to also track velocity and volatility metrics."
msgstr ""
msgid "Iterations|Move incomplete issues to the next iteration"
msgstr ""
......@@ -21015,6 +21018,9 @@ msgstr ""
msgid "Iterations|No iteration cadences to show."
msgstr ""
msgid "Iterations|No iterations found"
msgstr ""
msgid "Iterations|No iterations in cadence."
msgstr ""
......@@ -25166,9 +25172,6 @@ msgstr ""
msgid "No iteration"
msgstr ""
msgid "No iterations found"
msgstr ""
msgid "No iterations to show"
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