Commit 92571fb2 authored by Miguel Rincon's avatar Miguel Rincon

Merge branch '228722-generic-node-status-bars' into 'master'

Geo - Generic Node Status Bars

Closes #228722

See merge request gitlab-org/gitlab!37831
parents 745f2de1 9d48764b
......@@ -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', () => {
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();
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: {
syncStatuses: [
{
itemEnabled: true,
itemTitle: 'Repositories',
itemName: 'repositories',
itemValue: {
totalCount: 12,
successCount: 12,
failureCount: 0,
verificationSuccessCount: 12,
verificationFailureCount: 0,
checksumSuccessCount: 12,
checksumFailureCount: 0,
},
secondaryView: true,
},
wikis: {
{
itemEnabled: true,
itemTitle: 'Wikis',
itemName: 'wikis',
itemValue: {
totalCount: 12,
successCount: 12,
failureCount: 0,
verificationSuccessCount: 12,
verificationFailureCount: 0,
checksumSuccessCount: 12,
checksumFailureCount: 0,
},
lfs: {
totalCount: 0,
successCount: 0,
failureCount: 0,
},
jobArtifacts: {
totalCount: 0,
successCount: 0,
failureCount: 0,
},
containerRepositories: {
totalCount: 0,
{
itemEnabled: true,
itemTitle: 'Designs',
itemName: 'designs',
itemValue: {
totalCount: 25,
successCount: 0,
failureCount: 0,
failureCount: 25,
verificationSuccessCount: null,
verificationFailureCount: null,
checksumSuccessCount: null,
checksumFailureCount: null,
},
designRepositories: {
totalCount: 0,
successCount: 0,
failureCount: 0,
secondaryView: true,
},
packageFiles: {
totalCount: 25,
successCount: 25,
failureCount: 0,
{
itemEnabled: true,
itemTitle: 'Package Files',
itemName: 'packageFiles',
itemValue: {
totalCount: 20,
successCount: 12,
failureCount: 8,
verificationSuccessCount: null,
verificationFailureCount: null,
checksumSuccessCount: null,
checksumFailureCount: null,
},
attachments: {
totalCount: 0,
successCount: 0,
failureCount: 0,
secondaryView: true,
},
repositoriesChecksummed: {
],
verificationStatuses: [
{
itemEnabled: true,
itemTitle: 'Repositories',
itemName: 'repositories',
itemValue: {
totalCount: 12,
successCount: 12,
failureCount: 0,
verificationSuccessCount: 12,
verificationFailureCount: 0,
checksumSuccessCount: 12,
checksumFailureCount: 0,
},
secondaryView: true,
},
wikisChecksummed: {
{
itemEnabled: true,
itemTitle: 'Wikis',
itemName: 'wikis',
itemValue: {
totalCount: 12,
successCount: 12,
failureCount: 0,
verificationSuccessCount: 12,
verificationFailureCount: 0,
checksumSuccessCount: 12,
checksumFailureCount: 0,
},
verifiedRepositories: {
},
],
checksumStatuses: [
{
itemEnabled: true,
itemTitle: 'Repositories',
itemName: 'repositories',
itemValue: {
totalCount: 12,
successCount: 12,
failureCount: 0,
verificationSuccessCount: 12,
verificationFailureCount: 0,
checksumSuccessCount: 12,
checksumFailureCount: 0,
},
secondaryView: true,
},
verifiedWikis: {
{
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
......
......@@ -10963,30 +10963,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 ""
......@@ -11002,12 +10987,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 ""
......@@ -11035,12 +11014,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 ""
......@@ -11062,15 +11035,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 ""
......@@ -11098,27 +11062,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 ""
......@@ -11131,6 +11080,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