Commit 9d48764b authored by Zack Cuddy's avatar Zack Cuddy Committed by Miguel Rincon

Geo - Generic Node Status Bars

This change removes the need for a hard coded
list of replicable types in the Geo Frontend
in regards to Node Status Bars.

Instead the frontend recieves the list and formats the
data from the API as it needs to match the existing
patterns.

This change also required some small backend
tweaks.
parent 03da73d6
......@@ -43,7 +43,8 @@ export default {
},
itemValueType: {
type: String,
required: true,
required: false,
default: VALUE_TYPE.GRAPH,
},
customType: {
type: String,
......
<script>
import { GlPopover, GlSprintf, GlLink } from '@gitlab/ui';
import { toNumber } from 'lodash';
import StackedProgressBar from '~/vue_shared/components/stacked_progress_bar.vue';
import tooltip from '~/vue_shared/directives/tooltip';
......@@ -22,8 +23,6 @@ export default {
itemValue: {
type: Object,
required: true,
validator: value =>
['totalCount', 'successCount', 'failureCount'].every(key => typeof value[key] === 'number'),
},
detailsPath: {
type: String,
......@@ -33,7 +32,16 @@ export default {
},
computed: {
queuedCount() {
return this.itemValue.totalCount - this.itemValue.successCount - this.itemValue.failureCount;
return this.totalCount - this.successCount - this.failureCount;
},
totalCount() {
return toNumber(this.itemValue.totalCount) || 0;
},
failureCount() {
return toNumber(this.itemValue.failureCount) || 0;
},
successCount() {
return toNumber(this.itemValue.successCount) || 0;
},
},
};
......@@ -46,9 +54,9 @@ export default {
tabindex="0"
:hide-tooltips="true"
:unavailable-label="__('Nothing to synchronize')"
:success-count="itemValue.successCount"
:failure-count="itemValue.failureCount"
:total-count="itemValue.totalCount"
:success-count="successCount"
:failure-count="failureCount"
:total-count="totalCount"
/>
<gl-popover
:target="`syncProgress-${itemTitle}`"
......@@ -67,12 +75,12 @@ export default {
<div class="d-flex align-items-center my-1">
<div class="mr-2 bg-transparent gl-w-5 gl-h-2"></div>
<span class="flex-grow-1 mr-3">{{ __('Total') }}</span>
<span class="font-weight-bold">{{ itemValue.totalCount.toLocaleString() }}</span>
<span class="font-weight-bold">{{ totalCount.toLocaleString() }}</span>
</div>
<div class="d-flex align-items-center my-2">
<div class="mr-2 bg-success-500 gl-w-5 gl-h-2"></div>
<span class="flex-grow-1 mr-3">{{ __('Synced') }}</span>
<span class="font-weight-bold">{{ itemValue.successCount.toLocaleString() }}</span>
<span class="font-weight-bold">{{ successCount.toLocaleString() }}</span>
</div>
<div class="d-flex align-items-center my-2">
<div class="mr-2 bg-secondary-200 gl-w-5 gl-h-2"></div>
......@@ -82,7 +90,7 @@ export default {
<div class="d-flex align-items-center my-2">
<div class="mr-2 bg-danger-500 gl-w-5 gl-h-2"></div>
<span class="flex-grow-1 mr-3">{{ __('Failed') }}</span>
<span class="font-weight-bold">{{ itemValue.failureCount.toLocaleString() }}</span>
<span class="font-weight-bold">{{ failureCount.toLocaleString() }}</span>
</div>
<div v-if="detailsPath" class="mt-3">
<gl-link class="gl-font-sm" :href="detailsPath" target="_blank">{{
......
......@@ -32,56 +32,7 @@ export default {
itemValueType: VALUE_TYPE.CUSTOM,
customType: CUSTOM_TYPE.SYNC,
},
{
itemEnabled: this.nodeDetails.repositories.enabled,
itemTitle: s__('GeoNodes|Repositories'),
itemValue: this.nodeDetails.repositories,
itemValueType: VALUE_TYPE.GRAPH,
detailsPath: `${this.node.url}admin/geo/projects`,
},
{
itemEnabled: this.nodeDetails.wikis.enabled,
itemTitle: s__('GeoNodes|Wikis'),
itemValue: this.nodeDetails.wikis,
itemValueType: VALUE_TYPE.GRAPH,
},
{
itemEnabled: this.nodeDetails.lfs.enabled,
itemTitle: s__('GeoNodes|LFS objects'),
itemValue: this.nodeDetails.lfs,
itemValueType: VALUE_TYPE.GRAPH,
},
{
itemEnabled: this.nodeDetails.attachments.enabled,
itemTitle: s__('GeoNodes|Attachments'),
itemValue: this.nodeDetails.attachments,
itemValueType: VALUE_TYPE.GRAPH,
detailsPath: `${this.node.url}admin/geo/uploads`,
},
{
itemEnabled: this.nodeDetails.jobArtifacts.enabled,
itemTitle: s__('GeoNodes|Job artifacts'),
itemValue: this.nodeDetails.jobArtifacts,
itemValueType: VALUE_TYPE.GRAPH,
},
{
itemEnabled: this.nodeDetails.containerRepositories.enabled,
itemTitle: s__('GeoNodes|Container repositories'),
itemValue: this.nodeDetails.containerRepositories,
itemValueType: VALUE_TYPE.GRAPH,
},
{
itemEnabled: this.nodeDetails.designRepositories.enabled,
itemTitle: s__('GeoNodes|Design repositories'),
itemValue: this.nodeDetails.designRepositories,
itemValueType: VALUE_TYPE.GRAPH,
detailsPath: `${this.node.url}admin/geo/designs`,
},
{
itemTitle: s__('GeoNodes|Package files'),
itemValue: this.nodeDetails.packageFiles,
itemValueType: VALUE_TYPE.GRAPH,
},
...this.nodeDetails.syncStatuses,
{
itemTitle: s__('GeoNodes|Data replication lag'),
itemValue: this.dbReplicationLag(),
......@@ -140,6 +91,22 @@ export default {
handleSectionToggle(toggleState) {
this.showSectionItems = toggleState;
},
detailsPath(nodeDetailItem) {
if (!nodeDetailItem.secondaryView) {
return '';
}
// This is due to some legacy coding patterns on the GeoNodeStatus API.
// This will be fixed as part of https://gitlab.com/gitlab-org/gitlab/-/issues/228718
if (nodeDetailItem.itemName === 'repositories') {
return `${this.node.url}admin/geo/replication/projects`;
} else if (nodeDetailItem.itemName === 'attachments') {
return `${this.node.url}admin/geo/replication/uploads`;
}
return `${this.node.url}admin/geo/replication/${nodeDetailItem.itemName}`;
},
},
};
</script>
......@@ -163,7 +130,7 @@ export default {
:item-value-type="nodeDetailItem.itemValueType"
:custom-type="nodeDetailItem.customType"
:event-type-log-status="nodeDetailItem.eventTypeLogStatus"
:details-path="nodeDetailItem.detailsPath"
:details-path="detailsPath(nodeDetailItem)"
/>
</div>
</div>
......
<script>
import { GlPopover, GlLink, GlIcon, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import { sprintf, s__ } from '~/locale';
import { VALUE_TYPE, HELP_INFO_URL } from '../../constants';
import { HELP_INFO_URL } from '../../constants';
import GeoNodeDetailItem from '../geo_node_detail_item.vue';
import SectionRevealButton from './section_reveal_button.vue';
......@@ -30,64 +30,42 @@ export default {
data() {
return {
showSectionItems: false,
primaryNodeDetailItems: this.getPrimaryNodeDetailItems(),
secondaryNodeDetailItems: this.getSecondaryNodeDetailItems(),
};
},
computed: {
nodeDetailItems() {
return this.nodeTypePrimary
? this.getPrimaryNodeDetailItems()
: this.getSecondaryNodeDetailItems();
? this.nodeDetails.checksumStatuses
: this.nodeDetails.verificationStatuses;
},
nodeText() {
return this.nodeTypePrimary ? s__('GeoNodes|secondary nodes') : s__('GeoNodes|primary node');
},
},
methods: {
getPrimaryNodeDetailItems() {
return [
{
itemTitle: s__('GeoNodes|Repository checksum progress'),
itemValue: this.nodeDetails.repositoriesChecksummed,
itemValueType: VALUE_TYPE.GRAPH,
successLabel: s__('GeoNodes|Checksummed'),
neutraLabel: s__('GeoNodes|Not checksummed'),
failureLabel: s__('GeoNodes|Failed'),
},
{
itemTitle: s__('GeoNodes|Wiki checksum progress'),
itemValue: this.nodeDetails.wikisChecksummed,
itemValueType: VALUE_TYPE.GRAPH,
successLabel: s__('GeoNodes|Checksummed'),
neutraLabel: s__('GeoNodes|Not checksummed'),
failureLabel: s__('GeoNodes|Failed'),
},
];
},
getSecondaryNodeDetailItems() {
return [
{
itemTitle: s__('GeoNodes|Repository verification progress'),
itemValue: this.nodeDetails.verifiedRepositories,
itemValueType: VALUE_TYPE.GRAPH,
successLabel: s__('GeoNodes|Verified'),
neutraLabel: s__('GeoNodes|Unverified'),
failureLabel: s__('GeoNodes|Failed'),
},
{
itemTitle: s__('GeoNodes|Wiki verification progress'),
itemValue: this.nodeDetails.verifiedWikis,
itemValueType: VALUE_TYPE.GRAPH,
successLabel: s__('GeoNodes|Verified'),
neutraLabel: s__('GeoNodes|Unverified'),
failureLabel: s__('GeoNodes|Failed'),
},
];
},
handleSectionToggle(toggleState) {
this.showSectionItems = toggleState;
},
itemValue(nodeDetailItem) {
return {
totalCount: nodeDetailItem.itemValue.totalCount,
successCount: this.nodeTypePrimary
? nodeDetailItem.itemValue.checksumSuccessCount
: nodeDetailItem.itemValue.verificationSuccessCount,
failureCount: this.nodeTypePrimary
? nodeDetailItem.itemValue.checksumFailureCount
: nodeDetailItem.itemValue.verificationFailureCount,
};
},
itemTitle(nodeDetailItem) {
return this.nodeTypePrimary
? sprintf(s__('Geo|%{itemTitle} checksum progress'), {
itemTitle: nodeDetailItem.itemTitle,
})
: sprintf(s__('Geo|%{itemTitle} verification progress'), {
itemTitle: nodeDetailItem.itemTitle,
});
},
},
HELP_INFO_URL,
};
......@@ -128,14 +106,8 @@ export default {
<geo-node-detail-item
v-for="(nodeDetailItem, index) in nodeDetailItems"
:key="index"
:css-class="nodeDetailItem.cssClass"
:item-title="nodeDetailItem.itemTitle"
:item-value="nodeDetailItem.itemValue"
:item-value-type="nodeDetailItem.itemValueType"
:success-label="nodeDetailItem.successLabel"
:neutral-label="nodeDetailItem.neutraLabel"
:failure-label="nodeDetailItem.failureLabel"
:custom-type="nodeDetailItem.customType"
:item-title="itemTitle(nodeDetailItem)"
:item-value="itemValue(nodeDetailItem)"
/>
</div>
</template>
......
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import { GlToast } from '@gitlab/ui';
import Translate from '~/vue_shared/translate';
import { parseBoolean } from '~/lib/utils/common_utils';
import { parseBoolean, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import GeoNodesStore from './store/geo_nodes_store';
import GeoNodesService from './service/geo_nodes_service';
......@@ -27,9 +27,12 @@ export default () => {
data() {
const { dataset } = this.$options.el;
const { primaryVersion, primaryRevision, geoTroubleshootingHelpPath } = dataset;
const replicableTypes = convertObjectPropsToCamelCase(JSON.parse(dataset.replicableTypes), {
deep: true,
});
const nodeActionsAllowed = parseBoolean(dataset.nodeActionsAllowed);
const nodeEditAllowed = parseBoolean(dataset.nodeEditAllowed);
const store = new GeoNodesStore(primaryVersion, primaryRevision);
const store = new GeoNodesStore(primaryVersion, primaryRevision, replicableTypes);
const service = new GeoNodesService();
return {
......
export default class GeoNodesStore {
constructor(primaryVersion, primaryRevision) {
constructor(primaryVersion, primaryRevision, replicableTypes) {
this.state = {};
this.state.nodes = [];
this.state.nodeDetails = {};
this.state.primaryVersion = primaryVersion;
this.state.primaryRevision = primaryRevision;
this.state.replicableTypes = replicableTypes;
}
setNodes(nodes) {
......@@ -16,7 +17,10 @@ export default class GeoNodesStore {
}
setNodeDetails(nodeId, nodeDetails) {
this.state.nodeDetails[nodeId] = GeoNodesStore.formatNodeDetails(nodeDetails);
this.state.nodeDetails[nodeId] = GeoNodesStore.formatNodeDetails(
nodeDetails,
this.state.replicableTypes,
);
}
removeNode(node) {
......@@ -60,7 +64,33 @@ export default class GeoNodesStore {
};
}
static formatNodeDetails(rawNodeDetails) {
static formatNodeDetails(rawNodeDetails, replicableTypes) {
const syncStatuses = replicableTypes.map(replicable => {
return {
itemEnabled: rawNodeDetails[`${replicable.namePlural}_replication_enabled`],
itemTitle: replicable.titlePlural,
itemName: replicable.namePlural,
itemValue: {
totalCount: rawNodeDetails[`${replicable.namePlural}_count`],
successCount: rawNodeDetails[`${replicable.namePlural}_synced_count`],
failureCount: rawNodeDetails[`${replicable.namePlural}_failed_count`],
verificationSuccessCount: rawNodeDetails[`${replicable.namePlural}_verified_count`],
verificationFailureCount:
rawNodeDetails[`${replicable.namePlural}_verification_failed_count`],
checksumSuccessCount: rawNodeDetails[`${replicable.namePlural}_checksummed_count`],
checksumFailureCount: rawNodeDetails[`${replicable.namePlural}_checksum_failed_count`],
},
...replicable,
};
});
const verificationStatuses = syncStatuses.filter(s =>
Boolean(s.itemValue.verificationSuccessCount || s.itemValue.verificationFailureCount),
);
const checksumStatuses = syncStatuses.filter(s =>
Boolean(s.itemValue.checksumSuccessCount || s.itemValue.checksumFailureCount),
);
return {
id: rawNodeDetails.geo_node_id,
health: rawNodeDetails.health,
......@@ -81,73 +111,9 @@ export default class GeoNodesStore {
successCount: rawNodeDetails.replication_slots_used_count || 0,
failureCount: 0,
},
repositories: {
enabled: rawNodeDetails.repositories_replication_enabled,
totalCount: rawNodeDetails.projects_count || 0,
successCount: rawNodeDetails.repositories_synced_count || 0,
failureCount: rawNodeDetails.repositories_failed_count || 0,
},
wikis: {
enabled: rawNodeDetails.repositories_replication_enabled,
totalCount: rawNodeDetails.projects_count || 0,
successCount: rawNodeDetails.wikis_synced_count || 0,
failureCount: rawNodeDetails.wikis_failed_count || 0,
},
repositoriesChecksummed: {
totalCount: rawNodeDetails.projects_count || 0,
successCount: rawNodeDetails.repositories_checksummed_count || 0,
failureCount: rawNodeDetails.repositories_checksum_failed_count || 0,
},
wikisChecksummed: {
totalCount: rawNodeDetails.projects_count || 0,
successCount: rawNodeDetails.wikis_checksummed_count || 0,
failureCount: rawNodeDetails.wikis_checksum_failed_count || 0,
},
verifiedRepositories: {
totalCount: rawNodeDetails.projects_count || 0,
successCount: rawNodeDetails.repositories_verified_count || 0,
failureCount: rawNodeDetails.repositories_verification_failed_count || 0,
},
verifiedWikis: {
totalCount: rawNodeDetails.projects_count || 0,
successCount: rawNodeDetails.wikis_verified_count || 0,
failureCount: rawNodeDetails.wikis_verification_failed_count || 0,
},
lfs: {
enabled: rawNodeDetails.lfs_objects_replication_enabled,
totalCount: rawNodeDetails.lfs_objects_count || 0,
successCount: rawNodeDetails.lfs_objects_synced_count || 0,
failureCount: rawNodeDetails.lfs_objects_failed_count || 0,
},
jobArtifacts: {
enabled: rawNodeDetails.job_artifacts_replication_enabled,
totalCount: rawNodeDetails.job_artifacts_count || 0,
successCount: rawNodeDetails.job_artifacts_synced_count || 0,
failureCount: rawNodeDetails.job_artifacts_failed_count || 0,
},
containerRepositories: {
enabled: rawNodeDetails.container_repositories_replication_enabled,
totalCount: rawNodeDetails.container_repositories_count || 0,
successCount: rawNodeDetails.container_repositories_synced_count || 0,
failureCount: rawNodeDetails.container_repositories_failed_count || 0,
},
designRepositories: {
enabled: rawNodeDetails.design_repositories_replication_enabled,
totalCount: rawNodeDetails.design_repositories_count || 0,
successCount: rawNodeDetails.design_repositories_synced_count || 0,
failureCount: rawNodeDetails.design_repositories_failed_count || 0,
},
packageFiles: {
totalCount: rawNodeDetails.package_files_registry_count || 0,
successCount: rawNodeDetails.package_files_synced_count || 0,
failureCount: rawNodeDetails.package_files_failed_count || 0,
},
attachments: {
enabled: rawNodeDetails.attachments_replication_enabled,
totalCount: rawNodeDetails.attachments_count || 0,
successCount: rawNodeDetails.attachments_synced_count || 0,
failureCount: rawNodeDetails.attachments_failed_count || 0,
},
syncStatuses,
verificationStatuses,
checksumStatuses,
lastEvent: {
id: rawNodeDetails.last_event_id || 0,
timeStamp: rawNodeDetails.last_event_timestamp,
......
......@@ -184,8 +184,8 @@ module EE
{
title: _('LFS object'),
title_plural: _('LFS objects'),
name: 'lfs',
name_plural: 'lfs'
name: 'lfs_object',
name_plural: 'lfs_objects'
},
{
title: _('Attachment'),
......
......@@ -16,6 +16,7 @@ import {
mockNodes,
mockNode,
rawMockNodeDetails,
MOCK_REPLICABLE_TYPES,
} from '../mock_data';
jest.mock('~/smart_interval');
......@@ -23,7 +24,11 @@ jest.mock('ee/geo_nodes/event_hub');
const createComponent = () => {
const Component = Vue.extend(appComponent);
const store = new GeoNodesStore(PRIMARY_VERSION.version, PRIMARY_VERSION.revision);
const store = new GeoNodesStore(
PRIMARY_VERSION.version,
PRIMARY_VERSION.revision,
MOCK_REPLICABLE_TYPES,
);
const service = new GeoNodesService(NODE_DETAILS_PATH);
return mountComponent(Component, {
......
......@@ -57,11 +57,47 @@ describe('GeoNodeSyncProgress', () => {
});
describe('computed', () => {
beforeEach(() => {
createComponent();
describe.each`
itemValue | expectedItemValue
${{ successCount: 5, failureCount: 3, totalCount: 10 }} | ${{ successCount: 5, failureCount: 3, totalCount: 10 }}
${{ successCount: '5', failureCount: '3', totalCount: '10' }} | ${{ successCount: 5, failureCount: 3, totalCount: 10 }}
${{ successCount: null, failureCount: null, totalCount: null }} | ${{ successCount: 0, failureCount: 0, totalCount: 0 }}
${{ successCount: 'abc', failureCount: 'def', totalCount: 'ghi' }} | ${{ successCount: 0, failureCount: 0, totalCount: 0 }}
`(`status counts`, ({ itemValue, expectedItemValue }) => {
beforeEach(() => {
createComponent({ itemValue });
});
it(`when itemValue.totalCount is ${
itemValue.totalCount
} (${typeof itemValue.totalCount}), it should compute to ${
expectedItemValue.totalCount
}`, () => {
expect(wrapper.vm.totalCount).toBe(expectedItemValue.totalCount);
});
it(`when itemValue.successCount is ${
itemValue.successCount
} (${typeof itemValue.successCount}), it should compute to ${
expectedItemValue.successCount
}`, () => {
expect(wrapper.vm.successCount).toBe(expectedItemValue.successCount);
});
it(`when itemValue.failureCount is ${
itemValue.failureCount
} (${typeof itemValue.failureCount}), it should compute to ${
expectedItemValue.failureCount
}`, () => {
expect(wrapper.vm.failureCount).toBe(expectedItemValue.failureCount);
});
});
describe('queuedCount', () => {
beforeEach(() => {
createComponent();
});
it('returns total - success - failure', () => {
expect(wrapper.vm.queuedCount).toEqual(MOCK_ITEM_VALUE.queuedCount);
});
......
......@@ -82,6 +82,26 @@ describe('NodeDetailsSectionSync', () => {
);
});
});
describe.each`
nodeDetailItem | path
${{ secondaryView: false, itemName: '' }} | ${''}
${{ secondaryView: true, itemName: 'repositories' }} | ${`${mockNode.url}admin/geo/replication/projects`}
${{ secondaryView: true, itemName: 'attachments' }} | ${`${mockNode.url}admin/geo/replication/uploads`}
${{ secondaryView: true, itemName: 'package_files' }} | ${`${mockNode.url}admin/geo/replication/package_files`}
`(`detailsPath`, ({ nodeDetailItem, path }) => {
describe(`when detail item is ${nodeDetailItem.itemName}`, () => {
let detailPath = '';
beforeEach(() => {
detailPath = wrapper.vm.detailsPath(nodeDetailItem);
});
it(`returns the correct path`, () => {
expect(detailPath).toBe(path);
});
});
});
});
describe('template', () => {
......
......@@ -3,6 +3,7 @@ import { GlPopover, GlSprintf } from '@gitlab/ui';
import NodeDetailsSectionVerificationComponent from 'ee/geo_nodes/components/node_detail_sections/node_details_section_verification.vue';
import SectionRevealButton from 'ee/geo_nodes/components/node_detail_sections/section_reveal_button.vue';
import GeoNodeDetailItem from 'ee/geo_nodes/components/geo_node_detail_item.vue';
import { mockNodeDetails } from '../../mock_data';
......@@ -32,58 +33,11 @@ describe('NodeDetailsSectionVerification', () => {
});
const findGlPopover = () => wrapper.find(GlPopover);
const findDetailItems = () => wrapper.findAll(GeoNodeDetailItem);
describe('data', () => {
it('returns default data props', () => {
expect(wrapper.vm.showSectionItems).toBe(false);
expect(Array.isArray(wrapper.vm.primaryNodeDetailItems)).toBe(true);
expect(Array.isArray(wrapper.vm.secondaryNodeDetailItems)).toBe(true);
expect(wrapper.vm.primaryNodeDetailItems.length).toBeGreaterThan(0);
expect(wrapper.vm.secondaryNodeDetailItems.length).toBeGreaterThan(0);
});
});
describe('methods', () => {
describe('getPrimaryNodeDetailItems', () => {
const primaryItems = [
{
title: 'Repository checksum progress',
valueProp: 'repositoriesChecksummed',
},
{
title: 'Wiki checksum progress',
valueProp: 'wikisChecksummed',
},
];
it('returns array containing items to show under primary node', () => {
const actualPrimaryItems = wrapper.vm.getPrimaryNodeDetailItems();
primaryItems.forEach((item, index) => {
expect(actualPrimaryItems[index].itemTitle).toBe(item.title);
expect(actualPrimaryItems[index].itemValue).toBe(mockNodeDetails[item.valueProp]);
});
});
});
describe('getSecondaryNodeDetailItems', () => {
const secondaryItems = [
{
title: 'Repository verification progress',
valueProp: 'verifiedRepositories',
},
{
title: 'Wiki verification progress',
valueProp: 'verifiedWikis',
},
];
it('returns array containing items to show under secondary node', () => {
const actualSecondaryItems = wrapper.vm.getSecondaryNodeDetailItems();
secondaryItems.forEach((item, index) => {
expect(actualSecondaryItems[index].itemTitle).toBe(item.title);
expect(actualSecondaryItems[index].itemValue).toBe(mockNodeDetails[item.valueProp]);
});
});
});
});
......@@ -111,6 +65,50 @@ describe('NodeDetailsSectionVerification', () => {
});
});
describe('methods', () => {
describe.each`
primaryNode | dataKey | nodeDetailItem
${true} | ${'checksum'} | ${{ itemValue: { checksumSuccessCount: 20, checksumFailureCount: 10, verificationSuccessCount: 30, verificationFailureCount: 15 } }}
${false} | ${'verification'} | ${{ itemValue: { totalCount: 100, checksumSuccessCount: 20, checksumFailureCount: 10, verificationSuccessCount: 30, verificationFailureCount: 15 } }}
`(`itemValue`, ({ primaryNode, dataKey, nodeDetailItem }) => {
describe(`when node is ${primaryNode ? 'primary' : 'secondary'}`, () => {
let itemValue = {};
beforeEach(() => {
createComponent({ nodeTypePrimary: primaryNode });
itemValue = wrapper.vm.itemValue(nodeDetailItem);
});
it(`gets successCount correctly`, () => {
expect(itemValue.successCount).toBe(nodeDetailItem.itemValue[`${dataKey}SuccessCount`]);
});
it(`gets failureCount correctly`, () => {
expect(itemValue.failureCount).toBe(nodeDetailItem.itemValue[`${dataKey}FailureCount`]);
});
});
});
describe.each`
primaryNode | itemTitle | titlePostfix
${true} | ${'test'} | ${'checksum progress'}
${false} | ${'test'} | ${'verification progress'}
`(`itemTitle`, ({ primaryNode, itemTitle, titlePostfix }) => {
describe(`when node is ${primaryNode ? 'primary' : 'secondary'}`, () => {
let title = '';
beforeEach(() => {
createComponent({ nodeTypePrimary: primaryNode });
title = wrapper.vm.itemTitle({ itemTitle });
});
it(`creates full title correctly`, () => {
expect(title).toBe(`${itemTitle} ${titlePostfix}`);
});
});
});
});
describe('template', () => {
it('renders component container element', () => {
expect(wrapper.vm.$el.classList.contains('verification-section')).toBe(true);
......@@ -143,5 +141,29 @@ describe('NodeDetailsSectionVerification', () => {
).toContain('Replicated data is verified');
});
});
describe('GeoNodeDetailItems', () => {
describe('on Primary node', () => {
beforeEach(() => {
createComponent({ nodeTypePrimary: true });
wrapper.vm.showSectionItems = true;
});
it('renders the checksum data', () => {
expect(findDetailItems()).toHaveLength(mockNodeDetails.checksumStatuses.length);
});
});
describe('on Secondary node', () => {
beforeEach(() => {
createComponent({ nodeTypePrimary: false });
wrapper.vm.showSectionItems = true;
});
it('renders the verification data', () => {
expect(findDetailItems()).toHaveLength(mockNodeDetails.verificationStatuses.length);
});
});
});
});
});
......@@ -182,66 +182,129 @@ export const mockNodeDetails = {
successCount: 1,
failureCount: 0,
},
repositories: {
totalCount: 12,
successCount: 12,
failureCount: 0,
},
wikis: {
totalCount: 12,
successCount: 12,
failureCount: 0,
},
lfs: {
totalCount: 0,
successCount: 0,
failureCount: 0,
},
jobArtifacts: {
totalCount: 0,
successCount: 0,
failureCount: 0,
},
containerRepositories: {
totalCount: 0,
successCount: 0,
failureCount: 0,
},
designRepositories: {
totalCount: 0,
successCount: 0,
failureCount: 0,
},
packageFiles: {
totalCount: 25,
successCount: 25,
failureCount: 0,
},
attachments: {
totalCount: 0,
successCount: 0,
failureCount: 0,
},
repositoriesChecksummed: {
totalCount: 12,
successCount: 12,
failureCount: 0,
},
wikisChecksummed: {
totalCount: 12,
successCount: 12,
failureCount: 0,
},
verifiedRepositories: {
totalCount: 12,
successCount: 12,
failureCount: 0,
},
verifiedWikis: {
totalCount: 12,
successCount: 12,
failureCount: 0,
},
syncStatuses: [
{
itemEnabled: true,
itemTitle: 'Repositories',
itemName: 'repositories',
itemValue: {
totalCount: 12,
successCount: 12,
failureCount: 0,
verificationSuccessCount: 12,
verificationFailureCount: 0,
checksumSuccessCount: 12,
checksumFailureCount: 0,
},
secondaryView: true,
},
{
itemEnabled: true,
itemTitle: 'Wikis',
itemName: 'wikis',
itemValue: {
totalCount: 12,
successCount: 12,
failureCount: 0,
verificationSuccessCount: 12,
verificationFailureCount: 0,
checksumSuccessCount: 12,
checksumFailureCount: 0,
},
},
{
itemEnabled: true,
itemTitle: 'Designs',
itemName: 'designs',
itemValue: {
totalCount: 25,
successCount: 0,
failureCount: 25,
verificationSuccessCount: null,
verificationFailureCount: null,
checksumSuccessCount: null,
checksumFailureCount: null,
},
secondaryView: true,
},
{
itemEnabled: true,
itemTitle: 'Package Files',
itemName: 'packageFiles',
itemValue: {
totalCount: 20,
successCount: 12,
failureCount: 8,
verificationSuccessCount: null,
verificationFailureCount: null,
checksumSuccessCount: null,
checksumFailureCount: null,
},
secondaryView: true,
},
],
verificationStatuses: [
{
itemEnabled: true,
itemTitle: 'Repositories',
itemName: 'repositories',
itemValue: {
totalCount: 12,
successCount: 12,
failureCount: 0,
verificationSuccessCount: 12,
verificationFailureCount: 0,
checksumSuccessCount: 12,
checksumFailureCount: 0,
},
secondaryView: true,
},
{
itemEnabled: true,
itemTitle: 'Wikis',
itemName: 'wikis',
itemValue: {
totalCount: 12,
successCount: 12,
failureCount: 0,
verificationSuccessCount: 12,
verificationFailureCount: 0,
checksumSuccessCount: 12,
checksumFailureCount: 0,
},
},
],
checksumStatuses: [
{
itemEnabled: true,
itemTitle: 'Repositories',
itemName: 'repositories',
itemValue: {
totalCount: 12,
successCount: 12,
failureCount: 0,
verificationSuccessCount: 12,
verificationFailureCount: 0,
checksumSuccessCount: 12,
checksumFailureCount: 0,
},
secondaryView: true,
},
{
itemEnabled: true,
itemTitle: 'Wikis',
itemName: 'wikis',
itemValue: {
totalCount: 12,
successCount: 12,
failureCount: 0,
verificationSuccessCount: 12,
verificationFailureCount: 0,
checksumSuccessCount: 12,
checksumFailureCount: 0,
},
},
],
lastEvent: {
id: 3,
timeStamp: 1511255200,
......@@ -254,3 +317,32 @@ export const mockNodeDetails = {
namespaces: [],
dbReplicationLag: 0,
};
export const MOCK_REPLICABLE_TYPES = [
{
title: 'Repository',
titlePlural: 'Repositories',
name: 'repository',
namePlural: 'repositories',
secondaryView: true,
},
{
title: 'Wiki',
titlePlural: 'Wikis',
name: 'wiki',
namePlural: 'wikis',
},
{
title: 'Design',
titlePlural: 'Designs',
name: 'design',
name_plural: 'designs',
secondaryView: true,
},
{
title: 'Package File',
titlePlural: 'Package Files',
name: 'package_file',
namePlural: 'package_files',
},
];
import GeoNodesStore from 'ee/geo_nodes/store/geo_nodes_store';
import { mockNodes, rawMockNodeDetails, mockNodeDetails } from '../mock_data';
import {
mockNodes,
rawMockNodeDetails,
mockNodeDetails,
MOCK_REPLICABLE_TYPES,
} from '../mock_data';
describe('GeoNodesStore', () => {
let store;
beforeEach(() => {
store = new GeoNodesStore(mockNodeDetails.primaryVersion, mockNodeDetails.primaryRevision);
store = new GeoNodesStore(
mockNodeDetails.primaryVersion,
mockNodeDetails.primaryRevision,
MOCK_REPLICABLE_TYPES,
);
});
describe('constructor', () => {
......@@ -16,6 +25,7 @@ describe('GeoNodesStore', () => {
expect(typeof store.state.nodeDetails).toBe('object');
expect(store.state.primaryVersion).toBe(mockNodeDetails.primaryVersion);
expect(store.state.primaryRevision).toBe(mockNodeDetails.primaryRevision);
expect(store.state.replicableTypes).toBe(MOCK_REPLICABLE_TYPES);
});
});
......@@ -60,12 +70,20 @@ describe('GeoNodesStore', () => {
describe('formatNodeDetails', () => {
it('returns formatted raw node details object', () => {
const nodeDetails = GeoNodesStore.formatNodeDetails(rawMockNodeDetails);
const nodeDetails = GeoNodesStore.formatNodeDetails(
rawMockNodeDetails,
store.state.replicableTypes,
);
expect(nodeDetails.healthStatus).toBe(rawMockNodeDetails.health_status);
expect(nodeDetails.replicationSlotWAL).toBe(
rawMockNodeDetails.replication_slots_max_retained_wal_bytes,
);
const syncStatusNames = nodeDetails.syncStatuses.map(({ namePlural }) => namePlural);
const replicableTypesNames = store.state.replicableTypes.map(({ namePlural }) => namePlural);
expect(syncStatusNames).toEqual(replicableTypesNames);
});
});
});
......@@ -28,7 +28,7 @@ RSpec.describe EE::GeoHelper do
expected_names = %w(
repositories
wikis
lfs
lfs_objects
attachments
job_artifacts
container_repositories
......
......@@ -10948,30 +10948,15 @@ msgstr ""
msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
msgstr ""
msgid "GeoNodes|Attachments"
msgstr ""
msgid "GeoNodes|Checksummed"
msgstr ""
msgid "GeoNodes|Consult Geo troubleshooting information"
msgstr ""
msgid "GeoNodes|Container repositories"
msgstr ""
msgid "GeoNodes|Data replication lag"
msgstr ""
msgid "GeoNodes|Design repositories"
msgstr ""
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
msgid "GeoNodes|Failed"
msgstr ""
msgid "GeoNodes|Full"
msgstr ""
......@@ -10987,12 +10972,6 @@ msgstr ""
msgid "GeoNodes|Internal URL"
msgstr ""
msgid "GeoNodes|Job artifacts"
msgstr ""
msgid "GeoNodes|LFS objects"
msgstr ""
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
......@@ -11020,12 +10999,6 @@ msgstr ""
msgid "GeoNodes|Node's status was updated %{timeAgo}."
msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
msgid "GeoNodes|Package files"
msgstr ""
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
......@@ -11047,15 +11020,6 @@ msgstr ""
msgid "GeoNodes|Replication status"
msgstr ""
msgid "GeoNodes|Repositories"
msgstr ""
msgid "GeoNodes|Repository checksum progress"
msgstr ""
msgid "GeoNodes|Repository verification progress"
msgstr ""
msgid "GeoNodes|Selective (%{syncLabel})"
msgstr ""
......@@ -11083,27 +11047,12 @@ msgstr ""
msgid "GeoNodes|Unused slots"
msgstr ""
msgid "GeoNodes|Unverified"
msgstr ""
msgid "GeoNodes|Updated %{timeAgo}"
msgstr ""
msgid "GeoNodes|Used slots"
msgstr ""
msgid "GeoNodes|Verified"
msgstr ""
msgid "GeoNodes|Wiki checksum progress"
msgstr ""
msgid "GeoNodes|Wiki verification progress"
msgstr ""
msgid "GeoNodes|Wikis"
msgstr ""
msgid "GeoNodes|With %{geo} you can install a special read-only and replicated instance anywhere. Before you add nodes, follow the %{instructions} in the exact order they appear."
msgstr ""
......@@ -11116,6 +11065,12 @@ msgstr ""
msgid "GeoNodes|secondary nodes"
msgstr ""
msgid "Geo|%{itemTitle} checksum progress"
msgstr ""
msgid "Geo|%{itemTitle} verification progress"
msgstr ""
msgid "Geo|%{label} can't be blank"
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