Commit c3efd8e0 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'kp-fix-geo-admin-bugs' into 'master'

Handle empty event timestamp and larger memory units

Closes #4653 and #4664

See merge request gitlab-org/gitlab-ee!4206
parents 041100ee 6d16eb9b
---
title: Handle empty event timestamp and larger memory units
merge_request: 4206
author:
type: fixed
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
/* eslint-disable vue/no-side-effects-in-computed-properties */ /* eslint-disable vue/no-side-effects-in-computed-properties */
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import { parseSeconds, stringifyTime } from '~/lib/utils/pretty_time'; import { parseSeconds, stringifyTime } from '~/lib/utils/pretty_time';
import { bytesToMiB } from '~/lib/utils/number_utils'; import { numberToHumanSize } from '~/lib/utils/number_utils';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import { VALUE_TYPE, CUSTOM_TYPE } from '../constants'; import { VALUE_TYPE, CUSTOM_TYPE } from '../constants';
...@@ -100,7 +100,7 @@ ...@@ -100,7 +100,7 @@
return `${this.nodeDetails.version} (${this.nodeDetails.revision})`; return `${this.nodeDetails.version} (${this.nodeDetails.revision})`;
}, },
replicationSlotWAL() { replicationSlotWAL() {
return `${bytesToMiB(this.nodeDetails.replicationSlotWAL)} MB`; return numberToHumanSize(this.nodeDetails.replicationSlotWAL);
}, },
dbReplicationLag() { dbReplicationLag() {
// Replication lag can be nil if the secondary isn't actually streaming // Replication lag can be nil if the secondary isn't actually streaming
......
...@@ -35,16 +35,24 @@ ...@@ -35,16 +35,24 @@
<div <div
class="node-detail-value" class="node-detail-value"
> >
<strong> <template v-if="eventTimeStamp">
{{ eventId }} <strong>
</strong> {{ eventId }}
<span </strong>
v-tooltip <span
class="event-status-timestamp" v-tooltip
data-placement="bottom" v-if="eventTimeStamp"
:title="timeStampString" class="event-status-timestamp"
data-placement="bottom"
:title="timeStampString"
>
({{ timeFormated(timeStamp) }})
</span>
</template>
<strong
v-else
> >
({{ timeFormated(timeStamp) }}) {{ __('Not available') }}
</span> </strong>
</div> </div>
</template> </template>
...@@ -32,6 +32,9 @@ ...@@ -32,6 +32,9 @@
syncType() { syncType() {
return this.namespaces.length > 0 ? s__('GeoNodes|Selective') : s__('GeoNodes|Full'); return this.namespaces.length > 0 ? s__('GeoNodes|Selective') : s__('GeoNodes|Full');
}, },
eventTimestampEmpty() {
return this.lastEvent.timeStamp === 0 || this.cursorLastEvent.timeStamp === 0;
},
syncLagInSeconds() { syncLagInSeconds() {
return this.lagInSeconds(this.lastEvent.timeStamp, this.cursorLastEvent.timeStamp); return this.lagInSeconds(this.lastEvent.timeStamp, this.cursorLastEvent.timeStamp);
}, },
...@@ -79,7 +82,8 @@ ...@@ -79,7 +82,8 @@
return `${timeAgoStr} (${pendingEvents} events)`; return `${timeAgoStr} (${pendingEvents} events)`;
}, },
statusTooltip(lagInSeconds) { statusTooltip(lagInSeconds) {
if (lagInSeconds <= TIME_DIFF.FIVE_MINS) { if (this.eventTimestampEmpty ||
lagInSeconds <= TIME_DIFF.FIVE_MINS) {
return ''; return '';
} else if (lagInSeconds > TIME_DIFF.FIVE_MINS && } else if (lagInSeconds > TIME_DIFF.FIVE_MINS &&
lagInSeconds <= TIME_DIFF.HOUR) { lagInSeconds <= TIME_DIFF.HOUR) {
...@@ -107,6 +111,7 @@ ...@@ -107,6 +111,7 @@
css-classes="sync-status-icon prepend-left-5" css-classes="sync-status-icon prepend-left-5"
/> />
<span <span
v-if="!eventTimestampEmpty"
class="sync-status-event-info prepend-left-5" class="sync-status-event-info prepend-left-5"
> >
{{ syncStatusEventInfo }} {{ syncStatusEventInfo }}
......
...@@ -74,11 +74,11 @@ export default class GeoNodesStore { ...@@ -74,11 +74,11 @@ export default class GeoNodesStore {
failureCount: rawNodeDetails.attachments_failed_count || 0, failureCount: rawNodeDetails.attachments_failed_count || 0,
}, },
lastEvent: { lastEvent: {
id: rawNodeDetails.last_event_id, id: rawNodeDetails.last_event_id || 0,
timeStamp: rawNodeDetails.last_event_timestamp, timeStamp: rawNodeDetails.last_event_timestamp,
}, },
cursorLastEvent: { cursorLastEvent: {
id: rawNodeDetails.cursor_last_event_id, id: rawNodeDetails.cursor_last_event_id || 0,
timeStamp: rawNodeDetails.cursor_last_event_timestamp, timeStamp: rawNodeDetails.cursor_last_event_timestamp,
}, },
namespaces: rawNodeDetails.namespaces, namespaces: rawNodeDetails.namespaces,
......
...@@ -85,7 +85,7 @@ describe('GeoNodeDetailsComponent', () => { ...@@ -85,7 +85,7 @@ describe('GeoNodeDetailsComponent', () => {
describe('replicationSlotWAL', () => { describe('replicationSlotWAL', () => {
it('returns replication slot WAL in Megabytes', () => { it('returns replication slot WAL in Megabytes', () => {
expect(vm.replicationSlotWAL).toBe('0 MB'); expect(vm.replicationSlotWAL).toBe('479.37 MiB');
}); });
}); });
......
...@@ -5,12 +5,15 @@ import { mockNodeDetails } from '../mock_data'; ...@@ -5,12 +5,15 @@ import { mockNodeDetails } from '../mock_data';
import mountComponent from '../../helpers/vue_mount_component_helper'; import mountComponent from '../../helpers/vue_mount_component_helper';
const createComponent = () => { const createComponent = (
eventId = mockNodeDetails.lastEvent.id,
eventTimeStamp = mockNodeDetails.lastEvent.timeStamp,
) => {
const Component = Vue.extend(geoNodeEventStatusComponent); const Component = Vue.extend(geoNodeEventStatusComponent);
return mountComponent(Component, { return mountComponent(Component, {
eventId: mockNodeDetails.lastEvent.id, eventId,
eventTimeStamp: mockNodeDetails.lastEvent.timeStamp, eventTimeStamp,
}); });
}; };
...@@ -46,5 +49,13 @@ describe('GeoNodeEventStatus', () => { ...@@ -46,5 +49,13 @@ describe('GeoNodeEventStatus', () => {
expect(vm.$el.querySelector('strong').innerText.trim()).toBe(`${mockNodeDetails.lastEvent.id}`); expect(vm.$el.querySelector('strong').innerText.trim()).toBe(`${mockNodeDetails.lastEvent.id}`);
expect(vm.$el.querySelector('.event-status-timestamp').innerText).toContain('ago'); expect(vm.$el.querySelector('.event-status-timestamp').innerText).toContain('ago');
}); });
it('renders empty state when timestamp is not present', () => {
const vmWithoutTimestamp = createComponent(0, 0);
expect(vmWithoutTimestamp.$el.querySelectorAll('strong').length).not.toBe(0);
expect(vmWithoutTimestamp.$el.querySelectorAll('.event-status-timestamp').length).toBe(0);
expect(vmWithoutTimestamp.$el.querySelector('strong').innerText.trim()).toBe('Not available');
vmWithoutTimestamp.$destroy();
});
}); });
}); });
...@@ -27,6 +27,26 @@ describe('GeoNodeSyncSettingsComponent', () => { ...@@ -27,6 +27,26 @@ describe('GeoNodeSyncSettingsComponent', () => {
vm.$destroy(); vm.$destroy();
}); });
}); });
describe('eventTimestampEmpty', () => {
it('returns `true` if one of the event timestamp is empty', () => {
const vmEmptyTimestamp = createComponent(mockNodeDetails.namespaces, {
id: 0,
timeStamp: 0,
}, {
id: 0,
timeStamp: 0,
});
expect(vmEmptyTimestamp.eventTimestampEmpty).toBeTruthy();
vmEmptyTimestamp.$destroy();
});
it('return `false` if one of the event timestamp is present', () => {
const vm = createComponent();
expect(vm.eventTimestampEmpty).toBeFalsy();
vm.$destroy();
});
});
}); });
describe('methods', () => { describe('methods', () => {
......
...@@ -110,7 +110,7 @@ export const mockNodeDetails = { ...@@ -110,7 +110,7 @@ export const mockNodeDetails = {
revision: 'b93c51849b', revision: 'b93c51849b',
primaryVersion: '10.4.0-pre', primaryVersion: '10.4.0-pre',
primaryRevision: 'b93c51849b', primaryRevision: 'b93c51849b',
replicationSlotWAL: null, replicationSlotWAL: 502658737,
missingOAuthApplication: false, missingOAuthApplication: false,
storageShardsMatch: false, storageShardsMatch: false,
replicationSlots: { replicationSlots: {
......
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