Commit ce62970d authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '220182-skeleton-loading' into 'master'

Add gl-skeleton-loading to cluster list vue

See merge request gitlab-org/gitlab!34090
parents 42109dd9 290ea7d2
...@@ -6,6 +6,7 @@ import { ...@@ -6,6 +6,7 @@ import {
GlLink, GlLink,
GlLoadingIcon, GlLoadingIcon,
GlPagination, GlPagination,
GlSkeletonLoading,
GlSprintf, GlSprintf,
GlTable, GlTable,
} from '@gitlab/ui'; } from '@gitlab/ui';
...@@ -21,6 +22,7 @@ export default { ...@@ -21,6 +22,7 @@ export default {
GlLink, GlLink,
GlLoadingIcon, GlLoadingIcon,
GlPagination, GlPagination,
GlSkeletonLoading,
GlSprintf, GlSprintf,
GlTable, GlTable,
}, },
...@@ -28,7 +30,18 @@ export default { ...@@ -28,7 +30,18 @@ export default {
tooltip, tooltip,
}, },
computed: { computed: {
...mapState(['clusters', 'clustersPerPage', 'loading', 'page', 'providers', 'totalCulsters']), ...mapState([
'clusters',
'clustersPerPage',
'loadingClusters',
'loadingNodes',
'page',
'providers',
'totalCulsters',
]),
contentAlignClasses() {
return 'gl-display-flex gl-align-items-center gl-justify-content-end gl-justify-content-md-start';
},
currentPage: { currentPage: {
get() { get() {
return this.page; return this.page;
...@@ -180,14 +193,12 @@ export default { ...@@ -180,14 +193,12 @@ export default {
</script> </script>
<template> <template>
<gl-loading-icon v-if="loading" size="md" class="mt-3" /> <gl-loading-icon v-if="loadingClusters" size="md" class="gl-mt-3" />
<section v-else> <section v-else>
<gl-table :items="clusters" :fields="fields" stacked="md" class="qa-clusters-table"> <gl-table :items="clusters" :fields="fields" stacked="md" class="qa-clusters-table">
<template #cell(name)="{ item }"> <template #cell(name)="{ item }">
<div <div :class="[contentAlignClasses, 'js-status']">
class="gl-display-flex gl-align-items-center gl-justify-content-end gl-justify-content-md-start js-status"
>
<img <img
:src="selectedProvider(item.provider_type).path" :src="selectedProvider(item.provider_type).path"
:alt="selectedProvider(item.provider_type).text" :alt="selectedProvider(item.provider_type).text"
...@@ -214,6 +225,9 @@ export default { ...@@ -214,6 +225,9 @@ export default {
<template #cell(node_size)="{ item }"> <template #cell(node_size)="{ item }">
<span v-if="item.nodes">{{ item.nodes.length }}</span> <span v-if="item.nodes">{{ item.nodes.length }}</span>
<gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" />
<small v-else class="gl-font-sm gl-font-style-italic gl-text-gray-400">{{ <small v-else class="gl-font-sm gl-font-style-italic gl-text-gray-400">{{
__('Unknown') __('Unknown')
}}</small> }}</small>
...@@ -231,6 +245,8 @@ export default { ...@@ -231,6 +245,8 @@ export default {
> >
</gl-sprintf> </gl-sprintf>
</span> </span>
<gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" />
</template> </template>
<template #cell(total_memory)="{ item }"> <template #cell(total_memory)="{ item }">
...@@ -245,6 +261,8 @@ export default { ...@@ -245,6 +261,8 @@ export default {
> >
</gl-sprintf> </gl-sprintf>
</span> </span>
<gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" />
</template> </template>
<template #cell(cluster_type)="{value}"> <template #cell(cluster_type)="{value}">
......
...@@ -19,6 +19,8 @@ const allNodesPresent = (clusters, retryCount) => { ...@@ -19,6 +19,8 @@ const allNodesPresent = (clusters, retryCount) => {
export const fetchClusters = ({ state, commit }) => { export const fetchClusters = ({ state, commit }) => {
let retryCount = 0; let retryCount = 0;
commit(types.SET_LOADING_NODES, true);
const poll = new Poll({ const poll = new Poll({
resource: { resource: {
fetchClusters: paginatedEndPoint => axios.get(paginatedEndPoint), fetchClusters: paginatedEndPoint => axios.get(paginatedEndPoint),
...@@ -34,15 +36,19 @@ export const fetchClusters = ({ state, commit }) => { ...@@ -34,15 +36,19 @@ export const fetchClusters = ({ state, commit }) => {
const paginationInformation = parseIntPagination(normalizedHeaders); const paginationInformation = parseIntPagination(normalizedHeaders);
commit(types.SET_CLUSTERS_DATA, { data, paginationInformation }); commit(types.SET_CLUSTERS_DATA, { data, paginationInformation });
commit(types.SET_LOADING_STATE, false); commit(types.SET_LOADING_CLUSTERS, false);
if (allNodesPresent(data.clusters, retryCount)) { if (allNodesPresent(data.clusters, retryCount)) {
poll.stop(); poll.stop();
commit(types.SET_LOADING_NODES, false);
} }
} }
} catch (error) { } catch (error) {
poll.stop(); poll.stop();
commit(types.SET_LOADING_CLUSTERS, false);
commit(types.SET_LOADING_NODES, false);
Sentry.withScope(scope => { Sentry.withScope(scope => {
scope.setTag('javascript_clusters_list', 'fetchClustersSuccessCallback'); scope.setTag('javascript_clusters_list', 'fetchClustersSuccessCallback');
Sentry.captureException(error); Sentry.captureException(error);
...@@ -52,7 +58,8 @@ export const fetchClusters = ({ state, commit }) => { ...@@ -52,7 +58,8 @@ export const fetchClusters = ({ state, commit }) => {
errorCallback: response => { errorCallback: response => {
poll.stop(); poll.stop();
commit(types.SET_LOADING_STATE, false); commit(types.SET_LOADING_CLUSTERS, false);
commit(types.SET_LOADING_NODES, false);
flash(__('Clusters|An error occurred while loading clusters')); flash(__('Clusters|An error occurred while loading clusters'));
Sentry.withScope(scope => { Sentry.withScope(scope => {
......
export const SET_CLUSTERS_DATA = 'SET_CLUSTERS_DATA'; export const SET_CLUSTERS_DATA = 'SET_CLUSTERS_DATA';
export const SET_LOADING_STATE = 'SET_LOADING_STATE'; export const SET_LOADING_CLUSTERS = 'SET_LOADING_CLUSTERS';
export const SET_LOADING_NODES = 'SET_LOADING_NODES';
export const SET_PAGE = 'SET_PAGE'; export const SET_PAGE = 'SET_PAGE';
import * as types from './mutation_types'; import * as types from './mutation_types';
export default { export default {
[types.SET_LOADING_STATE](state, value) { [types.SET_LOADING_CLUSTERS](state, value) {
state.loading = value; state.loadingClusters = value;
},
[types.SET_LOADING_NODES](state, value) {
state.loadingNodes = value;
}, },
[types.SET_CLUSTERS_DATA](state, { data, paginationInformation }) { [types.SET_CLUSTERS_DATA](state, { data, paginationInformation }) {
Object.assign(state, { Object.assign(state, {
......
export default (initialState = {}) => ({ export default (initialState = {}) => ({
endpoint: initialState.endpoint, endpoint: initialState.endpoint,
hasAncestorClusters: false, hasAncestorClusters: false,
loading: true,
clusters: [], clusters: [],
clustersPerPage: 0, clustersPerPage: 0,
loadingClusters: true,
loadingNodes: true,
page: 1, page: 1,
providers: { providers: {
aws: { path: initialState.imgTagsAwsPath, text: initialState.imgTagsAwsText }, aws: { path: initialState.imgTagsAwsPath, text: initialState.imgTagsAwsText },
......
---
title: Add skeleton loader to cluster list
merge_request: 34090
author:
type: changed
...@@ -4,7 +4,7 @@ import ClusterStore from '~/clusters_list/store'; ...@@ -4,7 +4,7 @@ import ClusterStore from '~/clusters_list/store';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { apiData } from '../mock_data'; import { apiData } from '../mock_data';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { GlLoadingIcon, GlTable, GlPagination } from '@gitlab/ui'; import { GlLoadingIcon, GlPagination, GlSkeletonLoading, GlTable } from '@gitlab/ui';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
describe('Clusters', () => { describe('Clusters', () => {
...@@ -64,7 +64,7 @@ describe('Clusters', () => { ...@@ -64,7 +64,7 @@ describe('Clusters', () => {
describe('clusters table', () => { describe('clusters table', () => {
describe('when data is loading', () => { describe('when data is loading', () => {
beforeEach(() => { beforeEach(() => {
wrapper.vm.$store.state.loading = true; wrapper.vm.$store.state.loadingClusters = true;
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
}); });
...@@ -131,19 +131,48 @@ describe('Clusters', () => { ...@@ -131,19 +131,48 @@ describe('Clusters', () => {
}); });
describe('nodes present', () => { describe('nodes present', () => {
it.each` describe('nodes while loading', () => {
nodeSize | lineNumber it.each`
${'Unknown'} | ${0} nodeSize | lineNumber
${'1'} | ${1} ${null} | ${0}
${'2'} | ${2} ${'1'} | ${1}
${'1'} | ${3} ${'2'} | ${2}
${'1'} | ${4} ${'1'} | ${3}
${'Unknown'} | ${5} ${'1'} | ${4}
`('renders node size for each cluster', ({ nodeSize, lineNumber }) => { ${null} | ${5}
const sizes = findTable().findAll('td:nth-child(3)'); `('renders node size for each cluster', ({ nodeSize, lineNumber }) => {
const size = sizes.at(lineNumber); const sizes = findTable().findAll('td:nth-child(3)');
const size = sizes.at(lineNumber);
expect(size.text()).toBe(nodeSize);
if (nodeSize) {
expect(size.text()).toBe(nodeSize);
} else {
expect(size.find(GlSkeletonLoading).exists()).toBe(true);
}
});
});
describe('nodes finish loading', () => {
beforeEach(() => {
wrapper.vm.$store.state.loadingNodes = false;
return wrapper.vm.$nextTick();
});
it.each`
nodeSize | lineNumber
${'Unknown'} | ${0}
${'1'} | ${1}
${'2'} | ${2}
${'1'} | ${3}
${'1'} | ${4}
${'Unknown'} | ${5}
`('renders node size for each cluster', ({ nodeSize, lineNumber }) => {
const sizes = findTable().findAll('td:nth-child(3)');
const size = sizes.at(lineNumber);
expect(size.text()).toBe(nodeSize);
expect(size.find(GlSkeletonLoading).exists()).toBe(false);
});
}); });
describe('nodes with unknown quantity', () => { describe('nodes with unknown quantity', () => {
......
...@@ -48,8 +48,9 @@ describe('Clusters store actions', () => { ...@@ -48,8 +48,9 @@ describe('Clusters store actions', () => {
{ endpoint: apiData.endpoint }, { endpoint: apiData.endpoint },
{}, {},
[ [
{ type: types.SET_LOADING_NODES, payload: true },
{ type: types.SET_CLUSTERS_DATA, payload: { data: apiData, paginationInformation } }, { type: types.SET_CLUSTERS_DATA, payload: { data: apiData, paginationInformation } },
{ type: types.SET_LOADING_STATE, payload: false }, { type: types.SET_LOADING_CLUSTERS, payload: false },
], ],
[], [],
() => done(), () => done(),
...@@ -63,7 +64,11 @@ describe('Clusters store actions', () => { ...@@ -63,7 +64,11 @@ describe('Clusters store actions', () => {
actions.fetchClusters, actions.fetchClusters,
{ endpoint: apiData.endpoint }, { endpoint: apiData.endpoint },
{}, {},
[{ type: types.SET_LOADING_STATE, payload: false }], [
{ type: types.SET_LOADING_NODES, payload: true },
{ type: types.SET_LOADING_CLUSTERS, payload: false },
{ type: types.SET_LOADING_NODES, payload: false },
],
[], [],
() => { () => {
expect(flashError).toHaveBeenCalledWith(expect.stringMatching('error')); expect(flashError).toHaveBeenCalledWith(expect.stringMatching('error'));
...@@ -100,8 +105,9 @@ describe('Clusters store actions', () => { ...@@ -100,8 +105,9 @@ describe('Clusters store actions', () => {
{ endpoint: apiData.endpoint }, { endpoint: apiData.endpoint },
{}, {},
[ [
{ type: types.SET_LOADING_NODES, payload: true },
{ type: types.SET_CLUSTERS_DATA, payload: { data: apiData, paginationInformation } }, { type: types.SET_CLUSTERS_DATA, payload: { data: apiData, paginationInformation } },
{ type: types.SET_LOADING_STATE, payload: false }, { type: types.SET_LOADING_CLUSTERS, payload: false },
], ],
[], [],
() => { () => {
...@@ -149,11 +155,14 @@ describe('Clusters store actions', () => { ...@@ -149,11 +155,14 @@ describe('Clusters store actions', () => {
{ endpoint: apiData.endpoint }, { endpoint: apiData.endpoint },
{}, {},
[ [
{ type: types.SET_LOADING_NODES, payload: true },
{ {
type: types.SET_CLUSTERS_DATA, type: types.SET_CLUSTERS_DATA,
payload: { data: badApiResponse, paginationInformation }, payload: { data: badApiResponse, paginationInformation },
}, },
{ type: types.SET_LOADING_STATE, payload: false }, { type: types.SET_LOADING_CLUSTERS, payload: false },
{ type: types.SET_LOADING_CLUSTERS, payload: false },
{ type: types.SET_LOADING_NODES, payload: false },
], ],
[], [],
() => { () => {
......
import * as types from '~/clusters_list/store/mutation_types';
import { apiData } from '../mock_data';
import getInitialState from '~/clusters_list/store/state';
import mutations from '~/clusters_list/store/mutations';
describe('Admin statistics panel mutations', () => {
let state;
const paginationInformation = {
nextPage: 1,
page: 1,
perPage: 20,
previousPage: 1,
total: apiData.clusters.length,
totalPages: 1,
};
beforeEach(() => {
state = getInitialState();
});
describe(`${types.SET_CLUSTERS_DATA}`, () => {
it('sets clusters and pagination values', () => {
mutations[types.SET_CLUSTERS_DATA](state, { data: apiData, paginationInformation });
expect(state.clusters).toBe(apiData.clusters);
expect(state.clustersPerPage).toBe(paginationInformation.perPage);
expect(state.hasAncestorClusters).toBe(apiData.has_ancestor_clusters);
expect(state.totalCulsters).toBe(paginationInformation.total);
});
});
describe(`${types.SET_LOADING_CLUSTERS}`, () => {
it('sets value to false', () => {
expect(state.loadingClusters).toBe(true);
mutations[types.SET_LOADING_CLUSTERS](state, false);
expect(state.loadingClusters).toBe(false);
});
});
describe(`${types.SET_LOADING_NODES}`, () => {
it('sets value to false', () => {
expect(state.loadingNodes).toBe(true);
mutations[types.SET_LOADING_NODES](state, false);
expect(state.loadingNodes).toBe(false);
});
});
describe(`${types.SET_PAGE}`, () => {
it('changes page value', () => {
mutations[types.SET_PAGE](state, 123);
expect(state.page).toBe(123);
});
});
});
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