Commit 703e2c32 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch '287978_02-geo-beta-loading-empty-state' into 'master'

Geo Node Status 2.0 - Loading and Empty States

See merge request gitlab-org/gitlab!54996
parents 21e1da36 fc235235
<script> <script>
import { GlLink, GlButton } from '@gitlab/ui'; import { GlLink, GlButton, GlLoadingIcon } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex'; import { mapActions, mapState } from 'vuex';
import { GEO_INFO_URL } from '../constants'; import { GEO_INFO_URL } from '../constants';
import GeoNodes from './geo_nodes.vue'; import GeoNodes from './geo_nodes.vue';
import GeoNodesEmptyState from './geo_nodes_empty_state.vue';
export default { export default {
name: 'GeoNodesBetaApp', name: 'GeoNodesBetaApp',
components: { components: {
GlLink, GlLink,
GlButton, GlButton,
GlLoadingIcon,
GeoNodes, GeoNodes,
GeoNodesEmptyState,
}, },
props: { props: {
newNodeUrl: { newNodeUrl: {
type: String, type: String,
required: true, required: true,
}, },
geoNodesEmptyStateSvg: {
type: String,
required: true,
},
}, },
computed: { computed: {
...mapState(['nodes']), ...mapState(['nodes', 'isLoading']),
noNodes() {
return !this.nodes || this.nodes.length === 0;
},
}, },
created() { created() {
this.fetchNodes(); this.fetchNodes();
...@@ -47,6 +57,7 @@ export default { ...@@ -47,6 +57,7 @@ export default {
}}</gl-link> }}</gl-link>
</div> </div>
<gl-button <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" class="gl-w-full gl-md-w-auto gl-ml-auto gl-mr-5 gl-mt-5 gl-md-mt-0"
variant="confirm" variant="confirm"
:href="newNodeUrl" :href="newNodeUrl"
...@@ -54,6 +65,10 @@ export default { ...@@ -54,6 +65,10 @@ export default {
>{{ s__('Geo|Add site') }} >{{ s__('Geo|Add site') }}
</gl-button> </gl-button>
</div> </div>
<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 v-for="node in nodes" :key="node.id" :node="node" />
<geo-nodes-empty-state v-if="noNodes" :svg-path="geoNodesEmptyStateSvg" />
</div>
</section> </section>
</template> </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'; import { helpPagePath } from '~/helpers/help_page_helper';
export const GEO_INFO_URL = helpPagePath('administration/geo/index.md'); 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 = () => { ...@@ -13,7 +13,7 @@ export const initGeoNodesBeta = () => {
return false; return false;
} }
const { primaryVersion, primaryRevision, newNodeUrl } = el.dataset; const { primaryVersion, primaryRevision, newNodeUrl, geoNodesEmptyStateSvg } = el.dataset;
let { replicableTypes } = el.dataset; let { replicableTypes } = el.dataset;
replicableTypes = convertObjectPropsToCamelCase(JSON.parse(replicableTypes), { deep: true }); replicableTypes = convertObjectPropsToCamelCase(JSON.parse(replicableTypes), { deep: true });
...@@ -25,6 +25,7 @@ export const initGeoNodesBeta = () => { ...@@ -25,6 +25,7 @@ export const initGeoNodesBeta = () => {
return createElement(GeoNodesBetaApp, { return createElement(GeoNodesBetaApp, {
props: { props: {
newNodeUrl, newNodeUrl,
geoNodesEmptyStateSvg,
}, },
}); });
}, },
......
...@@ -33,7 +33,8 @@ module EE ...@@ -33,7 +33,8 @@ module EE
node_edit_allowed: ::Gitlab::Geo.license_allows?.to_s, node_edit_allowed: ::Gitlab::Geo.license_allows?.to_s,
geo_troubleshooting_help_path: help_page_path('administration/geo/replication/troubleshooting.md'), geo_troubleshooting_help_path: help_page_path('administration/geo/replication/troubleshooting.md'),
replicable_types: replicable_types.to_json, 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 end
......
import { GlLink, GlButton } from '@gitlab/ui'; import { GlLink, GlButton, GlLoadingIcon } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import GeoNodesBetaApp from 'ee/geo_nodes_beta/components/app.vue'; import GeoNodesBetaApp from 'ee/geo_nodes_beta/components/app.vue';
import GeoNodes from 'ee/geo_nodes_beta/components/geo_nodes.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 { GEO_INFO_URL } from 'ee/geo_nodes_beta/constants';
import { import {
MOCK_PRIMARY_VERSION, MOCK_PRIMARY_VERSION,
MOCK_REPLICABLE_TYPES, MOCK_REPLICABLE_TYPES,
MOCK_NODES, MOCK_NODES,
MOCK_NEW_NODE_URL, MOCK_NEW_NODE_URL,
MOCK_EMPTY_STATE_SVG,
} from '../mock_data'; } from '../mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -23,6 +25,7 @@ describe('GeoNodesBetaApp', () => { ...@@ -23,6 +25,7 @@ describe('GeoNodesBetaApp', () => {
const defaultProps = { const defaultProps = {
newNodeUrl: MOCK_NEW_NODE_URL, newNodeUrl: MOCK_NEW_NODE_URL,
geoNodesEmptyStateSvg: MOCK_EMPTY_STATE_SVG,
}; };
const createComponent = (initialState, props) => { const createComponent = (initialState, props) => {
...@@ -54,14 +57,17 @@ describe('GeoNodesBetaApp', () => { ...@@ -54,14 +57,17 @@ describe('GeoNodesBetaApp', () => {
const findGeoNodesBetaContainer = () => wrapper.find('section'); const findGeoNodesBetaContainer = () => wrapper.find('section');
const findGeoLearnMoreLink = () => wrapper.find(GlLink); const findGeoLearnMoreLink = () => wrapper.find(GlLink);
const findGeoAddSiteButton = () => wrapper.find(GlButton); const findGeoAddSiteButton = () => wrapper.find(GlButton);
const findGlLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findGeoEmptyState = () => wrapper.find(GeoNodesEmptyState);
const findGeoNodes = () => wrapper.findAll(GeoNodes); const findGeoNodes = () => wrapper.findAll(GeoNodes);
describe('template', () => { describe('template', () => {
describe('always', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
}); });
it('renders the Geo Nodes Beta Container always', () => { it('renders the Geo Nodes Beta Container', () => {
expect(findGeoNodesBetaContainer().exists()).toBe(true); expect(findGeoNodesBetaContainer().exists()).toBe(true);
}); });
...@@ -69,14 +75,42 @@ describe('GeoNodesBetaApp', () => { ...@@ -69,14 +75,42 @@ describe('GeoNodesBetaApp', () => {
expect(findGeoLearnMoreLink().exists()).toBe(true); expect(findGeoLearnMoreLink().exists()).toBe(true);
expect(findGeoLearnMoreLink().attributes('href')).toBe(GEO_INFO_URL); expect(findGeoLearnMoreLink().attributes('href')).toBe(GEO_INFO_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('renders the Add site button correctly', () => { it(`does ${!showNodes ? 'not ' : ''}render GeoNodes`, () => {
expect(findGeoAddSiteButton().exists()).toBe(true); expect(findGeoNodes().exists()).toBe(showNodes);
expect(findGeoAddSiteButton().attributes('href')).toBe(MOCK_NEW_NODE_URL);
}); });
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', () => { describe('with Geo Nodes', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ nodes: MOCK_NODES }); createComponent({ nodes: MOCK_NODES });
}); });
...@@ -85,6 +119,7 @@ describe('GeoNodesBetaApp', () => { ...@@ -85,6 +119,7 @@ describe('GeoNodesBetaApp', () => {
expect(findGeoNodes()).toHaveLength(MOCK_NODES.length); expect(findGeoNodes()).toHaveLength(MOCK_NODES.length);
}); });
}); });
});
describe('onCreate', () => { describe('onCreate', () => {
beforeEach(() => { beforeEach(() => {
......
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_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 = { export const MOCK_PRIMARY_VERSION = {
version: '10.4.0-pre', version: '10.4.0-pre',
revision: 'b93c51849b', revision: 'b93c51849b',
......
...@@ -13574,6 +13574,9 @@ msgstr "" ...@@ -13574,6 +13574,9 @@ msgstr ""
msgid "Geo|Could not remove tracking entry for an existing upload." msgid "Geo|Could not remove tracking entry for an existing upload."
msgstr "" msgstr ""
msgid "Geo|Discover GitLab Geo"
msgstr ""
msgid "Geo|Failed" msgid "Geo|Failed"
msgstr "" msgstr ""
...@@ -13610,6 +13613,12 @@ msgstr "" ...@@ -13610,6 +13613,12 @@ msgstr ""
msgid "Geo|Last time verified" msgid "Geo|Last time verified"
msgstr "" 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" msgid "Geo|Never"
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