Commit fc235235 authored by Zack Cuddy's avatar Zack Cuddy Committed by Andrew Fontaine

Geo Node Status 2.0 - Loading and Empty States

This change adds an empty state and
loading state for the Geo Nodes Beta.

This change also hides the Add site
button when no nodes exist per a
previoius Geo requirment.
parent 048403df
<script>
import { GlLink, GlButton } from '@gitlab/ui';
import { GlLink, GlButton, GlLoadingIcon } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import { GEO_INFO_URL } from '../constants';
import GeoNodes from './geo_nodes.vue';
import GeoNodesEmptyState from './geo_nodes_empty_state.vue';
export default {
name: 'GeoNodesBetaApp',
components: {
GlLink,
GlButton,
GlLoadingIcon,
GeoNodes,
GeoNodesEmptyState,
},
props: {
newNodeUrl: {
type: String,
required: true,
},
geoNodesEmptyStateSvg: {
type: String,
required: true,
},
},
computed: {
...mapState(['nodes']),
...mapState(['nodes', 'isLoading']),
noNodes() {
return !this.nodes || this.nodes.length === 0;
},
},
created() {
this.fetchNodes();
......@@ -47,6 +57,7 @@ export default {
}}</gl-link>
</div>
<gl-button
v-if="!noNodes"
class="gl-w-full gl-md-w-auto gl-ml-auto gl-mr-5 gl-mt-5 gl-md-mt-0"
variant="confirm"
:href="newNodeUrl"
......@@ -54,6 +65,10 @@ export default {
>{{ s__('Geo|Add site') }}
</gl-button>
</div>
<geo-nodes v-for="node in nodes" :key="node.id" :node="node" />
<gl-loading-icon v-if="isLoading" size="xl" class="gl-mt-5" />
<div v-if="!isLoading">
<geo-nodes v-for="node in nodes" :key="node.id" :node="node" />
<geo-nodes-empty-state v-if="noNodes" :svg-path="geoNodesEmptyStateSvg" />
</div>
</section>
</template>
<script>
import { GlEmptyState } from '@gitlab/ui';
import { GEO_FEATURE_URL } from '../constants';
export default {
name: 'GeoNodesEmptyState',
components: {
GlEmptyState,
},
props: {
svgPath: {
type: String,
required: true,
},
},
GEO_FEATURE_URL,
};
</script>
<template>
<gl-empty-state
:title="s__('Geo|Discover GitLab Geo')"
:svg-path="svgPath"
:description="
s__(
'Geo|Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos.',
)
"
:primary-button-link="$options.GEO_FEATURE_URL"
:primary-button-text="s__('Geo|Learn more about Geo')"
/>
</template>
import { helpPagePath } from '~/helpers/help_page_helper';
export const GEO_INFO_URL = helpPagePath('administration/geo/index.md');
export const GEO_FEATURE_URL = 'https://about.gitlab.com/features/gitlab-geo/';
......@@ -13,7 +13,7 @@ export const initGeoNodesBeta = () => {
return false;
}
const { primaryVersion, primaryRevision, newNodeUrl } = el.dataset;
const { primaryVersion, primaryRevision, newNodeUrl, geoNodesEmptyStateSvg } = el.dataset;
let { replicableTypes } = el.dataset;
replicableTypes = convertObjectPropsToCamelCase(JSON.parse(replicableTypes), { deep: true });
......@@ -25,6 +25,7 @@ export const initGeoNodesBeta = () => {
return createElement(GeoNodesBetaApp, {
props: {
newNodeUrl,
geoNodesEmptyStateSvg,
},
});
},
......
......@@ -33,7 +33,8 @@ module EE
node_edit_allowed: ::Gitlab::Geo.license_allows?.to_s,
geo_troubleshooting_help_path: help_page_path('administration/geo/replication/troubleshooting.md'),
replicable_types: replicable_types.to_json,
new_node_url: new_admin_geo_node_path
new_node_url: new_admin_geo_node_path,
geo_nodes_empty_state_svg: image_path("illustrations/empty-state/geo-empty.svg")
}
end
......
import { GlLink, GlButton } from '@gitlab/ui';
import { GlLink, GlButton, GlLoadingIcon } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import GeoNodesBetaApp from 'ee/geo_nodes_beta/components/app.vue';
import GeoNodes from 'ee/geo_nodes_beta/components/geo_nodes.vue';
import GeoNodesEmptyState from 'ee/geo_nodes_beta/components/geo_nodes_empty_state.vue';
import { GEO_INFO_URL } from 'ee/geo_nodes_beta/constants';
import {
MOCK_PRIMARY_VERSION,
MOCK_REPLICABLE_TYPES,
MOCK_NODES,
MOCK_NEW_NODE_URL,
MOCK_EMPTY_STATE_SVG,
} from '../mock_data';
const localVue = createLocalVue();
......@@ -23,6 +25,7 @@ describe('GeoNodesBetaApp', () => {
const defaultProps = {
newNodeUrl: MOCK_NEW_NODE_URL,
geoNodesEmptyStateSvg: MOCK_EMPTY_STATE_SVG,
};
const createComponent = (initialState, props) => {
......@@ -54,35 +57,67 @@ describe('GeoNodesBetaApp', () => {
const findGeoNodesBetaContainer = () => wrapper.find('section');
const findGeoLearnMoreLink = () => wrapper.find(GlLink);
const findGeoAddSiteButton = () => wrapper.find(GlButton);
const findGlLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findGeoEmptyState = () => wrapper.find(GeoNodesEmptyState);
const findGeoNodes = () => wrapper.findAll(GeoNodes);
describe('template', () => {
beforeEach(() => {
createComponent();
});
it('renders the Geo Nodes Beta Container always', () => {
expect(findGeoNodesBetaContainer().exists()).toBe(true);
});
it('renders the Learn more link correctly', () => {
expect(findGeoLearnMoreLink().exists()).toBe(true);
expect(findGeoLearnMoreLink().attributes('href')).toBe(GEO_INFO_URL);
describe('always', () => {
beforeEach(() => {
createComponent();
});
it('renders the Geo Nodes Beta Container', () => {
expect(findGeoNodesBetaContainer().exists()).toBe(true);
});
it('renders the Learn more link correctly', () => {
expect(findGeoLearnMoreLink().exists()).toBe(true);
expect(findGeoLearnMoreLink().attributes('href')).toBe(GEO_INFO_URL);
});
});
it('renders the Add site button correctly', () => {
expect(findGeoAddSiteButton().exists()).toBe(true);
expect(findGeoAddSiteButton().attributes('href')).toBe(MOCK_NEW_NODE_URL);
});
});
describe.each`
isLoading | nodes | showLoadingIcon | showNodes | showEmptyState | showAddButton
${true} | ${[]} | ${true} | ${false} | ${false} | ${false}
${true} | ${MOCK_NODES} | ${true} | ${false} | ${false} | ${true}
${false} | ${[]} | ${false} | ${false} | ${true} | ${false}
${false} | ${MOCK_NODES} | ${false} | ${true} | ${false} | ${true}
`(
`conditionally`,
({ isLoading, nodes, showLoadingIcon, showNodes, showEmptyState, showAddButton }) => {
beforeEach(() => {
createComponent({ isLoading, nodes });
});
describe(`when isLoading is ${isLoading} & nodes length ${nodes.length}`, () => {
it(`does ${!showLoadingIcon ? 'not ' : ''}render GlLoadingIcon`, () => {
expect(findGlLoadingIcon().exists()).toBe(showLoadingIcon);
});
it(`does ${!showNodes ? 'not ' : ''}render GeoNodes`, () => {
expect(findGeoNodes().exists()).toBe(showNodes);
});
it(`does ${!showEmptyState ? 'not ' : ''}render EmptyState`, () => {
expect(findGeoEmptyState().exists()).toBe(showEmptyState);
});
it(`does ${!showAddButton ? 'not ' : ''}render AddSiteButton`, () => {
expect(findGeoAddSiteButton().exists()).toBe(showAddButton);
});
});
},
);
describe('Geo Nodes', () => {
beforeEach(() => {
createComponent({ nodes: MOCK_NODES });
});
describe('with Geo Nodes', () => {
beforeEach(() => {
createComponent({ nodes: MOCK_NODES });
});
it('renders a Geo Node component for each node', () => {
expect(findGeoNodes()).toHaveLength(MOCK_NODES.length);
it('renders a Geo Node component for each node', () => {
expect(findGeoNodes()).toHaveLength(MOCK_NODES.length);
});
});
});
......
import { GlEmptyState } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import GeoNodesEmptyState from 'ee/geo_nodes_beta/components/geo_nodes_empty_state.vue';
import { GEO_FEATURE_URL } from 'ee/geo_nodes_beta/constants';
import { MOCK_PRIMARY_VERSION, MOCK_REPLICABLE_TYPES, MOCK_EMPTY_STATE_SVG } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('GeoNodesEmptyState', () => {
let wrapper;
const defaultProps = {
svgPath: MOCK_EMPTY_STATE_SVG,
};
const createComponent = (initialState, props) => {
const store = new Vuex.Store({
state: {
primaryVersion: MOCK_PRIMARY_VERSION.version,
primaryRevision: MOCK_PRIMARY_VERSION.revision,
replicableTypes: MOCK_REPLICABLE_TYPES,
...initialState,
},
});
wrapper = shallowMount(GeoNodesEmptyState, {
localVue,
store,
propsData: {
...defaultProps,
...props,
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
const findGeoEmptyState = () => wrapper.find(GlEmptyState);
describe('template', () => {
beforeEach(() => {
createComponent();
});
it('renders the Geo Empty State always', () => {
expect(findGeoEmptyState().exists()).toBe(true);
});
it('adds the correct SVG', () => {
expect(findGeoEmptyState().attributes('svgpath')).toBe(MOCK_EMPTY_STATE_SVG);
});
it('links the correct help link', () => {
expect(findGeoEmptyState().attributes('primarybuttontext')).toBe('Learn more about Geo');
expect(findGeoEmptyState().attributes('primarybuttonlink')).toBe(GEO_FEATURE_URL);
});
});
});
export const MOCK_NEW_NODE_URL = 'http://localhost:3000/admin/geo/nodes/new';
export const MOCK_EMPTY_STATE_SVG = 'illustrations/empty-state/geo-empty.svg';
export const MOCK_PRIMARY_VERSION = {
version: '10.4.0-pre',
revision: 'b93c51849b',
......
......@@ -13568,6 +13568,9 @@ msgstr ""
msgid "Geo|Could not remove tracking entry for an existing upload."
msgstr ""
msgid "Geo|Discover GitLab Geo"
msgstr ""
msgid "Geo|Failed"
msgstr ""
......@@ -13604,6 +13607,12 @@ msgstr ""
msgid "Geo|Last time verified"
msgstr ""
msgid "Geo|Learn more about Geo"
msgstr ""
msgid "Geo|Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
msgid "Geo|Never"
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