Commit fecaaefd authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch '287978_08-geo-node-beta-secondary-replication-details' into 'master'

Geo Node Status 2.0 - Replication Details Container

See merge request gitlab-org/gitlab!56572
parents af930550 00a8f465
<script> <script>
import { s__ } from '~/locale';
import GeoNodeCoreDetails from './geo_node_core_details.vue'; import GeoNodeCoreDetails from './geo_node_core_details.vue';
import GeoNodePrimaryOtherInfo from './primary_node/geo_node_primary_other_info.vue'; import GeoNodePrimaryOtherInfo from './primary_node/geo_node_primary_other_info.vue';
import GeoNodeVerificationInfo from './primary_node/geo_node_verification_info.vue'; import GeoNodeVerificationInfo from './primary_node/geo_node_verification_info.vue';
import GeoNodeReplicationDetails from './secondary_node/geo_node_replication_details.vue';
import GeoNodeReplicationSummary from './secondary_node/geo_node_replication_summary.vue'; import GeoNodeReplicationSummary from './secondary_node/geo_node_replication_summary.vue';
import GeoNodeSecondaryOtherInfo from './secondary_node/geo_node_secondary_other_info.vue'; import GeoNodeSecondaryOtherInfo from './secondary_node/geo_node_secondary_other_info.vue';
export default { export default {
name: 'GeoNodeDetails', name: 'GeoNodeDetails',
i18n: {
replicationDetails: s__('Geo|Replication Details'),
},
components: { components: {
GeoNodeCoreDetails, GeoNodeCoreDetails,
GeoNodePrimaryOtherInfo, GeoNodePrimaryOtherInfo,
GeoNodeVerificationInfo, GeoNodeVerificationInfo,
GeoNodeReplicationSummary, GeoNodeReplicationSummary,
GeoNodeSecondaryOtherInfo, GeoNodeSecondaryOtherInfo,
GeoNodeReplicationDetails,
}, },
props: { props: {
node: { node: {
...@@ -50,7 +48,7 @@ export default { ...@@ -50,7 +48,7 @@ export default {
/> />
<geo-node-secondary-other-info class="gl-flex-fill-1 gl-h-full gl-w-full" :node="node" /> <geo-node-secondary-other-info class="gl-flex-fill-1 gl-h-full gl-w-full" :node="node" />
</div> </div>
<p data-testid="secondary-replication-details">{{ $options.i18n.replicationDetails }}</p> <geo-node-replication-details :node="node" />
</div> </div>
</div> </div>
</template> </template>
<script>
import { GlIcon, GlPopover, GlLink, GlButton } from '@gitlab/ui';
import { mapGetters, mapState } from 'vuex';
import { GEO_REPLICATION_TYPES_URL } from 'ee/geo_nodes_beta/constants';
import { s__, __ } from '~/locale';
export default {
name: 'GeoNodeReplicationDetails',
i18n: {
replicationDetailsDesktop: s__('Geo|Replication Details Desktop'),
replicationDetailsMobile: s__('Geo|Replication Details Mobile'),
replicationDetails: s__('Geo|Replication Details'),
popoverText: s__('Geo|Geo supports replication of many data types.'),
learnMore: __('Learn more'),
},
components: {
GlIcon,
GlPopover,
GlLink,
GlButton,
},
props: {
node: {
type: Object,
required: true,
},
},
data() {
return {
collapsed: false,
};
},
computed: {
...mapState(['replicableTypes']),
...mapGetters(['verificationInfo', 'syncInfo']),
replicationItems() {
const syncInfoData = this.syncInfo(this.node.id);
const verificationInfoData = this.verificationInfo(this.node.id);
return this.replicableTypes.map((replicable) => {
const replicableSyncInfo = syncInfoData.find((r) => r.title === replicable.titlePlural);
const replicableVerificationInfo = verificationInfoData.find(
(r) => r.title === replicable.titlePlural,
);
return {
dataTypeTitle: replicable.dataTypeTitle,
component: replicable.titlePlural,
syncValues: replicableSyncInfo ? replicableSyncInfo.values : null,
verificationValues: replicableVerificationInfo ? replicableVerificationInfo.values : null,
};
});
},
chevronIcon() {
return this.collapsed ? 'chevron-right' : 'chevron-down';
},
},
methods: {
collapseSection() {
this.collapsed = !this.collapsed;
},
},
GEO_REPLICATION_TYPES_URL,
};
</script>
<template>
<div>
<div
class="gl-display-flex gl-align-items-center gl-cursor-pointer gl-py-5 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100 gl-border-t-1 gl-border-t-solid gl-border-t-gray-100"
>
<gl-button
class="gl-mr-3 gl-p-0!"
category="tertiary"
variant="confirm"
:icon="chevronIcon"
@click="collapseSection"
>
{{ $options.i18n.replicationDetails }}
</gl-button>
<gl-icon
ref="replicationDetails"
tabindex="0"
name="question"
class="gl-text-blue-500 gl-cursor-pointer gl-ml-2"
/>
<gl-popover
:target="() => $refs.replicationDetails.$el"
placement="top"
triggers="hover focus"
>
<p>
{{ $options.i18n.popoverText }}
</p>
<gl-link :href="$options.GEO_REPLICATION_TYPES_URL" target="_blank">{{
$options.i18n.learnMore
}}</gl-link>
</gl-popover>
</div>
<div v-if="!collapsed">
<span class="gl-display-none gl-md-display-block" data-testid="replication-details-desktop">{{
$options.i18n.replicationDetailsDesktop
}}</span>
<span class="gl-md-display-none!" data-testid="replication-details-mobile">{{
$options.i18n.replicationDetailsMobile
}}</span>
</div>
</div>
</template>
...@@ -22,6 +22,10 @@ export const REPLICATION_PAUSE_URL = helpPagePath('administration/geo/index.html ...@@ -22,6 +22,10 @@ export const REPLICATION_PAUSE_URL = helpPagePath('administration/geo/index.html
anchor: 'pausing-and-resuming-replication', anchor: 'pausing-and-resuming-replication',
}); });
export const GEO_REPLICATION_TYPES_URL = helpPagePath(
'administration/geo/replication/datatypes.html',
);
export const HEALTH_STATUS_UI = { export const HEALTH_STATUS_UI = {
healthy: { healthy: {
icon: 'status_success', icon: 'status_success',
......
...@@ -50,3 +50,12 @@ ...@@ -50,3 +50,12 @@
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
grid-gap: $gl-spacing-scale-5; grid-gap: $gl-spacing-scale-5;
} }
.geo-node-replication-details-grid-columns {
grid-template-columns: 1fr 1fr;
grid-gap: 1rem;
@include media-breakpoint-up(md) {
grid-template-columns: 1fr 1fr 2fr 2fr;
}
}
...@@ -3,6 +3,7 @@ import GeoNodeCoreDetails from 'ee/geo_nodes_beta/components/details/geo_node_co ...@@ -3,6 +3,7 @@ import GeoNodeCoreDetails from 'ee/geo_nodes_beta/components/details/geo_node_co
import GeoNodeDetails from 'ee/geo_nodes_beta/components/details/geo_node_details.vue'; import GeoNodeDetails from 'ee/geo_nodes_beta/components/details/geo_node_details.vue';
import GeoNodePrimaryOtherInfo from 'ee/geo_nodes_beta/components/details/primary_node/geo_node_primary_other_info.vue'; import GeoNodePrimaryOtherInfo from 'ee/geo_nodes_beta/components/details/primary_node/geo_node_primary_other_info.vue';
import GeoNodeVerificationInfo from 'ee/geo_nodes_beta/components/details/primary_node/geo_node_verification_info.vue'; import GeoNodeVerificationInfo from 'ee/geo_nodes_beta/components/details/primary_node/geo_node_verification_info.vue';
import GeoNodeReplicationDetails from 'ee/geo_nodes_beta/components/details/secondary_node/geo_node_replication_details.vue';
import GeoNodeReplicationSummary from 'ee/geo_nodes_beta/components/details/secondary_node/geo_node_replication_summary.vue'; import GeoNodeReplicationSummary from 'ee/geo_nodes_beta/components/details/secondary_node/geo_node_replication_summary.vue';
import GeoNodeSecondaryOtherInfo from 'ee/geo_nodes_beta/components/details/secondary_node/geo_node_secondary_other_info.vue'; import GeoNodeSecondaryOtherInfo from 'ee/geo_nodes_beta/components/details/secondary_node/geo_node_secondary_other_info.vue';
import { MOCK_NODES } from 'ee_jest/geo_nodes_beta/mock_data'; import { MOCK_NODES } from 'ee_jest/geo_nodes_beta/mock_data';
...@@ -37,7 +38,7 @@ describe('GeoNodeDetails', () => { ...@@ -37,7 +38,7 @@ describe('GeoNodeDetails', () => {
wrapper.findComponent(GeoNodeReplicationSummary); wrapper.findComponent(GeoNodeReplicationSummary);
const findGeoNodeSecondaryOtherInfo = () => wrapper.findComponent(GeoNodeSecondaryOtherInfo); const findGeoNodeSecondaryOtherInfo = () => wrapper.findComponent(GeoNodeSecondaryOtherInfo);
const findGeoNodeSecondaryReplicationDetails = () => const findGeoNodeSecondaryReplicationDetails = () =>
wrapper.findByTestId('secondary-replication-details'); wrapper.findComponent(GeoNodeReplicationDetails);
describe('template', () => { describe('template', () => {
describe('always', () => { describe('always', () => {
......
import { GlIcon, GlPopover, GlLink, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import GeoNodeReplicationDetails from 'ee/geo_nodes_beta/components/details/secondary_node/geo_node_replication_details.vue';
import { GEO_REPLICATION_TYPES_URL } from 'ee/geo_nodes_beta/constants';
import { MOCK_NODES, MOCK_REPLICABLE_TYPES } from 'ee_jest/geo_nodes_beta/mock_data';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
Vue.use(Vuex);
describe('GeoNodeReplicationDetails', () => {
let wrapper;
const defaultProps = {
node: MOCK_NODES[1],
};
const createComponent = (initialState, props, getters) => {
const store = new Vuex.Store({
state: {
replicableTypes: [],
...initialState,
},
getters: {
syncInfo: () => () => [],
verificationInfo: () => () => [],
...getters,
},
});
wrapper = extendedWrapper(
shallowMount(GeoNodeReplicationDetails, {
store,
propsData: {
...defaultProps,
...props,
},
}),
);
};
afterEach(() => {
wrapper.destroy();
});
const findGeoMobileReplicationDetails = () => wrapper.findByTestId('replication-details-mobile');
const findGeoDesktopReplicationDetails = () =>
wrapper.findByTestId('replication-details-desktop');
const findGlIcon = () => wrapper.findComponent(GlIcon);
const findGlPopover = () => wrapper.findComponent(GlPopover);
const findGlPopoverLink = () => findGlPopover().findComponent(GlLink);
const findCollapseButton = () => wrapper.findComponent(GlButton);
describe('template', () => {
describe('always', () => {
beforeEach(() => {
createComponent();
});
it('renders the question icon correctly', () => {
expect(findGlIcon().exists()).toBe(true);
expect(findGlIcon().props('name')).toBe('question');
});
it('renders the GlPopover always', () => {
expect(findGlPopover().exists()).toBe(true);
});
it('renders the popover link correctly', () => {
expect(findGlPopoverLink().exists()).toBe(true);
expect(findGlPopoverLink().attributes('href')).toBe(GEO_REPLICATION_TYPES_URL);
});
});
describe('when un-collapsed', () => {
beforeEach(() => {
createComponent();
});
it('renders the collapse button correctly', () => {
expect(findCollapseButton().exists()).toBe(true);
expect(findCollapseButton().attributes('icon')).toBe('chevron-down');
});
it('renders mobile replication details with correct visibility class', () => {
expect(findGeoMobileReplicationDetails().exists()).toBe(true);
expect(findGeoMobileReplicationDetails().classes()).toStrictEqual(['gl-md-display-none!']);
});
it('renders desktop details with correct visibility class', () => {
expect(findGeoDesktopReplicationDetails().exists()).toBe(true);
expect(findGeoDesktopReplicationDetails().classes()).toStrictEqual([
'gl-display-none',
'gl-md-display-block',
]);
});
});
describe('when collapsed', () => {
beforeEach(() => {
createComponent();
findCollapseButton().vm.$emit('click');
});
it('renders the collapse button correctly', () => {
expect(findCollapseButton().exists()).toBe(true);
expect(findCollapseButton().attributes('icon')).toBe('chevron-right');
});
it('does not render mobile replication details', () => {
expect(findGeoMobileReplicationDetails().exists()).toBe(false);
});
it('does not render desktop replication details', () => {
expect(findGeoDesktopReplicationDetails().exists()).toBe(false);
});
});
const mockSync = {
dataTypeTitle: MOCK_REPLICABLE_TYPES[0].dataTypeTitle,
title: MOCK_REPLICABLE_TYPES[0].titlePlural,
values: { total: 100, success: 0 },
};
const mockVerif = {
dataTypeTitle: MOCK_REPLICABLE_TYPES[0].dataTypeTitle,
title: MOCK_REPLICABLE_TYPES[0].titlePlural,
values: { total: 50, success: 50 },
};
const mockExpectedNoValues = {
dataTypeTitle: MOCK_REPLICABLE_TYPES[0].dataTypeTitle,
component: MOCK_REPLICABLE_TYPES[0].titlePlural,
syncValues: null,
verificationValues: null,
};
const mockExpectedOnlySync = {
dataTypeTitle: MOCK_REPLICABLE_TYPES[0].dataTypeTitle,
component: MOCK_REPLICABLE_TYPES[0].titlePlural,
syncValues: { total: 100, success: 0 },
verificationValues: null,
};
const mockExpectedOnlyVerif = {
dataTypeTitle: MOCK_REPLICABLE_TYPES[0].dataTypeTitle,
component: MOCK_REPLICABLE_TYPES[0].titlePlural,
syncValues: null,
verificationValues: { total: 50, success: 50 },
};
const mockExpectedBothTypes = {
dataTypeTitle: MOCK_REPLICABLE_TYPES[0].dataTypeTitle,
component: MOCK_REPLICABLE_TYPES[0].titlePlural,
syncValues: { total: 100, success: 0 },
verificationValues: { total: 50, success: 50 },
};
describe.each`
description | mockSyncData | mockVerificationData | expectedData
${'with no data'} | ${[]} | ${[]} | ${[mockExpectedNoValues]}
${'with no verification data'} | ${[mockSync]} | ${[]} | ${[mockExpectedOnlySync]}
${'with no sync data'} | ${[]} | ${[mockVerif]} | ${[mockExpectedOnlyVerif]}
${'with all data'} | ${[mockSync]} | ${[mockVerif]} | ${[mockExpectedBothTypes]}
`('$description', ({ mockSyncData, mockVerificationData, expectedData }) => {
beforeEach(() => {
createComponent({ replicableTypes: [MOCK_REPLICABLE_TYPES[0]] }, null, {
syncInfo: () => () => mockSyncData,
verificationInfo: () => () => mockVerificationData,
});
});
// TODO: Replace this spec with a template spec, once the UI has been hooked up in the next MR.
it('creates the correct replicationItems array', () => {
expect(wrapper.vm.replicationItems).toStrictEqual(expectedData);
});
});
});
});
...@@ -14368,6 +14368,9 @@ msgstr "" ...@@ -14368,6 +14368,9 @@ msgstr ""
msgid "Geo|Geo sites" msgid "Geo|Geo sites"
msgstr "" msgstr ""
msgid "Geo|Geo supports replication of many data types."
msgstr ""
msgid "Geo|Go to the primary site" msgid "Geo|Go to the primary site"
msgstr "" msgstr ""
...@@ -14476,6 +14479,12 @@ msgstr "" ...@@ -14476,6 +14479,12 @@ msgstr ""
msgid "Geo|Replication Details" msgid "Geo|Replication Details"
msgstr "" msgstr ""
msgid "Geo|Replication Details Desktop"
msgstr ""
msgid "Geo|Replication Details Mobile"
msgstr ""
msgid "Geo|Replication details" msgid "Geo|Replication details"
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