Commit a808713a authored by Phil Hughes's avatar Phil Hughes

Merge branch '4909-show-status-data-timeout-info' into 'master'

Show status information stale icon in Geo admin dashboard

Closes #4909

See merge request gitlab-org/gitlab-ee!5653
parents 99ca7ab4 e9f660d8
<script>
import { s__ } from '~/locale';
import popover from '~/vue_shared/directives/popover';
import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
import StackedProgressBar from '~/vue_shared/components/stacked_progress_bar.vue';
......@@ -18,6 +19,7 @@
},
directives: {
popover,
tooltip,
},
props: {
itemTitle: {
......@@ -33,6 +35,16 @@
type: [Object, String, Number],
required: true,
},
itemValueStale: {
type: Boolean,
required: false,
default: false,
},
itemValueStaleTooltip: {
type: String,
required: false,
default: '',
},
successLabel: {
type: String,
required: false,
......@@ -132,8 +144,10 @@
<div
v-if="isValueTypeGraph"
class="node-detail-value"
:class="{ 'd-flex': itemValueStale }"
>
<stacked-progress-bar
:css-class="itemValueStale ? 'flex-fill' : ''"
:success-label="successLabel"
:failure-label="failureLabel"
:neutral-label="neutralLabel"
......@@ -141,6 +155,14 @@
:failure-count="itemValue.failureCount"
:total-count="itemValue.totalCount"
/>
<icon
v-tooltip
v-show="itemValueStale"
name="time-out"
css-classes="prepend-left-10 detail-value-stale-icon"
data-container="body"
:title="itemValueStaleTooltip"
/>
</div>
<template v-if="isValueTypeCustom">
<geo-node-sync-settings
......
......@@ -4,6 +4,8 @@
import { VALUE_TYPE } from '../../constants';
import DetailsSectionMixin from '../../mixins/details_section_mixin';
import GeoNodeDetailItem from '../geo_node_detail_item.vue';
import SectionRevealButton from './section_reveal_button.vue';
......@@ -13,6 +15,9 @@
SectionRevealButton,
GeoNodeDetailItem,
},
mixins: [
DetailsSectionMixin,
],
props: {
nodeDetails: {
type: Object,
......@@ -98,14 +103,12 @@
class="col-md-6 prepend-left-15 prepend-top-10 section-items-container"
>
<geo-node-detail-item
v-for="(nodeDetailItem, index) in nodeDetailItems"
:key="index"
:css-class="nodeDetailItem.cssClass"
:item-title="nodeDetailItem.itemTitle"
:item-value="nodeDetailItem.itemValue"
:item-value-type="nodeDetailItem.itemValueType"
:success-label="nodeDetailItem.successLabel"
:neutral-label="nodeDetailItem.neutraLabel"
:item-title="s__('GeoNodes|Storage config')"
:item-value="storageShardsStatus"
:item-value-type="$options.valueType.PLAIN"
:item-value-stale="statusInfoStale"
:item-value-stale-tooltip="statusInfoStaleMessage"
:css-class="storageShardsCssClass"
/>
</div>
</div>
......
......@@ -4,6 +4,8 @@
import { VALUE_TYPE, CUSTOM_TYPE } from '../../constants';
import DetailsSectionMixin from '../../mixins/details_section_mixin';
import GeoNodeDetailItem from '../geo_node_detail_item.vue';
import SectionRevealButton from './section_reveal_button.vue';
......@@ -12,6 +14,9 @@
SectionRevealButton,
GeoNodeDetailItem,
},
mixins: [
DetailsSectionMixin,
],
props: {
nodeDetails: {
type: Object,
......@@ -135,6 +140,8 @@
:item-title="nodeDetailItem.itemTitle"
:item-value="nodeDetailItem.itemValue"
:item-value-type="nodeDetailItem.itemValueType"
:item-value-stale="statusInfoStale"
:item-value-stale-tooltip="statusInfoStaleMessage"
:custom-type="nodeDetailItem.customType"
:event-type-log-status="nodeDetailItem.eventTypeLogStatus"
/>
......
......@@ -3,6 +3,8 @@
import { VALUE_TYPE, HELP_INFO_URL } from '../../constants';
import DetailsSectionMixin from '../../mixins/details_section_mixin';
import GeoNodeDetailItem from '../geo_node_detail_item.vue';
import SectionRevealButton from './section_reveal_button.vue';
......@@ -11,6 +13,9 @@
GeoNodeDetailItem,
SectionRevealButton,
},
mixins: [
DetailsSectionMixin,
],
props: {
nodeDetails: {
type: Object,
......@@ -122,6 +127,8 @@
:item-title="nodeDetailItem.itemTitle"
:item-value="nodeDetailItem.itemValue"
:item-value-type="nodeDetailItem.itemValueType"
:item-value-stale="statusInfoStale"
:item-value-stale-tooltip="statusInfoStaleMessage"
:success-label="nodeDetailItem.successLabel"
:neutral-label="nodeDetailItem.neutraLabel"
:failure-label="nodeDetailItem.failureLabel"
......
......@@ -28,4 +28,6 @@ export const TIME_DIFF = {
HOUR: 3600,
};
export const STATUS_DELAY_THRESHOLD_MS = 60000;
export const HELP_INFO_URL = 'https://docs.gitlab.com/ee/administration/geo/disaster_recovery/background_verification.html#repository-verification';
import { s__, sprintf } from '~/locale';
import timeAgoMixin from '~/vue_shared/mixins/timeago';
import { STATUS_DELAY_THRESHOLD_MS } from '../constants';
export default {
mixins: [timeAgoMixin],
computed: {
statusInfoStale() {
const elapsedMilliseconds = Math.abs(this.nodeDetails.statusCheckTimestamp - Date.now());
return elapsedMilliseconds > STATUS_DELAY_THRESHOLD_MS;
},
statusInfoStaleMessage() {
return sprintf(s__('GeoNodes|Data is out of date from %{timeago}'), {
timeago: this.timeFormated(
this.nodeDetails.statusCheckTimestamp,
),
});
},
},
};
......@@ -68,6 +68,7 @@ export default class GeoNodesStore {
revision: rawNodeDetails.revision,
primaryVersion: rawNodeDetails.primaryVersion,
primaryRevision: rawNodeDetails.primaryRevision,
statusCheckTimestamp: rawNodeDetails.last_successful_status_check_timestamp * 1000,
replicationSlotWAL: rawNodeDetails.replication_slots_max_retained_wal_bytes,
missingOAuthApplication: rawNodeDetails.missing_oauth_application || false,
syncStatusUnavailable: rawNodeDetails.sync_status_unavailable || false,
......
......@@ -115,6 +115,10 @@
margin-top: 4px;
}
.detail-value-stale-icon {
color: $gl-warning;
}
.node-detail-value-bold {
font-weight: $gl-font-weight-bold;
}
......
---
title: Show status information stale icon in Geo admin dashboard
merge_request: 5653
author:
type: added
......@@ -56,6 +56,22 @@ describe('GeoNodeDetailItemComponent', () => {
vm.$destroy();
});
it('renders stale information status icon when `itemValueStale` prop is true', () => {
const itemValueStaleTooltip = 'Data is out of date from 8 hours ago';
const vm = createComponent({
itemValueType: VALUE_TYPE.GRAPH,
itemValue: { successCount: 5, failureCount: 3, totalCount: 10 },
itemValueStale: true,
itemValueStaleTooltip,
});
const iconEl = vm.$el.querySelector('.detail-value-stale-icon');
expect(iconEl).not.toBeNull();
expect(iconEl.dataset.originalTitle).toBe(itemValueStaleTooltip);
expect(iconEl.querySelector('use').getAttribute('xlink:href')).toContain('time-out');
vm.$destroy();
});
it('renders sync settings item value', () => {
const vm = createComponent({
itemValueType: VALUE_TYPE.CUSTOM,
......
import Vue from 'vue';
import DetailsSectionMixin from 'ee/geo_nodes/mixins/details_section_mixin';
import { STATUS_DELAY_THRESHOLD_MS } from 'ee/geo_nodes/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockNodeDetails } from '../mock_data';
const createComponent = (nodeDetails = mockNodeDetails) => {
const Component = Vue.extend({
template: '<div></div>',
mixins: [DetailsSectionMixin],
data() {
return { nodeDetails };
},
});
return mountComponent(Component);
};
describe('DetailsSectionMixin', () => {
let vm;
afterEach(() => {
vm.$destroy();
});
describe('computed', () => {
describe('statusInfoStale', () => {
it('returns true when `nodeDetails.statusCheckTimestamp` is past the value of STATUS_DELAY_THRESHOLD_MS', () => {
// Move statusCheckTimestamp to 2 minutes in the past
const statusCheckTimestamp = new Date(Date.now() - STATUS_DELAY_THRESHOLD_MS * 2).getTime();
vm = createComponent(Object.assign({}, mockNodeDetails, { statusCheckTimestamp }));
expect(vm.statusInfoStale).toBe(true);
});
it('returns false when `nodeDetails.statusCheckTimestamp` is under the value of STATUS_DELAY_THRESHOLD_MS', () => {
// Move statusCheckTimestamp to 30 seconds in the past
const statusCheckTimestamp = new Date(Date.now() - STATUS_DELAY_THRESHOLD_MS / 2).getTime();
vm = createComponent(Object.assign({}, mockNodeDetails, { statusCheckTimestamp }));
expect(vm.statusInfoStale).toBe(false);
});
});
describe('statusInfoStaleMessage', () => {
it('returns stale information message containing the duration elapsed', () => {
// Move statusCheckTimestamp to 1 minute in the past
const statusCheckTimestamp = new Date(Date.now() - STATUS_DELAY_THRESHOLD_MS).getTime();
vm = createComponent(Object.assign({}, mockNodeDetails, { statusCheckTimestamp }));
expect(vm.statusInfoStaleMessage).toBe('Data is out of date from about a minute ago');
});
});
});
});
......@@ -148,6 +148,7 @@ export const mockNodeDetails = {
revision: 'b93c51849b',
primaryVersion: '10.4.0-pre',
primaryRevision: 'b93c51849b',
statusCheckTimestamp: 1515142330,
replicationSlotWAL: 502658737,
missingOAuthApplication: false,
storageShardsMatch: false,
......
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