Commit 46b67b33 authored by Zack Cuddy's avatar Zack Cuddy

Disabled vs Empty Geo Sync

Currenlty in the UI we do not differentiate
between when a sync is empty (0) and when
a sync is actually disabled.

This MR changes that by first exposing
the data from the API to tell when
a feature is disabled. Then reflecting that
in the UI.
parent 57bfac17
<script> <script>
import { __ } from '~/locale';
import { roundOffFloat } from '~/lib/utils/common_utils'; import { roundOffFloat } from '~/lib/utils/common_utils';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
...@@ -27,6 +28,11 @@ export default { ...@@ -27,6 +28,11 @@ export default {
required: false, required: false,
default: 'neutral', default: 'neutral',
}, },
unavailableLabel: {
type: String,
required: false,
default: __('Not available'),
},
successCount: { successCount: {
type: Number, type: Number,
required: true, required: true,
...@@ -103,7 +109,7 @@ export default { ...@@ -103,7 +109,7 @@ export default {
<template> <template>
<div :class="cssClass" class="stacked-progress-bar"> <div :class="cssClass" class="stacked-progress-bar">
<span v-if="!totalCount" class="status-unavailable"> {{ __('Not available') }} </span> <span v-if="!totalCount" class="status-unavailable">{{ unavailableLabel }}</span>
<span <span
v-if="successPercent" v-if="successPercent"
v-tooltip v-tooltip
......
<script> <script>
import { GlIcon, GlPopover, GlLink } from '@gitlab/ui';
import popover from '~/vue_shared/directives/popover'; import popover from '~/vue_shared/directives/popover';
import { VALUE_TYPE, CUSTOM_TYPE } from '../constants'; import { VALUE_TYPE, CUSTOM_TYPE, REPLICATION_HELP_URL } from '../constants';
import GeoNodeSyncSettings from './geo_node_sync_settings.vue'; import GeoNodeSyncSettings from './geo_node_sync_settings.vue';
import GeoNodeEventStatus from './geo_node_event_status.vue'; import GeoNodeEventStatus from './geo_node_event_status.vue';
...@@ -12,6 +13,9 @@ export default { ...@@ -12,6 +13,9 @@ export default {
GeoNodeSyncSettings, GeoNodeSyncSettings,
GeoNodeEventStatus, GeoNodeEventStatus,
GeoNodeSyncProgress, GeoNodeSyncProgress,
GlIcon,
GlPopover,
GlLink,
}, },
directives: { directives: {
popover, popover,
...@@ -26,6 +30,11 @@ export default { ...@@ -26,6 +30,11 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
itemEnabled: {
type: Boolean,
required: false,
default: true,
},
itemValue: { itemValue: {
type: [Object, String, Number], type: [Object, String, Number],
required: true, required: true,
...@@ -54,11 +63,6 @@ export default { ...@@ -54,11 +63,6 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
featureDisabled: {
type: Boolean,
required: false,
default: false,
},
detailsPath: { detailsPath: {
type: String, type: String,
required: false, required: false,
...@@ -82,19 +86,22 @@ export default { ...@@ -82,19 +86,22 @@ export default {
return this.customType === CUSTOM_TYPE.SYNC; return this.customType === CUSTOM_TYPE.SYNC;
}, },
}, },
replicationHelpUrl: REPLICATION_HELP_URL,
}; };
</script> </script>
<template> <template>
<div v-if="!featureDisabled" class="mt-2 ml-2 node-detail-item"> <div class="mt-2 ml-2 node-detail-item">
<div class="d-flex align-items-center text-secondary-700"> <div class="d-flex align-items-center text-secondary-700">
<span class="node-detail-title">{{ itemTitle }}</span> <span class="node-detail-title">{{ itemTitle }}</span>
</div> </div>
<div v-if="itemEnabled">
<div v-if="isValueTypePlain" :class="cssClass" class="mt-1 node-detail-value"> <div v-if="isValueTypePlain" :class="cssClass" class="mt-1 node-detail-value">
{{ itemValue }} {{ itemValue }}
</div> </div>
<geo-node-sync-progress <geo-node-sync-progress
v-if="isValueTypeGraph" v-if="isValueTypeGraph"
:item-enabled="itemEnabled"
:item-title="itemTitle" :item-title="itemTitle"
:item-value="itemValue" :item-value="itemValue"
:item-value-stale="itemValueStale" :item-value-stale="itemValueStale"
...@@ -104,13 +111,7 @@ export default { ...@@ -104,13 +111,7 @@ export default {
class="mt-1" class="mt-1"
/> />
<template v-if="isValueTypeCustom"> <template v-if="isValueTypeCustom">
<geo-node-sync-settings <geo-node-sync-settings v-if="isCustomTypeSync" v-bind="itemValue" />
v-if="isCustomTypeSync"
:sync-status-unavailable="itemValue.syncStatusUnavailable"
:selective-sync-type="itemValue.selectiveSyncType"
:last-event="itemValue.lastEvent"
:cursor-last-event="itemValue.cursorLastEvent"
/>
<geo-node-event-status <geo-node-event-status
v-else v-else
:event-id="itemValue.eventId" :event-id="itemValue.eventId"
...@@ -119,4 +120,34 @@ export default { ...@@ -119,4 +120,34 @@ export default {
/> />
</template> </template>
</div> </div>
<div v-else class="mt-1">
<div
:id="`syncDisabled-${itemTitle}`"
class="d-inline-flex align-items-center cursor-pointer"
>
<gl-icon name="canceled-circle" :size="14" class="mr-1 text-secondary-300" />
<span ref="disabledText" class="text-secondary-600 gl-font-size-small">{{
__('Synchronization disabled')
}}</span>
</div>
<gl-popover
:target="`syncDisabled-${itemTitle}`"
placement="right"
triggers="hover focus"
:css-classes="['w-100']"
>
<section>
<p>{{ __('Synchronization of container repositories is disabled.') }}</p>
<div class="mt-3">
<gl-link
class="gl-font-size-small"
:href="$options.replicationHelpUrl"
target="_blank"
>{{ __('Learn how to enable synchronization') }}</gl-link
>
</div>
</section>
</gl-popover>
</div>
</div>
</template> </template>
...@@ -58,6 +58,7 @@ export default { ...@@ -58,6 +58,7 @@ export default {
tabindex="0" tabindex="0"
:css-class="itemValueStale ? 'flex-fill' : ''" :css-class="itemValueStale ? 'flex-fill' : ''"
:hide-tooltips="true" :hide-tooltips="true"
:unavailable-label="__('Nothing to synchronize')"
:success-count="itemValue.successCount" :success-count="itemValue.successCount"
:failure-count="itemValue.failureCount" :failure-count="itemValue.failureCount"
:total-count="itemValue.totalCount" :total-count="itemValue.totalCount"
......
...@@ -36,42 +36,48 @@ export default { ...@@ -36,42 +36,48 @@ export default {
customType: CUSTOM_TYPE.SYNC, customType: CUSTOM_TYPE.SYNC,
}, },
{ {
itemEnabled: this.nodeDetails.repositories.enabled,
itemTitle: s__('GeoNodes|Repositories'), itemTitle: s__('GeoNodes|Repositories'),
itemValue: this.nodeDetails.repositories, itemValue: this.nodeDetails.repositories,
itemValueType: VALUE_TYPE.GRAPH, itemValueType: VALUE_TYPE.GRAPH,
detailsPath: `${this.node.url}admin/geo/projects`, detailsPath: `${this.node.url}admin/geo/projects`,
}, },
{ {
itemEnabled: this.nodeDetails.repositories.enabled,
itemTitle: s__('GeoNodes|Wikis'), itemTitle: s__('GeoNodes|Wikis'),
itemValue: this.nodeDetails.wikis, itemValue: this.nodeDetails.wikis,
itemValueType: VALUE_TYPE.GRAPH, itemValueType: VALUE_TYPE.GRAPH,
}, },
{ {
itemEnabled: this.nodeDetails.lfs.enabled,
itemTitle: s__('GeoNodes|LFS objects'), itemTitle: s__('GeoNodes|LFS objects'),
itemValue: this.nodeDetails.lfs, itemValue: this.nodeDetails.lfs,
itemValueType: VALUE_TYPE.GRAPH, itemValueType: VALUE_TYPE.GRAPH,
}, },
{ {
itemEnabled: this.nodeDetails.attachments.enabled,
itemTitle: s__('GeoNodes|Attachments'), itemTitle: s__('GeoNodes|Attachments'),
itemValue: this.nodeDetails.attachments, itemValue: this.nodeDetails.attachments,
itemValueType: VALUE_TYPE.GRAPH, itemValueType: VALUE_TYPE.GRAPH,
detailsPath: `${this.node.url}admin/geo/uploads`, detailsPath: `${this.node.url}admin/geo/uploads`,
}, },
{ {
itemEnabled: this.nodeDetails.jobArtifacts.enabled,
itemTitle: s__('GeoNodes|Job artifacts'), itemTitle: s__('GeoNodes|Job artifacts'),
itemValue: this.nodeDetails.jobArtifacts, itemValue: this.nodeDetails.jobArtifacts,
itemValueType: VALUE_TYPE.GRAPH, itemValueType: VALUE_TYPE.GRAPH,
}, },
{ {
itemEnabled: this.nodeDetails.containerRepositories.enabled,
itemTitle: s__('GeoNodes|Container repositories'), itemTitle: s__('GeoNodes|Container repositories'),
itemValue: this.nodeDetails.containerRepositories, itemValue: this.nodeDetails.containerRepositories,
itemValueType: VALUE_TYPE.GRAPH, itemValueType: VALUE_TYPE.GRAPH,
}, },
{ {
itemEnabled: this.nodeDetails.designRepositories.enabled,
itemTitle: s__('GeoNodes|Design repositories'), itemTitle: s__('GeoNodes|Design repositories'),
itemValue: this.nodeDetails.designRepositories, itemValue: this.nodeDetails.designRepositories,
itemValueType: VALUE_TYPE.GRAPH, itemValueType: VALUE_TYPE.GRAPH,
featureDisabled: !gon.features.enableGeoDesignSync,
detailsPath: `${this.node.url}admin/geo/designs`, detailsPath: `${this.node.url}admin/geo/designs`,
}, },
{ {
...@@ -149,6 +155,7 @@ export default { ...@@ -149,6 +155,7 @@ export default {
v-for="(nodeDetailItem, index) in nodeDetailItems" v-for="(nodeDetailItem, index) in nodeDetailItems"
:key="index" :key="index"
:css-class="nodeDetailItem.cssClass" :css-class="nodeDetailItem.cssClass"
:item-enabled="nodeDetailItem.itemEnabled"
:item-title="nodeDetailItem.itemTitle" :item-title="nodeDetailItem.itemTitle"
:item-value="nodeDetailItem.itemValue" :item-value="nodeDetailItem.itemValue"
:item-value-type="nodeDetailItem.itemValueType" :item-value-type="nodeDetailItem.itemValueType"
...@@ -156,7 +163,6 @@ export default { ...@@ -156,7 +163,6 @@ export default {
:item-value-stale-tooltip="statusInfoStaleMessage" :item-value-stale-tooltip="statusInfoStaleMessage"
:custom-type="nodeDetailItem.customType" :custom-type="nodeDetailItem.customType"
:event-type-log-status="nodeDetailItem.eventTypeLogStatus" :event-type-log-status="nodeDetailItem.eventTypeLogStatus"
:feature-disabled="nodeDetailItem.featureDisabled"
:details-path="nodeDetailItem.detailsPath" :details-path="nodeDetailItem.detailsPath"
/> />
</div> </div>
......
...@@ -40,3 +40,6 @@ export const STATUS_DELAY_THRESHOLD_MS = 60000; ...@@ -40,3 +40,6 @@ export const STATUS_DELAY_THRESHOLD_MS = 60000;
export const HELP_INFO_URL = export const HELP_INFO_URL =
'https://docs.gitlab.com/ee/administration/geo/disaster_recovery/background_verification.html#repository-verification'; 'https://docs.gitlab.com/ee/administration/geo/disaster_recovery/background_verification.html#repository-verification';
export const REPLICATION_HELP_URL =
'https://docs.gitlab.com/ee/administration/geo/replication/datatypes.html#limitations-on-replicationverification';
...@@ -81,11 +81,13 @@ export default class GeoNodesStore { ...@@ -81,11 +81,13 @@ export default class GeoNodesStore {
failureCount: 0, failureCount: 0,
}, },
repositories: { repositories: {
enabled: rawNodeDetails.repositories_replication_enabled,
totalCount: rawNodeDetails.projects_count || 0, totalCount: rawNodeDetails.projects_count || 0,
successCount: rawNodeDetails.repositories_synced_count || 0, successCount: rawNodeDetails.repositories_synced_count || 0,
failureCount: rawNodeDetails.repositories_failed_count || 0, failureCount: rawNodeDetails.repositories_failed_count || 0,
}, },
wikis: { wikis: {
enabled: rawNodeDetails.repositories_replication_enabled,
totalCount: rawNodeDetails.projects_count || 0, totalCount: rawNodeDetails.projects_count || 0,
successCount: rawNodeDetails.wikis_synced_count || 0, successCount: rawNodeDetails.wikis_synced_count || 0,
failureCount: rawNodeDetails.wikis_failed_count || 0, failureCount: rawNodeDetails.wikis_failed_count || 0,
...@@ -111,26 +113,31 @@ export default class GeoNodesStore { ...@@ -111,26 +113,31 @@ export default class GeoNodesStore {
failureCount: rawNodeDetails.wikis_verification_failed_count || 0, failureCount: rawNodeDetails.wikis_verification_failed_count || 0,
}, },
lfs: { lfs: {
enabled: rawNodeDetails.lfs_objects_replication_enabled,
totalCount: rawNodeDetails.lfs_objects_count || 0, totalCount: rawNodeDetails.lfs_objects_count || 0,
successCount: rawNodeDetails.lfs_objects_synced_count || 0, successCount: rawNodeDetails.lfs_objects_synced_count || 0,
failureCount: rawNodeDetails.lfs_objects_failed_count || 0, failureCount: rawNodeDetails.lfs_objects_failed_count || 0,
}, },
jobArtifacts: { jobArtifacts: {
enabled: rawNodeDetails.job_artifacts_replication_enabled,
totalCount: rawNodeDetails.job_artifacts_count || 0, totalCount: rawNodeDetails.job_artifacts_count || 0,
successCount: rawNodeDetails.job_artifacts_synced_count || 0, successCount: rawNodeDetails.job_artifacts_synced_count || 0,
failureCount: rawNodeDetails.job_artifacts_failed_count || 0, failureCount: rawNodeDetails.job_artifacts_failed_count || 0,
}, },
containerRepositories: { containerRepositories: {
enabled: rawNodeDetails.container_repositories_replication_enabled,
totalCount: rawNodeDetails.container_repositories_count || 0, totalCount: rawNodeDetails.container_repositories_count || 0,
successCount: rawNodeDetails.container_repositories_synced_count || 0, successCount: rawNodeDetails.container_repositories_synced_count || 0,
failureCount: rawNodeDetails.container_repositories_failed_count || 0, failureCount: rawNodeDetails.container_repositories_failed_count || 0,
}, },
designRepositories: { designRepositories: {
enabled: rawNodeDetails.design_repositories_replication_enabled,
totalCount: rawNodeDetails.design_repositories_count || 0, totalCount: rawNodeDetails.design_repositories_count || 0,
successCount: rawNodeDetails.design_repositories_synced_count || 0, successCount: rawNodeDetails.design_repositories_synced_count || 0,
failureCount: rawNodeDetails.design_repositories_failed_count || 0, failureCount: rawNodeDetails.design_repositories_failed_count || 0,
}, },
attachments: { attachments: {
enabled: rawNodeDetails.attachments_replication_enabled,
totalCount: rawNodeDetails.attachments_count || 0, totalCount: rawNodeDetails.attachments_count || 0,
successCount: rawNodeDetails.attachments_synced_count || 0, successCount: rawNodeDetails.attachments_synced_count || 0,
failureCount: rawNodeDetails.attachments_failed_count || 0, failureCount: rawNodeDetails.attachments_failed_count || 0,
......
---
title: Differentiate between empty and disabled Geo sync
merge_request: 28963
author:
type: changed
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlPopover, GlLink } from '@gitlab/ui';
import GeoNodeDetailItemComponent from 'ee/geo_nodes/components/geo_node_detail_item.vue'; import GeoNodeDetailItemComponent from 'ee/geo_nodes/components/geo_node_detail_item.vue';
import GeoNodeSyncSettings from 'ee/geo_nodes/components/geo_node_sync_settings.vue'; import GeoNodeSyncSettings from 'ee/geo_nodes/components/geo_node_sync_settings.vue';
import GeoNodeEventStatus from 'ee/geo_nodes/components/geo_node_event_status.vue'; import GeoNodeEventStatus from 'ee/geo_nodes/components/geo_node_event_status.vue';
import GeoNodeSyncProgress from 'ee/geo_nodes/components/geo_node_sync_progress.vue'; import GeoNodeSyncProgress from 'ee/geo_nodes/components/geo_node_sync_progress.vue';
import { VALUE_TYPE, CUSTOM_TYPE } from 'ee/geo_nodes/constants'; import { VALUE_TYPE, CUSTOM_TYPE, REPLICATION_HELP_URL } from 'ee/geo_nodes/constants';
import { rawMockNodeDetails } from '../mock_data'; import { rawMockNodeDetails } from '../mock_data';
describe('GeoNodeDetailItemComponent', () => { describe('GeoNodeDetailItemComponent', () => {
...@@ -127,16 +128,51 @@ describe('GeoNodeDetailItemComponent', () => { ...@@ -127,16 +128,51 @@ describe('GeoNodeDetailItemComponent', () => {
expect(wrapper.find(GeoNodeSyncProgress).exists()).toBeFalsy(); expect(wrapper.find(GeoNodeSyncProgress).exists()).toBeFalsy();
}); });
}); });
});
describe('itemEnabled', () => {
describe('when false', () => {
beforeEach(() => {
createComponent({
itemEnabled: false,
});
});
it('renders synchronization disabled text', () => {
expect(
wrapper
.find({ ref: 'disabledText' })
.text()
.trim(),
).toBe('Synchronization disabled');
});
it('renders GlPopover', () => {
expect(wrapper.find(GlPopover).exists()).toBeTruthy();
});
it('renders link to replication help documentation in popover', () => {
const popoverLink = wrapper.find(GlPopover).find(GlLink);
describe('when featureDisabled is true', () => { expect(popoverLink.exists()).toBeTruthy();
expect(popoverLink.text()).toBe('Learn how to enable synchronization');
expect(popoverLink.attributes('href')).toBe(REPLICATION_HELP_URL);
});
});
describe('when true', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
featureDisabled: true, itemEnabled: true,
}); });
}); });
it('does not render', () => { it('does not render synchronization disabled text', () => {
expect(wrapper.vm.$el.innerHTML).toBeUndefined(); expect(wrapper.find('.node-detail-item').text()).not.toContain('Synchronization disabled');
});
it('does not render GlPopover', () => {
expect(wrapper.find(GlPopover).exists()).toBeFalsy();
}); });
}); });
}); });
......
...@@ -11879,6 +11879,9 @@ msgstr "" ...@@ -11879,6 +11879,9 @@ msgstr ""
msgid "Learn how to %{no_packages_link_start}publish and share your packages%{no_packages_link_end} with GitLab." msgid "Learn how to %{no_packages_link_start}publish and share your packages%{no_packages_link_end} with GitLab."
msgstr "" msgstr ""
msgid "Learn how to enable synchronization"
msgstr ""
msgid "Learn more" msgid "Learn more"
msgstr "" msgstr ""
...@@ -13815,6 +13818,9 @@ msgstr "" ...@@ -13815,6 +13818,9 @@ msgstr ""
msgid "Nothing to preview." msgid "Nothing to preview."
msgstr "" msgstr ""
msgid "Nothing to synchronize"
msgstr ""
msgid "Notification events" msgid "Notification events"
msgstr "" msgstr ""
...@@ -19845,6 +19851,12 @@ msgstr "" ...@@ -19845,6 +19851,12 @@ msgstr ""
msgid "Synced" msgid "Synced"
msgstr "" msgstr ""
msgid "Synchronization disabled"
msgstr ""
msgid "Synchronization of container repositories is disabled."
msgstr ""
msgid "System" msgid "System"
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