Commit 7edc337f authored by Kushal Pandya's avatar Kushal Pandya

Update layout for Geo nodes admin page

parent 2c84eb5c
...@@ -10,13 +10,13 @@ import eventHub from '../event_hub'; ...@@ -10,13 +10,13 @@ import eventHub from '../event_hub';
import { NODE_ACTIONS } from '../constants'; import { NODE_ACTIONS } from '../constants';
import geoNodesList from './geo_nodes_list.vue'; import GeoNodeItem from './geo_node_item.vue';
export default { export default {
components: { components: {
loadingIcon, loadingIcon,
DeprecatedModal, DeprecatedModal,
geoNodesList, GeoNodeItem,
}, },
props: { props: {
store: { store: {
...@@ -46,7 +46,6 @@ export default { ...@@ -46,7 +46,6 @@ export default {
modalKind: 'warning', modalKind: 'warning',
modalMessage: '', modalMessage: '',
modalActionLabel: '', modalActionLabel: '',
errorMessage: '',
}; };
}, },
computed: { computed: {
...@@ -85,7 +84,6 @@ export default { ...@@ -85,7 +84,6 @@ export default {
}); });
}, },
fetchGeoNodes() { fetchGeoNodes() {
this.hasError = false;
this.service this.service
.getGeoNodes() .getGeoNodes()
.then(res => res.data) .then(res => res.data)
...@@ -93,9 +91,11 @@ export default { ...@@ -93,9 +91,11 @@ export default {
this.store.setNodes(nodes); this.store.setNodes(nodes);
this.isLoading = false; this.isLoading = false;
}) })
.catch(err => { .catch(() => {
this.hasError = true; this.isLoading = false;
this.errorMessage = err; Flash(
s__('GeoNodes|Something went wrong while fetching nodes'),
);
}); });
}, },
fetchNodeDetails(node) { fetchNodeDetails(node) {
...@@ -217,28 +217,21 @@ export default { ...@@ -217,28 +217,21 @@ export default {
</script> </script>
<template> <template>
<div class="panel panel-default"> <div class="geo-nodes-container">
<div class="panel-heading">
Geo nodes ({{ nodes.length }})
</div>
<loading-icon <loading-icon
class="loading-animation prepend-top-20 append-bottom-20" class="loading-animation prepend-top-20 append-bottom-20"
size="2" size="2"
v-if="isLoading" v-if="isLoading"
:label="s__('GeoNodes|Loading nodes')" :label="s__('GeoNodes|Loading nodes')"
/> />
<geo-nodes-list <geo-node-item
v-if="!isLoading" v-for="(node, index) in nodes"
:nodes="nodes" :key="index"
:node="node"
:primary-node="node.primary"
:node-actions-allowed="nodeActionsAllowed" :node-actions-allowed="nodeActionsAllowed"
:node-edit-allowed="nodeEditAllowed" :node-edit-allowed="nodeEditAllowed"
/> />
<p
class="health-message prepend-left-15 append-right-15"
v-if="hasError"
>
{{ errorMessage }}
</p>
<deprecated-modal <deprecated-modal
v-show="showModal" v-show="showModal"
:title="__('Are you sure?')" :title="__('Are you sure?')"
......
...@@ -4,14 +4,12 @@ ...@@ -4,14 +4,12 @@
import { VALUE_TYPE, CUSTOM_TYPE } from '../constants'; import { VALUE_TYPE, CUSTOM_TYPE } from '../constants';
import geoNodeHealthStatus from './geo_node_health_status.vue';
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';
export default { export default {
components: { components: {
stackedProgressBar, stackedProgressBar,
geoNodeHealthStatus,
geoNodeSyncSettings, geoNodeSyncSettings,
geoNodeEventStatus, geoNodeEventStatus,
}, },
...@@ -53,6 +51,11 @@ ...@@ -53,6 +51,11 @@
required: false, required: false,
default: '', default: '',
}, },
eventTypeLogStatus: {
type: Boolean,
required: false,
default: false,
},
}, },
computed: { computed: {
isValueTypePlain() { isValueTypePlain() {
...@@ -64,9 +67,6 @@ ...@@ -64,9 +67,6 @@
isValueTypeCustom() { isValueTypeCustom() {
return this.itemValueType === VALUE_TYPE.CUSTOM; return this.itemValueType === VALUE_TYPE.CUSTOM;
}, },
isCustomTypeStatus() {
return this.customType === CUSTOM_TYPE.STATUS;
},
isCustomTypeSync() { isCustomTypeSync() {
return this.customType === CUSTOM_TYPE.SYNC; return this.customType === CUSTOM_TYPE.SYNC;
}, },
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
</script> </script>
<template> <template>
<li class="row node-detail-item"> <div class="node-detail-item prepend-top-15 prepend-left-10">
<div class="node-detail-title"> <div class="node-detail-title">
{{ itemTitle }} {{ itemTitle }}
</div> </div>
...@@ -100,12 +100,8 @@ ...@@ -100,12 +100,8 @@
/> />
</div> </div>
<template v-if="isValueTypeCustom"> <template v-if="isValueTypeCustom">
<geo-node-health-status
v-if="isCustomTypeStatus"
:status="itemValue"
/>
<geo-node-sync-settings <geo-node-sync-settings
v-else-if="isCustomTypeSync" v-if="isCustomTypeSync"
:sync-status-unavailable="itemValue.syncStatusUnavailable" :sync-status-unavailable="itemValue.syncStatusUnavailable"
:selective-sync-type="itemValue.selectiveSyncType" :selective-sync-type="itemValue.selectiveSyncType"
:last-event="itemValue.lastEvent" :last-event="itemValue.lastEvent"
...@@ -115,7 +111,8 @@ ...@@ -115,7 +111,8 @@
v-else v-else
:event-id="itemValue.eventId" :event-id="itemValue.eventId"
:event-time-stamp="itemValue.eventTimeStamp" :event-time-stamp="itemValue.eventTimeStamp"
:event-type-log-status="eventTypeLogStatus"
/> />
</template> </template>
</li> </div>
</template> </template>
<script> <script>
import { s__, sprintf } from '~/locale';
import { formatDate } from '~/lib/utils/datetime_utility'; import { formatDate } from '~/lib/utils/datetime_utility';
import timeAgoMixin from '~/vue_shared/mixins/timeago'; import timeAgoMixin from '~/vue_shared/mixins/timeago';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
...@@ -18,6 +19,12 @@ ...@@ -18,6 +19,12 @@
eventTimeStamp: { eventTimeStamp: {
type: Number, type: Number,
required: true, required: true,
default: 0,
},
eventTypeLogStatus: {
type: Boolean,
required: false,
default: false,
}, },
}, },
computed: { computed: {
...@@ -27,6 +34,14 @@ ...@@ -27,6 +34,14 @@
timeStampString() { timeStampString() {
return formatDate(this.timeStamp); return formatDate(this.timeStamp);
}, },
eventString() {
if (this.eventTypeLogStatus) {
return sprintf(s__('GeoNodes|%{eventId} events behind'), {
eventId: this.eventId,
});
}
return this.eventId;
},
}, },
}; };
</script> </script>
...@@ -37,7 +52,7 @@ ...@@ -37,7 +52,7 @@
> >
<template v-if="eventTimeStamp"> <template v-if="eventTimeStamp">
<strong> <strong>
{{ eventId }} {{ eventString }}
</strong> </strong>
<span <span
v-tooltip v-tooltip
......
...@@ -24,18 +24,23 @@ ...@@ -24,18 +24,23 @@
</script> </script>
<template> <template>
<div <div class="prepend-top-15 detail-section-item">
class="node-detail-value node-health-status" <div class="node-detail-title">
:class="healthCssClass" {{ s__('GeoNodes|Health status:') }}
> </div>
<icon <div
:size="16" class="node-detail-value node-health-status"
:name="statusIconName" :class="healthCssClass"
/>
<span
class="status-text prepend-left-5"
> >
{{ status }} <icon
</span> :size="16"
:name="statusIconName"
/>
<span
class="status-text prepend-left-5"
>
{{ status }}
</span>
</div>
</div> </div>
</template> </template>
<script> <script>
import { s__ } from '~/locale';
import icon from '~/vue_shared/components/icon.vue';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import geoNodeActions from './geo_node_actions.vue'; import GeoNodeHeader from './geo_node_header.vue';
import geoNodeDetails from './geo_node_details.vue'; import GeoNodeDetails from './geo_node_details.vue';
export default { export default {
components: { components: {
icon, GeoNodeHeader,
loadingIcon, GeoNodeDetails,
geoNodeActions,
geoNodeDetails,
},
directives: {
tooltip,
}, },
props: { props: {
node: { node: {
...@@ -47,41 +37,12 @@ export default { ...@@ -47,41 +37,12 @@ export default {
}; };
}, },
computed: { computed: {
isNodeNonHTTPS() {
return this.node.url.startsWith('http://');
},
showNodeStatusIcon() {
if (this.isNodeDetailsLoading) {
return false;
}
return this.isNodeNonHTTPS || this.isNodeDetailsFailed;
},
showNodeDetails() { showNodeDetails() {
if (!this.isNodeDetailsLoading) { if (!this.isNodeDetailsLoading) {
return !this.isNodeDetailsFailed; return !this.isNodeDetailsFailed;
} }
return false; return false;
}, },
nodeStatusIconClass() {
const iconClasses = 'prepend-left-10 pull-left';
if (this.isNodeDetailsFailed) {
return `${iconClasses} node-status-icon-failure`;
}
return `${iconClasses} node-status-icon-warning`;
},
nodeStatusIconName() {
if (this.isNodeDetailsFailed) {
return 'status_failed_borderless';
}
return 'warning';
},
nodeStatusIconTooltip() {
if (this.isNodeDetailsFailed) {
return '';
}
return s__('GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS.');
},
}, },
created() { created() {
eventHub.$on('nodeDetailsLoaded', this.handleNodeDetails); eventHub.$on('nodeDetailsLoaded', this.handleNodeDetails);
...@@ -119,59 +80,22 @@ export default { ...@@ -119,59 +80,22 @@ export default {
</script> </script>
<template> <template>
<li <div
class="panel panel-default geo-node-item"
:class="{ 'node-action-active': node.nodeActionActive }" :class="{ 'node-action-active': node.nodeActionActive }"
> >
<div class="row"> <geo-node-header
<div class="col-md-8"> :node="node"
<div class="row"> :node-details="nodeDetails"
<div class="col-md-8 clearfix"> :node-details-loading="isNodeDetailsLoading"
<strong class="node-url inline pull-left"> :node-details-failed="isNodeDetailsFailed"
{{ node.url }} />
</strong>
<loading-icon
v-if="isNodeDetailsLoading || node.nodeActionActive"
class="node-details-loading prepend-left-10 pull-left inline"
size="1"
/>
<icon
v-tooltip
v-if="showNodeStatusIcon"
data-container="body"
data-placement="bottom"
:name="nodeStatusIconName"
:size="18"
:css-classes="nodeStatusIconClass"
:title="nodeStatusIconTooltip"
/>
<span class="inline pull-left prepend-left-10">
<span
class="node-badge current-node"
v-if="node.current"
>
{{ s__('Current node') }}
</span>
<span
class="node-badge primary-node"
v-if="node.primary"
>
{{ s__('Primary') }}
</span>
</span>
</div>
</div>
</div>
<geo-node-actions
v-if="showNodeDetails && nodeActionsAllowed"
:node="node"
:node-edit-allowed="nodeEditAllowed"
:node-missing-oauth="nodeDetails.missingOAuthApplication"
/>
</div>
<geo-node-details <geo-node-details
v-if="showNodeDetails" v-if="showNodeDetails"
:node="node" :node="node"
:node-details="nodeDetails" :node-details="nodeDetails"
:node-edit-allowed="nodeEditAllowed"
:node-actions-allowed="nodeActionsAllowed"
/> />
<div <div
v-if="isNodeDetailsFailed" v-if="isNodeDetailsFailed"
...@@ -181,5 +105,5 @@ export default { ...@@ -181,5 +105,5 @@ export default {
{{ errorMessage }} {{ errorMessage }}
</p> </p>
</div> </div>
</li> </div>
</template> </template>
...@@ -118,7 +118,7 @@ ...@@ -118,7 +118,7 @@
<span <span
v-else v-else
v-tooltip v-tooltip
class="node-sync-settings inline" class="node-sync-settings"
data-placement="bottom" data-placement="bottom"
:title="syncStatusTooltip" :title="syncStatusTooltip"
> >
......
...@@ -23,7 +23,7 @@ export default { ...@@ -23,7 +23,7 @@ export default {
</script> </script>
<template> <template>
<ul class="well-list geo-nodes"> <div class="panel panel-default">
<geo-node-item <geo-node-item
v-for="(node, index) in nodes" v-for="(node, index) in nodes"
:key="index" :key="index"
...@@ -32,5 +32,5 @@ export default { ...@@ -32,5 +32,5 @@ export default {
:node-actions-allowed="nodeActionsAllowed" :node-actions-allowed="nodeActionsAllowed"
:node-edit-allowed="nodeEditAllowed" :node-edit-allowed="nodeEditAllowed"
/> />
</ul> </div>
</template> </template>
...@@ -56,7 +56,6 @@ describe('AppComponent', () => { ...@@ -56,7 +56,6 @@ describe('AppComponent', () => {
expect(vm.modalKind).toBe('warning'); expect(vm.modalKind).toBe('warning');
expect(vm.modalMessage).toBe(''); expect(vm.modalMessage).toBe('');
expect(vm.modalActionLabel).toBe(''); expect(vm.modalActionLabel).toBe('');
expect(vm.errorMessage).toBe('');
}); });
}); });
...@@ -91,10 +90,9 @@ describe('AppComponent', () => { ...@@ -91,10 +90,9 @@ describe('AppComponent', () => {
spyOn(vm.store, 'setNodes'); spyOn(vm.store, 'setNodes');
vm.fetchGeoNodes(); vm.fetchGeoNodes();
expect(vm.hasError).toBeFalsy();
setTimeout(() => { setTimeout(() => {
expect(vm.store.setNodes).toHaveBeenCalledWith(mockNodes); expect(vm.store.setNodes).toHaveBeenCalledWith(mockNodes);
expect(vm.isLoading).toBeFalsy(); expect(vm.isLoading).toBe(false);
done(); done();
}, 0); }, 0);
}); });
...@@ -104,10 +102,9 @@ describe('AppComponent', () => { ...@@ -104,10 +102,9 @@ describe('AppComponent', () => {
statusCode = 500; statusCode = 500;
vm.fetchGeoNodes(); vm.fetchGeoNodes();
expect(vm.hasError).toBeFalsy();
setTimeout(() => { setTimeout(() => {
expect(vm.hasError).toBeTruthy(); expect(vm.isLoading).toBe(false);
expect(vm.errorMessage.response.data).toBe(response); expect(document.querySelector('.flash-text').innerText.trim()).toBe('Something went wrong while fetching nodes');
done(); done();
}, 0); }, 0);
}); });
...@@ -380,39 +377,13 @@ describe('AppComponent', () => { ...@@ -380,39 +377,13 @@ describe('AppComponent', () => {
}); });
describe('template', () => { describe('template', () => {
it('renders container elements correctly', () => { it('renders container element with class `geo-nodes-container`', () => {
expect(vm.$el.classList.contains('panel', 'panel-default')).toBeTruthy(); expect(vm.$el.classList.contains('geo-nodes-container')).toBe(true);
expect(vm.$el.querySelectorAll('.panel-heading').length).not.toBe(0);
expect(vm.$el.querySelector('.panel-heading').innerText.trim()).toBe('Geo nodes (0)');
}); });
it('renders loading animation when `isLoading` is true', () => { it('renders loading animation when `isLoading` is true', () => {
vm.isLoading = true; vm.isLoading = true;
expect(vm.$el.querySelectorAll('.loading-animation.prepend-top-20.append-bottom-20').length).not.toBe(0); expect(vm.$el.querySelectorAll('.loading-animation.prepend-top-20.append-bottom-20').length).not.toBe(0);
}); });
it('renders list of nodes', (done) => {
vm.store.setNodes(mockNodes);
vm.isLoading = false;
Vue.nextTick(() => {
expect(vm.$el.querySelectorAll('.loading-animation.prepend-top-20.append-bottom-20').length).toBe(0);
expect(vm.$el.querySelectorAll('ul.geo-nodes').length).not.toBe(0);
done();
});
});
it('renders error message', (done) => {
vm.hasError = true;
vm.isLoading = false;
vm.errorMessage = 'Something went wrong.';
Vue.nextTick(() => {
const errEl = 'p.health-message.prepend-left-15.append-right-15';
expect(vm.$el.querySelectorAll(errEl).length).not.toBe(0);
expect(vm.$el.querySelector(errEl).innerText.trim()).toBe(vm.errorMessage);
done();
});
});
}); });
}); });
...@@ -46,16 +46,6 @@ describe('GeoNodeDetailItemComponent', () => { ...@@ -46,16 +46,6 @@ describe('GeoNodeDetailItemComponent', () => {
vm.$destroy(); vm.$destroy();
}); });
it('renders health status item value', () => {
const vm = createComponent({
itemValueType: VALUE_TYPE.CUSTOM,
customType: CUSTOM_TYPE.STATUS,
itemValue: rawMockNodeDetails.health,
});
expect(vm.$el.querySelectorAll('.node-health-status').length).not.toBe(0);
vm.$destroy();
});
it('renders sync settings item value', () => { it('renders sync settings item value', () => {
const vm = createComponent({ const vm = createComponent({
itemValueType: VALUE_TYPE.CUSTOM, itemValueType: VALUE_TYPE.CUSTOM,
......
...@@ -2,14 +2,21 @@ import Vue from 'vue'; ...@@ -2,14 +2,21 @@ import Vue from 'vue';
import geoNodeDetailsComponent from 'ee/geo_nodes/components/geo_node_details.vue'; import geoNodeDetailsComponent from 'ee/geo_nodes/components/geo_node_details.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockNodes, mockNodeDetails } from '../mock_data'; import { mockNode, mockNodeDetails } from '../mock_data';
const createComponent = (nodeDetails = mockNodeDetails) => { const createComponent = ({
node = mockNode,
nodeDetails = mockNodeDetails,
nodeActionsAllowed = true,
nodeEditAllowed = true,
}) => {
const Component = Vue.extend(geoNodeDetailsComponent); const Component = Vue.extend(geoNodeDetailsComponent);
return mountComponent(Component, { return mountComponent(Component, {
node,
nodeDetails, nodeDetails,
node: mockNodes[1], nodeActionsAllowed,
nodeEditAllowed,
}); });
}; };
...@@ -17,7 +24,7 @@ describe('GeoNodeDetailsComponent', () => { ...@@ -17,7 +24,7 @@ describe('GeoNodeDetailsComponent', () => {
let vm; let vm;
beforeEach(() => { beforeEach(() => {
vm = createComponent(); vm = createComponent({});
}); });
afterEach(() => { afterEach(() => {
...@@ -28,7 +35,6 @@ describe('GeoNodeDetailsComponent', () => { ...@@ -28,7 +35,6 @@ describe('GeoNodeDetailsComponent', () => {
it('returns default data props', () => { it('returns default data props', () => {
expect(vm.showAdvanceItems).toBeFalsy(); expect(vm.showAdvanceItems).toBeFalsy();
expect(vm.errorMessage).toBe(''); expect(vm.errorMessage).toBe('');
expect(Array.isArray(vm.nodeDetailItems)).toBeTruthy();
}); });
}); });
...@@ -40,7 +46,7 @@ describe('GeoNodeDetailsComponent', () => { ...@@ -40,7 +46,7 @@ describe('GeoNodeDetailsComponent', () => {
health: 'Something went wrong.', health: 'Something went wrong.',
healthy: false, healthy: false,
}); });
const vmX = createComponent(nodeDetails); const vmX = createComponent({ nodeDetails });
expect(vmX.errorMessage).toBe('Something went wrong.'); expect(vmX.errorMessage).toBe('Something went wrong.');
expect(vmX.hasError).toBeTruthy(); expect(vmX.hasError).toBeTruthy();
vmX.$destroy(); vmX.$destroy();
...@@ -57,7 +63,7 @@ describe('GeoNodeDetailsComponent', () => { ...@@ -57,7 +63,7 @@ describe('GeoNodeDetailsComponent', () => {
primaryVersion: '10.3.0-pre', primaryVersion: '10.3.0-pre',
primaryRevision: 'b93c51850b', primaryRevision: 'b93c51850b',
}); });
const vmX = createComponent(nodeDetails); const vmX = createComponent({ nodeDetails });
expect(vmX.errorMessage).toBe('GitLab version does not match the primary node version'); expect(vmX.errorMessage).toBe('GitLab version does not match the primary node version');
expect(vmX.hasVersionMismatch).toBeTruthy(); expect(vmX.hasVersionMismatch).toBeTruthy();
vmX.$destroy(); vmX.$destroy();
...@@ -66,147 +72,11 @@ describe('GeoNodeDetailsComponent', () => { ...@@ -66,147 +72,11 @@ describe('GeoNodeDetailsComponent', () => {
expect(vm.hasVersionMismatch).toBeFalsy(); expect(vm.hasVersionMismatch).toBeFalsy();
}); });
}); });
describe('advanceButtonIcon', () => {
it('returns button icon name', () => {
vm.showAdvanceItems = true;
expect(vm.advanceButtonIcon).toBe('angle-up');
vm.showAdvanceItems = false;
expect(vm.advanceButtonIcon).toBe('angle-down');
});
});
describe('nodeVersion', () => {
it('returns `Unknown` when `version` and `revision` are null', () => {
const nodeDetailsVersionNull = Object.assign({}, mockNodeDetails, {
version: null,
revision: null,
});
const vmVersionNull = createComponent(nodeDetailsVersionNull);
expect(vmVersionNull.nodeVersion).toBe('Unknown');
vmVersionNull.$destroy();
});
it('returns version string', () => {
expect(vm.nodeVersion).toBe('10.4.0-pre (b93c51849b)');
});
});
describe('replicationSlotWAL', () => {
it('returns replication slot WAL in Megabytes', () => {
expect(vm.replicationSlotWAL).toBe('479.37 MiB');
});
});
describe('dbReplicationLag', () => {
it('returns DB replication lag time duration', () => {
expect(vm.dbReplicationLag).toBe('0m');
});
it('returns `Unknown` when `dbReplicationLag` is null', () => {
const nodeDetailsLagNull = Object.assign({}, mockNodeDetails, {
dbReplicationLag: null,
});
const vmLagNull = createComponent(nodeDetailsLagNull);
expect(vmLagNull.dbReplicationLag).toBe('Unknown');
vmLagNull.$destroy();
});
});
describe('lastEventStatus', () => {
it('returns event status object', () => {
expect(vm.lastEventStatus.eventId).toBe(mockNodeDetails.lastEvent.id);
expect(vm.lastEventStatus.eventTimeStamp).toBe(mockNodeDetails.lastEvent.timeStamp);
});
});
describe('cursorLastEventStatus', () => {
it('returns event status object', () => {
expect(vm.cursorLastEventStatus.eventId).toBe(mockNodeDetails.cursorLastEvent.id);
expect(vm.cursorLastEventStatus.eventTimeStamp)
.toBe(mockNodeDetails.cursorLastEvent.timeStamp);
});
});
});
describe('methods', () => {
describe('nodeHealthStatus', () => {
it('returns health status string', () => {
// With altered mock data for Unhealthy status
const nodeDetails = Object.assign({}, mockNodeDetails, {
healthStatus: 'Unhealthy',
healthy: false,
});
const vmX = createComponent(nodeDetails);
expect(vmX.nodeHealthStatus()).toBe('Unhealthy');
vmX.$destroy();
// With default mock data
expect(vm.nodeHealthStatus()).toBe('Healthy');
});
});
describe('storageShardsStatus', () => {
it('returns storage shard status string', () => {
// With altered mock data for Unhealthy status
let nodeDetails = Object.assign({}, mockNodeDetails, {
storageShardsMatch: null,
});
let vmX = createComponent(nodeDetails);
expect(vmX.storageShardsStatus()).toBe('Unknown');
vmX.$destroy();
nodeDetails = Object.assign({}, mockNodeDetails, {
storageShardsMatch: true,
});
vmX = createComponent(nodeDetails);
expect(vmX.storageShardsStatus()).toBe('OK');
vmX.$destroy();
// With default mock data
expect(vm.storageShardsStatus()).toBe('Does not match the primary storage configuration');
});
});
describe('plainValueCssClass', () => {
it('returns CSS class for plain value item', () => {
expect(vm.plainValueCssClass()).toBe('node-detail-value-bold');
expect(vm.plainValueCssClass(true)).toBe('node-detail-value-bold node-detail-value-error');
});
});
describe('syncSettings', () => {
it('returns sync settings object', () => {
const nodeDetailsUnknownSync = Object.assign({}, mockNodeDetails, {
syncStatusUnavailable: true,
});
const vmUnknownSync = createComponent(nodeDetailsUnknownSync);
const syncSettings = vmUnknownSync.syncSettings();
expect(syncSettings.syncStatusUnavailable).toBe(true);
expect(syncSettings.namespaces).toBe(mockNodeDetails.namespaces);
expect(syncSettings.lastEvent).toBe(mockNodeDetails.lastEvent);
expect(syncSettings.cursorLastEvent).toBe(mockNodeDetails.cursorLastEvent);
vmUnknownSync.$destroy();
});
});
describe('onClickShowAdvance', () => {
it('toggles `showAdvanceItems` prop', () => {
vm.showAdvanceItems = true;
vm.onClickShowAdvance();
expect(vm.showAdvanceItems).toBeFalsy();
vm.showAdvanceItems = false;
vm.onClickShowAdvance();
expect(vm.showAdvanceItems).toBeTruthy();
});
});
}); });
describe('template', () => { describe('template', () => {
it('renders container elements correctly', () => { it('renders container elements correctly', () => {
expect(vm.$el.querySelectorAll('.node-details-list').length).not.toBe(0); expect(vm.$el.classList.contains('panel-body')).toBe(true);
expect(vm.$el.querySelectorAll('.btn-show-advanced').length).not.toBe(0);
}); });
}); });
}); });
...@@ -4,15 +4,17 @@ import geoNodeEventStatusComponent from 'ee/geo_nodes/components/geo_node_event_ ...@@ -4,15 +4,17 @@ import geoNodeEventStatusComponent from 'ee/geo_nodes/components/geo_node_event_
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockNodeDetails } from '../mock_data'; import { mockNodeDetails } from '../mock_data';
const createComponent = ( const createComponent = ({
eventId = mockNodeDetails.lastEvent.id, eventId = mockNodeDetails.lastEvent.id,
eventTimeStamp = mockNodeDetails.lastEvent.timeStamp, eventTimeStamp = mockNodeDetails.lastEvent.timeStamp,
) => { eventTypeLogStatus = false,
}) => {
const Component = Vue.extend(geoNodeEventStatusComponent); const Component = Vue.extend(geoNodeEventStatusComponent);
return mountComponent(Component, { return mountComponent(Component, {
eventId, eventId,
eventTimeStamp, eventTimeStamp,
eventTypeLogStatus,
}); });
}; };
...@@ -20,7 +22,7 @@ describe('GeoNodeEventStatus', () => { ...@@ -20,7 +22,7 @@ describe('GeoNodeEventStatus', () => {
let vm; let vm;
beforeEach(() => { beforeEach(() => {
vm = createComponent(); vm = createComponent({});
}); });
afterEach(() => { afterEach(() => {
...@@ -39,6 +41,18 @@ describe('GeoNodeEventStatus', () => { ...@@ -39,6 +41,18 @@ describe('GeoNodeEventStatus', () => {
expect(vm.timeStampString).toContain('Nov 21, 2017'); expect(vm.timeStampString).toContain('Nov 21, 2017');
}); });
}); });
describe('eventString', () => {
it('returns computed event string when `eventTypeLogStatus` prop is true', () => {
const vmWithLogStatus = createComponent({ eventTypeLogStatus: true });
expect(vmWithLogStatus.eventString).toBe(`${mockNodeDetails.lastEvent.id} events behind`);
vmWithLogStatus.$destroy();
});
it('returns event ID as it is when `eventTypeLogStatus` prop is false', () => {
expect(vm.eventString).toBe(mockNodeDetails.lastEvent.id);
});
});
}); });
describe('template', () => { describe('template', () => {
...@@ -50,7 +64,10 @@ describe('GeoNodeEventStatus', () => { ...@@ -50,7 +64,10 @@ describe('GeoNodeEventStatus', () => {
}); });
it('renders empty state when timestamp is not present', () => { it('renders empty state when timestamp is not present', () => {
const vmWithoutTimestamp = createComponent(0, 0); const vmWithoutTimestamp = createComponent({
eventId: 0,
eventTimeStamp: 0,
});
expect(vmWithoutTimestamp.$el.querySelectorAll('strong').length).not.toBe(0); expect(vmWithoutTimestamp.$el.querySelectorAll('strong').length).not.toBe(0);
expect(vmWithoutTimestamp.$el.querySelectorAll('.event-status-timestamp').length).toBe(0); expect(vmWithoutTimestamp.$el.querySelectorAll('.event-status-timestamp').length).toBe(0);
expect(vmWithoutTimestamp.$el.querySelector('strong').innerText.trim()).toBe('Not available'); expect(vmWithoutTimestamp.$el.querySelector('strong').innerText.trim()).toBe('Not available');
......
...@@ -50,10 +50,13 @@ describe('GeoNodeHealthStatusComponent', () => { ...@@ -50,10 +50,13 @@ describe('GeoNodeHealthStatusComponent', () => {
describe('template', () => { describe('template', () => {
it('renders container elements correctly', () => { it('renders container elements correctly', () => {
const vm = createComponent('Healthy'); const vm = createComponent('Healthy');
expect(vm.$el.classList.contains('node-detail-value', 'node-health-status', 'geo-node-healthy')).toBeTruthy(); expect(vm.$el.classList.contains('detail-section-item')).toBe(true);
expect(vm.$el.querySelectorAll('svg').length).not.toBe(0); expect(vm.$el.querySelector('.node-detail-title').innerText.trim()).toBe('Health status:');
expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('#status_success');
expect(vm.$el.querySelector('.status-text').innerText.trim()).toBe('Healthy'); const iconContainerEl = vm.$el.querySelector('.node-detail-value.node-health-status');
expect(iconContainerEl).not.toBeNull();
expect(iconContainerEl.querySelector('svg use').getAttribute('xlink:href')).toContain('#status_success');
expect(iconContainerEl.querySelector('.status-text').innerText.trim()).toBe('Healthy');
vm.$destroy(); vm.$destroy();
}); });
}); });
......
...@@ -3,9 +3,9 @@ import Vue from 'vue'; ...@@ -3,9 +3,9 @@ import Vue from 'vue';
import geoNodeItemComponent from 'ee/geo_nodes/components/geo_node_item.vue'; import geoNodeItemComponent from 'ee/geo_nodes/components/geo_node_item.vue';
import eventHub from 'ee/geo_nodes/event_hub'; import eventHub from 'ee/geo_nodes/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockNodes, mockNodeDetails } from '../mock_data'; import { mockNode, mockNodeDetails } from '../mock_data';
const createComponent = (node = mockNodes[0]) => { const createComponent = (node = mockNode) => {
const Component = Vue.extend(geoNodeItemComponent); const Component = Vue.extend(geoNodeItemComponent);
return mountComponent(Component, { return mountComponent(Component, {
...@@ -29,8 +29,8 @@ describe('GeoNodeItemComponent', () => { ...@@ -29,8 +29,8 @@ describe('GeoNodeItemComponent', () => {
describe('data', () => { describe('data', () => {
it('returns default data props', () => { it('returns default data props', () => {
expect(vm.isNodeDetailsLoading).toBeTruthy(); expect(vm.isNodeDetailsLoading).toBe(true);
expect(vm.isNodeDetailsFailed).toBeFalsy(); expect(vm.isNodeDetailsFailed).toBe(false);
expect(vm.nodeHealthStatus).toBe(''); expect(vm.nodeHealthStatus).toBe('');
expect(vm.errorMessage).toBe(''); expect(vm.errorMessage).toBe('');
expect(typeof vm.nodeDetails).toBe('object'); expect(typeof vm.nodeDetails).toBe('object');
...@@ -39,57 +39,20 @@ describe('GeoNodeItemComponent', () => { ...@@ -39,57 +39,20 @@ describe('GeoNodeItemComponent', () => {
describe('computed', () => { describe('computed', () => {
let vmHttps; let vmHttps;
let mockNode; let httpsNode;
beforeEach(() => { beforeEach(() => {
// Altered mock data for secure URL // Altered mock data for secure URL
mockNode = Object.assign({}, mockNodes[0], { httpsNode = Object.assign({}, mockNode, {
url: 'https://127.0.0.1:3001/', url: 'https://127.0.0.1:3001/',
}); });
vmHttps = createComponent(mockNode); vmHttps = createComponent(httpsNode);
}); });
afterEach(() => { afterEach(() => {
vmHttps.$destroy(); vmHttps.$destroy();
}); });
describe('isNodeNonHTTPS', () => {
it('returns `true` if Node URL protocol is non-HTTPS', () => {
// With default mock data
expect(vm.isNodeNonHTTPS).toBeTruthy();
});
it('returns `false` is Node URL protocol is HTTPS', () => {
// With altered mock data
expect(vmHttps.isNodeNonHTTPS).toBeFalsy();
});
});
describe('showNodeStatusIcon', () => {
it('returns `false` if Node details are still loading', () => {
vm.isNodeDetailsLoading = true;
expect(vm.showNodeStatusIcon).toBeFalsy();
});
it('returns `true` if Node details failed to load', () => {
vm.isNodeDetailsLoading = false;
vm.isNodeDetailsFailed = true;
expect(vm.showNodeStatusIcon).toBeTruthy();
});
it('returns `true` if Node details loaded and Node URL is non-HTTPS', () => {
vm.isNodeDetailsLoading = false;
vm.isNodeDetailsFailed = false;
expect(vm.showNodeStatusIcon).toBeTruthy();
});
it('returns `false` if Node details loaded and Node URL is HTTPS', () => {
vmHttps.isNodeDetailsLoading = false;
vmHttps.isNodeDetailsFailed = false;
expect(vmHttps.showNodeStatusIcon).toBeFalsy();
});
});
describe('showNodeDetails', () => { describe('showNodeDetails', () => {
it('returns `false` if Node details are still loading', () => { it('returns `false` if Node details are still loading', () => {
vm.isNodeDetailsLoading = true; vm.isNodeDetailsLoading = true;
...@@ -108,50 +71,17 @@ describe('GeoNodeItemComponent', () => { ...@@ -108,50 +71,17 @@ describe('GeoNodeItemComponent', () => {
expect(vm.showNodeDetails).toBeTruthy(); expect(vm.showNodeDetails).toBeTruthy();
}); });
}); });
describe('nodeStatusIconClass', () => {
it('returns `node-status-icon-failure` along with default classes if Node details failed to load', () => {
vm.isNodeDetailsFailed = true;
expect(vm.nodeStatusIconClass).toBe('prepend-left-10 pull-left node-status-icon-failure');
});
it('returns `node-status-icon-warning` along with default classes if Node details loaded and Node URL is non-HTTPS', () => {
vm.isNodeDetailsFailed = false;
expect(vm.nodeStatusIconClass).toBe('prepend-left-10 pull-left node-status-icon-warning');
});
});
describe('nodeStatusIconName', () => {
it('returns `warning` if Node details loaded and Node URL is non-HTTPS', () => {
vm.isNodeDetailsFailed = false;
expect(vm.nodeStatusIconName).toBe('warning');
});
it('returns `status_failed_borderless` if Node details failed to load', () => {
vm.isNodeDetailsFailed = true;
expect(vm.nodeStatusIconName).toBe('status_failed_borderless');
});
});
describe('nodeStatusIconTooltip', () => {
it('returns empty string if Node details failed to load', () => {
vm.isNodeDetailsFailed = true;
expect(vm.nodeStatusIconTooltip).toBe('');
});
it('returns tooltip string if Node details loaded and Node URL is non-HTTPS', () => {
vm.isNodeDetailsFailed = false;
expect(vm.nodeStatusIconTooltip).toBe('You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS.');
});
});
}); });
describe('methods', () => { describe('methods', () => {
describe('handleNodeDetails', () => { describe('handleNodeDetails', () => {
it('intializes props based on provided `nodeDetails`', () => { it('intializes props based on provided `nodeDetails`', () => {
// With altered mock data with matching ID // With altered mock data with matching ID
const mockNode = Object.assign({}, mockNodes[1]); const mockNodeSecondary = Object.assign({}, mockNode, {
const vmNodePrimary = createComponent(mockNode); id: mockNodeDetails.id,
primary: false,
});
const vmNodePrimary = createComponent(mockNodeSecondary);
vmNodePrimary.handleNodeDetails(mockNodeDetails); vmNodePrimary.handleNodeDetails(mockNodeDetails);
expect(vmNodePrimary.isNodeDetailsLoading).toBeFalsy(); expect(vmNodePrimary.isNodeDetailsLoading).toBeFalsy();
...@@ -212,21 +142,8 @@ describe('GeoNodeItemComponent', () => { ...@@ -212,21 +142,8 @@ describe('GeoNodeItemComponent', () => {
}); });
describe('template', () => { describe('template', () => {
it('renders node URL', () => { it('renders container element', () => {
expect(vm.$el.querySelectorAll('.node-url').length).not.toBe(0); expect(vm.$el.classList.contains('panel', 'panel-default', 'geo-node-item')).toBe(true);
});
it('renders node details loading animation', () => {
vm.isNodeDetailsLoading = true;
expect(vm.$el.querySelectorAll('.node-details-loading').length).not.toBe(0);
});
it('renders node badge `Current node`', () => {
expect(vm.$el.querySelectorAll('.node-badge.current-node').length).not.toBe(0);
});
it('renders node badge `Primary`', () => {
expect(vm.$el.querySelectorAll('.node-badge.primary-node').length).not.toBe(0);
}); });
it('renders node error message', (done) => { it('renders node error message', (done) => {
......
...@@ -18,7 +18,7 @@ describe('GeoNodesListComponent', () => { ...@@ -18,7 +18,7 @@ describe('GeoNodesListComponent', () => {
describe('template', () => { describe('template', () => {
it('renders container element correctly', () => { it('renders container element correctly', () => {
const vm = createComponent(); const vm = createComponent();
expect(vm.$el.classList.contains('well-list', 'geo-nodes')).toBeTruthy(); expect(vm.$el.classList.contains('panel', 'panel-default')).toBe(true);
vm.$destroy(); vm.$destroy();
}); });
}); });
......
...@@ -145,6 +145,7 @@ export const mockNodeDetails = { ...@@ -145,6 +145,7 @@ export const mockNodeDetails = {
replicationSlotWAL: 502658737, replicationSlotWAL: 502658737,
missingOAuthApplication: false, missingOAuthApplication: false,
storageShardsMatch: false, storageShardsMatch: false,
repositoryVerificationEnabled: true,
replicationSlots: { replicationSlots: {
totalCount: null, totalCount: null,
successCount: null, successCount: null,
...@@ -175,6 +176,16 @@ export const mockNodeDetails = { ...@@ -175,6 +176,16 @@ export const mockNodeDetails = {
successCount: 0, successCount: 0,
failureCount: 0, failureCount: 0,
}, },
verifiedRepositories: {
totalCount: 0,
successCount: 0,
failureCount: 0,
},
verifiedWikis: {
totalCount: 0,
successCount: 0,
failureCount: 0,
},
lastEvent: { lastEvent: {
id: 3, id: 3,
timeStamp: 1511255200, timeStamp: 1511255200,
......
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