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 { ...@@ -43,7 +43,8 @@ export default {
}, },
itemValueType: { itemValueType: {
type: String, type: String,
required: true, required: false,
default: VALUE_TYPE.GRAPH,
}, },
customType: { customType: {
type: String, type: String,
......
<script> <script>
import { GlPopover, GlSprintf, GlLink } from '@gitlab/ui'; import { GlPopover, GlSprintf, GlLink } from '@gitlab/ui';
import { toNumber } from 'lodash';
import StackedProgressBar from '~/vue_shared/components/stacked_progress_bar.vue'; import StackedProgressBar from '~/vue_shared/components/stacked_progress_bar.vue';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
...@@ -22,8 +23,6 @@ export default { ...@@ -22,8 +23,6 @@ export default {
itemValue: { itemValue: {
type: Object, type: Object,
required: true, required: true,
validator: value =>
['totalCount', 'successCount', 'failureCount'].every(key => typeof value[key] === 'number'),
}, },
detailsPath: { detailsPath: {
type: String, type: String,
...@@ -33,7 +32,16 @@ export default { ...@@ -33,7 +32,16 @@ export default {
}, },
computed: { computed: {
queuedCount() { 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 { ...@@ -46,9 +54,9 @@ export default {
tabindex="0" tabindex="0"
:hide-tooltips="true" :hide-tooltips="true"
:unavailable-label="__('Nothing to synchronize')" :unavailable-label="__('Nothing to synchronize')"
:success-count="itemValue.successCount" :success-count="successCount"
:failure-count="itemValue.failureCount" :failure-count="failureCount"
:total-count="itemValue.totalCount" :total-count="totalCount"
/> />
<gl-popover <gl-popover
:target="`syncProgress-${itemTitle}`" :target="`syncProgress-${itemTitle}`"
...@@ -67,12 +75,12 @@ export default { ...@@ -67,12 +75,12 @@ export default {
<div class="d-flex align-items-center my-1"> <div class="d-flex align-items-center my-1">
<div class="mr-2 bg-transparent gl-w-5 gl-h-2"></div> <div class="mr-2 bg-transparent gl-w-5 gl-h-2"></div>
<span class="flex-grow-1 mr-3">{{ __('Total') }}</span> <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>
<div class="d-flex align-items-center my-2"> <div class="d-flex align-items-center my-2">
<div class="mr-2 bg-success-500 gl-w-5 gl-h-2"></div> <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="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>
<div class="d-flex align-items-center my-2"> <div class="d-flex align-items-center my-2">
<div class="mr-2 bg-secondary-200 gl-w-5 gl-h-2"></div> <div class="mr-2 bg-secondary-200 gl-w-5 gl-h-2"></div>
...@@ -82,7 +90,7 @@ export default { ...@@ -82,7 +90,7 @@ export default {
<div class="d-flex align-items-center my-2"> <div class="d-flex align-items-center my-2">
<div class="mr-2 bg-danger-500 gl-w-5 gl-h-2"></div> <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="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>
<div v-if="detailsPath" class="mt-3"> <div v-if="detailsPath" class="mt-3">
<gl-link class="gl-font-sm" :href="detailsPath" target="_blank">{{ <gl-link class="gl-font-sm" :href="detailsPath" target="_blank">{{
......
...@@ -32,56 +32,7 @@ export default { ...@@ -32,56 +32,7 @@ export default {
itemValueType: VALUE_TYPE.CUSTOM, itemValueType: VALUE_TYPE.CUSTOM,
customType: CUSTOM_TYPE.SYNC, customType: CUSTOM_TYPE.SYNC,
}, },
{ ...this.nodeDetails.syncStatuses,
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,
},
{ {
itemTitle: s__('GeoNodes|Data replication lag'), itemTitle: s__('GeoNodes|Data replication lag'),
itemValue: this.dbReplicationLag(), itemValue: this.dbReplicationLag(),
...@@ -140,6 +91,22 @@ export default { ...@@ -140,6 +91,22 @@ export default {
handleSectionToggle(toggleState) { handleSectionToggle(toggleState) {
this.showSectionItems = 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> </script>
...@@ -163,7 +130,7 @@ export default { ...@@ -163,7 +130,7 @@ export default {
:item-value-type="nodeDetailItem.itemValueType" :item-value-type="nodeDetailItem.itemValueType"
:custom-type="nodeDetailItem.customType" :custom-type="nodeDetailItem.customType"
:event-type-log-status="nodeDetailItem.eventTypeLogStatus" :event-type-log-status="nodeDetailItem.eventTypeLogStatus"
:details-path="nodeDetailItem.detailsPath" :details-path="detailsPath(nodeDetailItem)"
/> />
</div> </div>
</div> </div>
......
<script> <script>
import { GlPopover, GlLink, GlIcon, GlSprintf } from '@gitlab/ui'; 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 GeoNodeDetailItem from '../geo_node_detail_item.vue';
import SectionRevealButton from './section_reveal_button.vue'; import SectionRevealButton from './section_reveal_button.vue';
...@@ -30,64 +30,42 @@ export default { ...@@ -30,64 +30,42 @@ export default {
data() { data() {
return { return {
showSectionItems: false, showSectionItems: false,
primaryNodeDetailItems: this.getPrimaryNodeDetailItems(),
secondaryNodeDetailItems: this.getSecondaryNodeDetailItems(),
}; };
}, },
computed: { computed: {
nodeDetailItems() { nodeDetailItems() {
return this.nodeTypePrimary return this.nodeTypePrimary
? this.getPrimaryNodeDetailItems() ? this.nodeDetails.checksumStatuses
: this.getSecondaryNodeDetailItems(); : this.nodeDetails.verificationStatuses;
}, },
nodeText() { nodeText() {
return this.nodeTypePrimary ? s__('GeoNodes|secondary nodes') : s__('GeoNodes|primary node'); return this.nodeTypePrimary ? s__('GeoNodes|secondary nodes') : s__('GeoNodes|primary node');
}, },
}, },
methods: { 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) { handleSectionToggle(toggleState) {
this.showSectionItems = 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, HELP_INFO_URL,
}; };
...@@ -128,14 +106,8 @@ export default { ...@@ -128,14 +106,8 @@ export default {
<geo-node-detail-item <geo-node-detail-item
v-for="(nodeDetailItem, index) in nodeDetailItems" v-for="(nodeDetailItem, index) in nodeDetailItems"
:key="index" :key="index"
:css-class="nodeDetailItem.cssClass" :item-title="itemTitle(nodeDetailItem)"
:item-title="nodeDetailItem.itemTitle" :item-value="itemValue(nodeDetailItem)"
: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"
/> />
</div> </div>
</template> </template>
......
...@@ -2,7 +2,7 @@ import Vue from 'vue'; ...@@ -2,7 +2,7 @@ import Vue from 'vue';
import { GlToast } from '@gitlab/ui'; import { GlToast } from '@gitlab/ui';
import Translate from '~/vue_shared/translate'; 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 GeoNodesStore from './store/geo_nodes_store';
import GeoNodesService from './service/geo_nodes_service'; import GeoNodesService from './service/geo_nodes_service';
...@@ -27,9 +27,12 @@ export default () => { ...@@ -27,9 +27,12 @@ export default () => {
data() { data() {
const { dataset } = this.$options.el; const { dataset } = this.$options.el;
const { primaryVersion, primaryRevision, geoTroubleshootingHelpPath } = dataset; const { primaryVersion, primaryRevision, geoTroubleshootingHelpPath } = dataset;
const replicableTypes = convertObjectPropsToCamelCase(JSON.parse(dataset.replicableTypes), {
deep: true,
});
const nodeActionsAllowed = parseBoolean(dataset.nodeActionsAllowed); const nodeActionsAllowed = parseBoolean(dataset.nodeActionsAllowed);
const nodeEditAllowed = parseBoolean(dataset.nodeEditAllowed); const nodeEditAllowed = parseBoolean(dataset.nodeEditAllowed);
const store = new GeoNodesStore(primaryVersion, primaryRevision); const store = new GeoNodesStore(primaryVersion, primaryRevision, replicableTypes);
const service = new GeoNodesService(); const service = new GeoNodesService();
return { return {
......
export default class GeoNodesStore { export default class GeoNodesStore {
constructor(primaryVersion, primaryRevision) { constructor(primaryVersion, primaryRevision, replicableTypes) {
this.state = {}; this.state = {};
this.state.nodes = []; this.state.nodes = [];
this.state.nodeDetails = {}; this.state.nodeDetails = {};
this.state.primaryVersion = primaryVersion; this.state.primaryVersion = primaryVersion;
this.state.primaryRevision = primaryRevision; this.state.primaryRevision = primaryRevision;
this.state.replicableTypes = replicableTypes;
} }
setNodes(nodes) { setNodes(nodes) {
...@@ -16,7 +17,10 @@ export default class GeoNodesStore { ...@@ -16,7 +17,10 @@ export default class GeoNodesStore {
} }
setNodeDetails(nodeId, nodeDetails) { setNodeDetails(nodeId, nodeDetails) {
this.state.nodeDetails[nodeId] = GeoNodesStore.formatNodeDetails(nodeDetails); this.state.nodeDetails[nodeId] = GeoNodesStore.formatNodeDetails(
nodeDetails,
this.state.replicableTypes,
);
} }
removeNode(node) { removeNode(node) {
...@@ -60,7 +64,33 @@ export default class GeoNodesStore { ...@@ -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 { return {
id: rawNodeDetails.geo_node_id, id: rawNodeDetails.geo_node_id,
health: rawNodeDetails.health, health: rawNodeDetails.health,
...@@ -81,73 +111,9 @@ export default class GeoNodesStore { ...@@ -81,73 +111,9 @@ export default class GeoNodesStore {
successCount: rawNodeDetails.replication_slots_used_count || 0, successCount: rawNodeDetails.replication_slots_used_count || 0,
failureCount: 0, failureCount: 0,
}, },
repositories: { syncStatuses,
enabled: rawNodeDetails.repositories_replication_enabled, verificationStatuses,
totalCount: rawNodeDetails.projects_count || 0, checksumStatuses,
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,
},
lastEvent: { lastEvent: {
id: rawNodeDetails.last_event_id || 0, id: rawNodeDetails.last_event_id || 0,
timeStamp: rawNodeDetails.last_event_timestamp, timeStamp: rawNodeDetails.last_event_timestamp,
......
...@@ -184,8 +184,8 @@ module EE ...@@ -184,8 +184,8 @@ module EE
{ {
title: _('LFS object'), title: _('LFS object'),
title_plural: _('LFS objects'), title_plural: _('LFS objects'),
name: 'lfs', name: 'lfs_object',
name_plural: 'lfs' name_plural: 'lfs_objects'
}, },
{ {
title: _('Attachment'), title: _('Attachment'),
......
...@@ -16,6 +16,7 @@ import { ...@@ -16,6 +16,7 @@ import {
mockNodes, mockNodes,
mockNode, mockNode,
rawMockNodeDetails, rawMockNodeDetails,
MOCK_REPLICABLE_TYPES,
} from '../mock_data'; } from '../mock_data';
jest.mock('~/smart_interval'); jest.mock('~/smart_interval');
...@@ -23,7 +24,11 @@ jest.mock('ee/geo_nodes/event_hub'); ...@@ -23,7 +24,11 @@ jest.mock('ee/geo_nodes/event_hub');
const createComponent = () => { const createComponent = () => {
const Component = Vue.extend(appComponent); 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); const service = new GeoNodesService(NODE_DETAILS_PATH);
return mountComponent(Component, { return mountComponent(Component, {
......
...@@ -57,11 +57,47 @@ describe('GeoNodeSyncProgress', () => { ...@@ -57,11 +57,47 @@ describe('GeoNodeSyncProgress', () => {
}); });
describe('computed', () => { describe('computed', () => {
beforeEach(() => { describe.each`
createComponent(); 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', () => { describe('queuedCount', () => {
beforeEach(() => {
createComponent();
});
it('returns total - success - failure', () => { it('returns total - success - failure', () => {
expect(wrapper.vm.queuedCount).toEqual(MOCK_ITEM_VALUE.queuedCount); expect(wrapper.vm.queuedCount).toEqual(MOCK_ITEM_VALUE.queuedCount);
}); });
......
...@@ -82,6 +82,26 @@ describe('NodeDetailsSectionSync', () => { ...@@ -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', () => { describe('template', () => {
......
...@@ -3,6 +3,7 @@ import { GlPopover, GlSprintf } from '@gitlab/ui'; ...@@ -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 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 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'; import { mockNodeDetails } from '../../mock_data';
...@@ -32,58 +33,11 @@ describe('NodeDetailsSectionVerification', () => { ...@@ -32,58 +33,11 @@ describe('NodeDetailsSectionVerification', () => {
}); });
const findGlPopover = () => wrapper.find(GlPopover); const findGlPopover = () => wrapper.find(GlPopover);
const findDetailItems = () => wrapper.findAll(GeoNodeDetailItem);
describe('data', () => { describe('data', () => {
it('returns default data props', () => { it('returns default data props', () => {
expect(wrapper.vm.showSectionItems).toBe(false); 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', () => { ...@@ -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', () => { describe('template', () => {
it('renders component container element', () => { it('renders component container element', () => {
expect(wrapper.vm.$el.classList.contains('verification-section')).toBe(true); expect(wrapper.vm.$el.classList.contains('verification-section')).toBe(true);
...@@ -143,5 +141,29 @@ describe('NodeDetailsSectionVerification', () => { ...@@ -143,5 +141,29 @@ describe('NodeDetailsSectionVerification', () => {
).toContain('Replicated data is verified'); ).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 = { ...@@ -182,66 +182,129 @@ export const mockNodeDetails = {
successCount: 1, successCount: 1,
failureCount: 0, failureCount: 0,
}, },
repositories: { syncStatuses: [
totalCount: 12, {
successCount: 12, itemEnabled: true,
failureCount: 0, itemTitle: 'Repositories',
}, itemName: 'repositories',
wikis: { itemValue: {
totalCount: 12, totalCount: 12,
successCount: 12, successCount: 12,
failureCount: 0, failureCount: 0,
}, verificationSuccessCount: 12,
lfs: { verificationFailureCount: 0,
totalCount: 0, checksumSuccessCount: 12,
successCount: 0, checksumFailureCount: 0,
failureCount: 0, },
}, secondaryView: true,
jobArtifacts: { },
totalCount: 0, {
successCount: 0, itemEnabled: true,
failureCount: 0, itemTitle: 'Wikis',
}, itemName: 'wikis',
containerRepositories: { itemValue: {
totalCount: 0, totalCount: 12,
successCount: 0, successCount: 12,
failureCount: 0, failureCount: 0,
}, verificationSuccessCount: 12,
designRepositories: { verificationFailureCount: 0,
totalCount: 0, checksumSuccessCount: 12,
successCount: 0, checksumFailureCount: 0,
failureCount: 0, },
}, },
packageFiles: { {
totalCount: 25, itemEnabled: true,
successCount: 25, itemTitle: 'Designs',
failureCount: 0, itemName: 'designs',
}, itemValue: {
attachments: { totalCount: 25,
totalCount: 0, successCount: 0,
successCount: 0, failureCount: 25,
failureCount: 0, verificationSuccessCount: null,
}, verificationFailureCount: null,
repositoriesChecksummed: { checksumSuccessCount: null,
totalCount: 12, checksumFailureCount: null,
successCount: 12, },
failureCount: 0, secondaryView: true,
}, },
wikisChecksummed: { {
totalCount: 12, itemEnabled: true,
successCount: 12, itemTitle: 'Package Files',
failureCount: 0, itemName: 'packageFiles',
}, itemValue: {
verifiedRepositories: { totalCount: 20,
totalCount: 12, successCount: 12,
successCount: 12, failureCount: 8,
failureCount: 0, verificationSuccessCount: null,
}, verificationFailureCount: null,
verifiedWikis: { checksumSuccessCount: null,
totalCount: 12, checksumFailureCount: null,
successCount: 12, },
failureCount: 0, 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: { lastEvent: {
id: 3, id: 3,
timeStamp: 1511255200, timeStamp: 1511255200,
...@@ -254,3 +317,32 @@ export const mockNodeDetails = { ...@@ -254,3 +317,32 @@ export const mockNodeDetails = {
namespaces: [], namespaces: [],
dbReplicationLag: 0, 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 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', () => { describe('GeoNodesStore', () => {
let store; let store;
beforeEach(() => { beforeEach(() => {
store = new GeoNodesStore(mockNodeDetails.primaryVersion, mockNodeDetails.primaryRevision); store = new GeoNodesStore(
mockNodeDetails.primaryVersion,
mockNodeDetails.primaryRevision,
MOCK_REPLICABLE_TYPES,
);
}); });
describe('constructor', () => { describe('constructor', () => {
...@@ -16,6 +25,7 @@ describe('GeoNodesStore', () => { ...@@ -16,6 +25,7 @@ describe('GeoNodesStore', () => {
expect(typeof store.state.nodeDetails).toBe('object'); expect(typeof store.state.nodeDetails).toBe('object');
expect(store.state.primaryVersion).toBe(mockNodeDetails.primaryVersion); expect(store.state.primaryVersion).toBe(mockNodeDetails.primaryVersion);
expect(store.state.primaryRevision).toBe(mockNodeDetails.primaryRevision); expect(store.state.primaryRevision).toBe(mockNodeDetails.primaryRevision);
expect(store.state.replicableTypes).toBe(MOCK_REPLICABLE_TYPES);
}); });
}); });
...@@ -60,12 +70,20 @@ describe('GeoNodesStore', () => { ...@@ -60,12 +70,20 @@ describe('GeoNodesStore', () => {
describe('formatNodeDetails', () => { describe('formatNodeDetails', () => {
it('returns formatted raw node details object', () => { 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.healthStatus).toBe(rawMockNodeDetails.health_status);
expect(nodeDetails.replicationSlotWAL).toBe( expect(nodeDetails.replicationSlotWAL).toBe(
rawMockNodeDetails.replication_slots_max_retained_wal_bytes, 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 ...@@ -28,7 +28,7 @@ RSpec.describe EE::GeoHelper do
expected_names = %w( expected_names = %w(
repositories repositories
wikis wikis
lfs lfs_objects
attachments attachments
job_artifacts job_artifacts
container_repositories container_repositories
......
...@@ -10948,30 +10948,15 @@ msgstr "" ...@@ -10948,30 +10948,15 @@ msgstr ""
msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
msgstr "" msgstr ""
msgid "GeoNodes|Attachments"
msgstr ""
msgid "GeoNodes|Checksummed"
msgstr ""
msgid "GeoNodes|Consult Geo troubleshooting information" msgid "GeoNodes|Consult Geo troubleshooting information"
msgstr "" msgstr ""
msgid "GeoNodes|Container repositories"
msgstr ""
msgid "GeoNodes|Data replication lag" msgid "GeoNodes|Data replication lag"
msgstr "" msgstr ""
msgid "GeoNodes|Design repositories"
msgstr ""
msgid "GeoNodes|Does not match the primary storage configuration" msgid "GeoNodes|Does not match the primary storage configuration"
msgstr "" msgstr ""
msgid "GeoNodes|Failed"
msgstr ""
msgid "GeoNodes|Full" msgid "GeoNodes|Full"
msgstr "" msgstr ""
...@@ -10987,12 +10972,6 @@ msgstr "" ...@@ -10987,12 +10972,6 @@ msgstr ""
msgid "GeoNodes|Internal URL" msgid "GeoNodes|Internal URL"
msgstr "" msgstr ""
msgid "GeoNodes|Job artifacts"
msgstr ""
msgid "GeoNodes|LFS objects"
msgstr ""
msgid "GeoNodes|Last event ID processed by cursor" msgid "GeoNodes|Last event ID processed by cursor"
msgstr "" msgstr ""
...@@ -11020,12 +10999,6 @@ msgstr "" ...@@ -11020,12 +10999,6 @@ msgstr ""
msgid "GeoNodes|Node's status was updated %{timeAgo}." msgid "GeoNodes|Node's status was updated %{timeAgo}."
msgstr "" msgstr ""
msgid "GeoNodes|Not checksummed"
msgstr ""
msgid "GeoNodes|Package files"
msgstr ""
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?" msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr "" msgstr ""
...@@ -11047,15 +11020,6 @@ msgstr "" ...@@ -11047,15 +11020,6 @@ msgstr ""
msgid "GeoNodes|Replication status" msgid "GeoNodes|Replication status"
msgstr "" msgstr ""
msgid "GeoNodes|Repositories"
msgstr ""
msgid "GeoNodes|Repository checksum progress"
msgstr ""
msgid "GeoNodes|Repository verification progress"
msgstr ""
msgid "GeoNodes|Selective (%{syncLabel})" msgid "GeoNodes|Selective (%{syncLabel})"
msgstr "" msgstr ""
...@@ -11083,27 +11047,12 @@ msgstr "" ...@@ -11083,27 +11047,12 @@ msgstr ""
msgid "GeoNodes|Unused slots" msgid "GeoNodes|Unused slots"
msgstr "" msgstr ""
msgid "GeoNodes|Unverified"
msgstr ""
msgid "GeoNodes|Updated %{timeAgo}" msgid "GeoNodes|Updated %{timeAgo}"
msgstr "" msgstr ""
msgid "GeoNodes|Used slots" msgid "GeoNodes|Used slots"
msgstr "" 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." 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 "" msgstr ""
...@@ -11116,6 +11065,12 @@ msgstr "" ...@@ -11116,6 +11065,12 @@ msgstr ""
msgid "GeoNodes|secondary nodes" msgid "GeoNodes|secondary nodes"
msgstr "" msgstr ""
msgid "Geo|%{itemTitle} checksum progress"
msgstr ""
msgid "Geo|%{itemTitle} verification progress"
msgstr ""
msgid "Geo|%{label} can't be blank" msgid "Geo|%{label} can't be blank"
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