Skip to content
Snippets Groups Projects
Commit 079d4b89 authored by Zack Cuddy's avatar Zack Cuddy Committed by Phil Hughes
Browse files

Cleanup styles for Geo Node Items

Remove unneeded class

Fix geo node status colors

Node warning styles

Cleanup geo-node-item

Cleanup node section items

Mobile friendly node actions

Tweak margins

Remove unneeded class

Starting to fix tests

Another fix

Lots of tests

Final lint and tests

Fix backend tests
parent c7fc256d
No related branches found
No related tags found
No related merge requests found
Showing
with 135 additions and 287 deletions
Loading
Loading
@@ -79,53 +79,49 @@ export default {
</script>
 
<template>
<div class="geo-node-actions">
<div v-if="isSecondaryNode" class="node-action-container">
<a :href="node.geoProjectsUrl" class="btn btn-sm btn-node-action" target="_blank">
<icon v-if="!node.current" name="external-link" /> {{ __('Open projects') }}
</a>
</div>
<div class="d-flex align-items-center justify-content-end geo-node-actions">
<a v-if="isSecondaryNode" :href="node.geoProjectsUrl" class="btn btn-sm mx-1 " target="_blank">
<icon v-if="!node.current" name="external-link" /> {{ __('Open projects') }}
</a>
<template v-if="nodeActionsAllowed">
<div v-if="nodeMissingOauth" class="node-action-container">
<button type="button" class="btn btn-default btn-sm btn-node-action" @click="onRepairNode">
{{ s__('Repair authentication') }}
</button>
</div>
<div v-if="isToggleAllowed" class="node-action-container">
<button
:class="{
'btn-warning': node.enabled,
'btn-success': !node.enabled,
}"
type="button"
class="btn btn-sm btn-node-action"
@click="onToggleNode"
>
<icon :name="nodeToggleIcon" />
{{ nodeToggleLabel }}
</button>
</div>
<div v-if="nodeEditAllowed" class="node-action-container">
<a :href="node.editPath" class="btn btn-sm btn-node-action"> {{ __('Edit') }} </a>
</div>
<div class="node-action-container">
<button
v-if="isSecondaryNode"
type="button"
class="btn btn-sm btn-node-action btn-danger"
@click="onRemoveSecondaryNode"
>
{{ __('Remove') }}
</button>
<button
v-else
type="button"
class="btn btn-sm btn-node-action btn-danger"
@click="onRemovePrimaryNode"
>
{{ __('Remove') }}
</button>
</div>
<button
v-if="nodeMissingOauth"
type="button"
class="btn btn-sm btn-default mx-1"
@click="onRepairNode"
>
{{ s__('Repair authentication') }}
</button>
<button
v-if="isToggleAllowed"
:class="{
'btn-warning': node.enabled,
'btn-success': !node.enabled,
}"
type="button"
class="btn btn-sm mx-1"
@click="onToggleNode"
>
<icon :name="nodeToggleIcon" />
{{ nodeToggleLabel }}
</button>
<a v-if="nodeEditAllowed" :href="node.editPath" class="btn btn-sm mx-1"> {{ __('Edit') }} </a>
<button
v-if="isSecondaryNode"
type="button"
class="btn btn-sm btn-danger mx-1"
@click="onRemoveSecondaryNode"
>
{{ __('Remove') }}
</button>
<button
v-if="!isSecondaryNode"
type="button"
class="btn btn-sm btn-danger mx-1"
@click="onRemovePrimaryNode"
>
{{ __('Remove') }}
</button>
</template>
</div>
</template>
Loading
Loading
@@ -126,19 +126,21 @@ export default {
</script>
 
<template>
<div v-if="!featureDisabled" class="prepend-top-15 prepend-left-10 node-detail-item">
<div class="node-detail-title">
<span>{{ itemTitle }}</span>
<div v-if="!featureDisabled" class="mt-2 ml-2 node-detail-item">
<div class="d-flex align-items-center text-secondary-700">
<span class="node-detail-title">{{ itemTitle }}</span>
<icon
v-if="hasHelpInfo"
v-popover="popoverConfig"
:size="12"
class="node-detail-help-text prepend-left-5"
class="text-primary-600 ml-1 node-detail-help-text"
name="question"
/>
</div>
<div v-if="isValueTypePlain" :class="cssClass" class="node-detail-value">{{ itemValue }}</div>
<div v-if="isValueTypeGraph" :class="{ 'd-flex': itemValueStale }" class="node-detail-value">
<div v-if="isValueTypePlain" :class="cssClass" class="mt-1 node-detail-value">
{{ itemValue }}
</div>
<div v-if="isValueTypeGraph" :class="{ 'd-flex': itemValueStale }" class="mt-1">
<stacked-progress-bar
:css-class="itemValueStale ? 'flex-fill' : ''"
:success-label="successLabel"
Loading
Loading
@@ -153,7 +155,7 @@ export default {
v-tooltip
:title="itemValueStaleTooltip"
name="time-out"
class="prepend-left-10 detail-value-stale-icon"
class="ml-2 text-warning-500"
data-container="body"
/>
</div>
Loading
Loading
Loading
Loading
@@ -66,7 +66,7 @@ export default {
</script>
 
<template>
<div class="card-body">
<div class="card-body p-0">
<node-details-section-main
:node="node"
:node-details="nodeDetails"
Loading
Loading
@@ -85,8 +85,8 @@ export default {
:node-details="nodeDetails"
:node-type-primary="node.primary"
/>
<div v-if="hasError || hasVersionMismatch" class="node-health-message-container">
<p class="node-health-message">
<div v-if="hasError || hasVersionMismatch">
<p class="p-3 mb-0 bg-danger-100 text-danger-500">
{{ errorMessage }}
<gl-link :href="geoTroubleshootingHelpPath">{{
s__('Geo|Please refer to Geo Troubleshooting.')
Loading
Loading
Loading
Loading
@@ -39,7 +39,7 @@ export default {
</script>
 
<template>
<div class="node-detail-value">
<div class="mt-1 node-detail-value">
<template v-if="eventTimeStamp">
<strong> {{ eventString }} </strong>
<span
Loading
Loading
Loading
Loading
@@ -42,11 +42,10 @@ export default {
return this.isNodeHTTP || this.nodeDetailsFailed;
},
nodeStatusIconClass() {
const iconClasses = 'prepend-left-10 node-status-icon';
if (this.nodeDetailsFailed) {
return `${iconClasses} status-icon-failure`;
}
return `${iconClasses} status-icon-warning`;
return [
'ml-2',
{ 'text-danger-500': this.nodeDetailsFailed, 'text-warning-500': !this.nodeDetailsFailed },
];
},
nodeStatusIconName() {
if (this.nodeDetailsFailed) {
Loading
Loading
@@ -70,7 +69,7 @@ export default {
<div class="card-header">
<div class="row">
<div class="col-md-8 clearfix">
<span class="d-flex float-left append-right-10">
<span class="d-flex align-items-center float-left append-right-10">
<strong class="node-url"> {{ node.url }} </strong>
<gl-loading-icon
v-if="nodeDetailsLoading || node.nodeActionActive"
Loading
Loading
@@ -87,11 +86,17 @@ export default {
data-placement="bottom"
/>
</span>
<span class="inline node-type-badges">
<span v-if="node.current" class="node-badge current-node">
<span class="inline">
<span
v-if="node.current"
class="rounded-pill gl-font-size-12 p-1 text-white bg-success-400"
>
{{ s__('Current node') }}
</span>
<span v-if="node.primary" class="prepend-left-5 node-badge primary-node">
<span
v-if="node.primary"
class="ml-1 rounded-pill gl-font-size-12 p-1 text-white bg-primary-600"
>
{{ s__('Primary') }}
</span>
</span>
Loading
Loading
<script>
import icon from '~/vue_shared/components/icon.vue';
import { HEALTH_STATUS_ICON } from '../constants';
import { HEALTH_STATUS_ICON, HEALTH_STATUS_CLASS } from '../constants';
 
export default {
components: {
Loading
Loading
@@ -14,7 +14,7 @@ export default {
},
computed: {
healthCssClass() {
return `geo-node-${this.status.toLowerCase()}`;
return HEALTH_STATUS_CLASS[this.status.toLowerCase()];
},
statusIconName() {
return HEALTH_STATUS_ICON[this.status.toLowerCase()];
Loading
Loading
@@ -24,11 +24,11 @@ export default {
</script>
 
<template>
<div class="prepend-top-15 detail-section-item">
<div class="node-detail-title">{{ s__('GeoNodes|Health status') }}</div>
<div :class="healthCssClass" class="node-detail-value node-health-status">
<div class="mt-2 detail-section-item">
<div class="text-secondary-700 node-detail-title">{{ s__('GeoNodes|Health status') }}</div>
<div :class="healthCssClass" class="mt-1 d-flex align-items-center node-health-status">
<icon :size="16" :name="statusIconName" />
<span class="status-text prepend-left-5"> {{ status }} </span>
<span class="status-text ml-2"> {{ status }} </span>
</div>
</div>
</template>
Loading
Loading
@@ -78,7 +78,7 @@ export default {
</script>
 
<template>
<div :class="{ 'node-action-active': node.nodeActionActive }" class="card geo-node-item">
<div :class="{ 'node-action-active': node.nodeActionActive }" class="card">
<geo-node-header
:node="node"
:node-details="nodeDetails"
Loading
Loading
@@ -93,8 +93,8 @@ export default {
:node-actions-allowed="nodeActionsAllowed"
:geo-troubleshooting-help-path="geoTroubleshootingHelpPath"
/>
<div v-if="isNodeDetailsFailed" class="node-health-message-container">
<p class="node-health-message">
<div v-if="isNodeDetailsFailed">
<p class="p-3 mb-0 bg-danger-100 text-danger-500">
{{ errorMessage
}}<gl-link :href="geoTroubleshootingHelpPath">{{
s__('Geo|Please refer to Geo Troubleshooting.')
Loading
Loading
Loading
Loading
@@ -108,18 +108,18 @@ export default {
</script>
 
<template>
<div class="node-detail-value">
<span v-if="syncStatusUnavailable" class="node-detail-value-bold"> {{ __('Unknown') }} </span>
<div class="mt-1 node-sync-settings">
<strong v-if="syncStatusUnavailable"> {{ __('Unknown') }} </strong>
<span
v-else
v-tooltip
:title="syncStatusTooltip"
class="node-sync-settings"
class="d-flex align-items-center"
data-placement="bottom"
>
<strong>{{ syncType }}</strong>
<icon name="retry" class="sync-status-icon prepend-left-5" />
<span v-if="!eventTimestampEmpty" class="sync-status-event-info prepend-left-5">
<icon name="retry" class="ml-2" />
<span v-if="!eventTimestampEmpty" class="ml-2">
{{ syncStatusEventInfo }}
</span>
</span>
Loading
Loading
Loading
Loading
@@ -46,13 +46,13 @@ export default {
</script>
 
<template>
<div class="row-fluid clearfix node-detail-section primary-section">
<div class="col-md-8 section-items-container">
<div class="detail-section-item node-version">
<div class="node-detail-title">{{ s__('GeoNodes|GitLab version') }}</div>
<div class="row-fluid clearfix py-3 primary-section">
<div class="col-md-8">
<div>
<div class="text-secondary-700 node-detail-title">{{ s__('GeoNodes|GitLab version') }}</div>
<div
:class="{ 'node-detail-value-error': versionMismatch }"
class="node-detail-value node-detail-value-bold"
:class="{ 'text-danger-500': versionMismatch }"
class="mt-1 font-weight-bold node-detail-value"
>
{{ nodeVersion }}
</div>
Loading
Loading
Loading
Loading
@@ -54,7 +54,7 @@ export default {
itemTitle: s__('GeoNodes|Replication slot WAL'),
itemValue: numberToHumanSize(this.nodeDetails.replicationSlotWAL),
itemValueType: VALUE_TYPE.PLAIN,
cssClass: 'node-detail-value-bold',
cssClass: 'font-weight-bold',
});
}
 
Loading
Loading
@@ -63,7 +63,7 @@ export default {
itemTitle: s__('GeoNodes|Internal URL'),
itemValue: this.node.internalUrl,
itemValueType: VALUE_TYPE.PLAIN,
cssClass: 'node-detail-value-bold',
cssClass: 'font-weight-bold',
});
}
 
Loading
Loading
@@ -76,7 +76,7 @@ export default {
itemTitle: s__('GeoNodes|Storage config'),
itemValue: this.storageShardsStatus,
itemValueType: VALUE_TYPE.PLAIN,
cssClass: this.storageShardsCssClass,
cssClass: this.storageShardsCssClass.join(' '),
},
];
},
Loading
Loading
@@ -89,10 +89,7 @@ export default {
: s__('GeoNodes|Does not match the primary storage configuration');
},
storageShardsCssClass() {
const cssClass = 'node-detail-value-bold';
return !this.nodeDetails.storageShardsMatch
? `${cssClass} node-detail-value-error`
: cssClass;
return ['font-weight-bold', { 'text-danger-500': !this.nodeDetails.storageShardsMatch }];
},
},
methods: {
Loading
Loading
@@ -104,17 +101,14 @@ export default {
</script>
 
<template>
<div class="row-fluid clearfix node-detail-section other-section">
<div class="row-fluid clearfix py-3 border-top border-color-default other-section">
<div class="col-md-12">
<section-reveal-button
:button-title="__('Other information')"
@toggleButton="handleSectionToggle"
/>
</div>
<div
v-show="showSectionItems"
class="col-md-6 prepend-left-15 prepend-top-10 section-items-container"
>
<div v-if="showSectionItems" class="col-md-6 ml-2 mt-2 section-items-container">
<geo-node-detail-item
v-for="(nodeDetailItem, index) in nodeDetailItems"
:key="index"
Loading
Loading
Loading
Loading
@@ -130,17 +130,14 @@ export default {
</script>
 
<template>
<div class="row-fluid clearfix node-detail-section sync-section">
<div class="row-fluid clearfix py-3 border-top border-color-default sync-section">
<div class="col-md-12">
<section-reveal-button
:button-title="__('Sync information')"
@toggleButton="handleSectionToggle"
/>
</div>
<div
v-show="showSectionItems"
class="col-md-6 prepend-left-15 prepend-top-10 section-items-container"
>
<div v-if="showSectionItems" class="col-md-6 ml-2 mt-2 section-items-container">
<geo-node-detail-item
v-for="(nodeDetailItem, index) in nodeDetailItems"
:key="index"
Loading
Loading
Loading
Loading
@@ -113,7 +113,7 @@ export default {
</script>
 
<template>
<div class="row-fluid clearfix node-detail-section verification-section">
<div class="row-fluid clearfix py-3 border-top border-color-default verification-section">
<div class="col-md-12">
<section-reveal-button
:button-title="__('Verification information')"
Loading
Loading
@@ -121,7 +121,7 @@ export default {
/>
</div>
<template v-if="showSectionItems">
<div class="col-md-6 prepend-left-15 prepend-top-10 section-items-container">
<div class="col-md-6 ml-2 mt-2 section-items-container">
<geo-node-detail-item
v-for="(nodeDetailItem, index) in nodeDetailItems"
:key="index"
Loading
Loading
Loading
Loading
@@ -31,7 +31,7 @@ export default {
</script>
 
<template>
<button class="btn-link btn-show-section" type="button" @click="onClickButton">
<button class="btn-link d-flex align-items-center" type="button" @click="onClickButton">
<icon :size="16" :name="toggleButtonIcon" />
<span class="prepend-left-8">{{ buttonTitle }}</span>
</button>
Loading
Loading
Loading
Loading
@@ -23,6 +23,14 @@ export const HEALTH_STATUS_ICON = {
offline: 'status_canceled',
};
 
export const HEALTH_STATUS_CLASS = {
healthy: 'text-success-500',
unhealthy: 'text-danger-500',
disabled: 'text-secondary-950',
unknown: 'cdark',
offline: 'cdark',
};
export const TIME_DIFF = {
FIVE_MINS: 300,
HOUR: 3600,
Loading
Loading
.node-badge {
color: $white-light;
padding: 1px $gl-padding-8;
font-size: $label-font-size;
border-radius: $label-border-radius;
@media (max-width: map-get($grid-breakpoints, sm)) {
.geo-node-actions {
flex-direction: column;
margin: 0 1rem;
 
&.primary-node {
background-color: $blue-600;
}
&.current-node {
background-color: $green-400;
}
}
.geo-node-healthy {
color: $green-500;
}
.geo-node-unhealthy {
color: $red-500;
}
.geo-node-offline {
color: $gray-950;
}
.geo-node-disabled {
color: $gray-darkest;
}
.geo-node-unknown {
color: $gray-darkest;
}
.geo-node-item {
.node-status-icon {
height: 35px;
}
.status-icon-warning {
fill: $orange-500;
}
.status-icon-failure {
fill: $red-500;
}
.card-body {
padding: 0;
.node-detail-section {
padding: $gl-padding 0;
&.sync-section,
&.verification-section,
&.other-section {
border-top: 1px solid $border-color;
}
.btn-show-section {
padding: 0;
}
}
}
.node-health-message-container {
max-height: $dropdown-max-height;
overflow-y: auto;
.node-health-message {
margin-bottom: 0;
padding: $gl-padding;
background-color: $red-100;
color: $red-500;
}
}
}
.node-detail-section {
.detail-section-item,
.section-items-container {
.node-detail-title {
color: $gray-700;
.node-detail-help-text {
color: $blue-600;
}
.tooltip .tooltip-inner {
text-align: left;
}
}
.node-detail-value {
margin-top: 4px;
}
.detail-value-stale-icon {
color: $orange-500;
}
.node-detail-value-bold {
font-weight: $gl-font-weight-bold;
}
.node-detail-value-error {
color: $red-500;
}
}
.section-items-container {
display: inline-block;
.node-detail-item {
&:first-child {
margin-top: 0;
}
}
@include media-breakpoint-down(sm) {
width: 95%;
}
}
}
.node-detail-title,
.node-health-status,
.node-sync-settings,
.node-detail-section .btn-show-section {
display: flex;
align-items: center;
}
.geo-node-actions {
display: inline-flex;
justify-content: center;
float: right;
.node-action-container {
margin: 0 5px;
&:last-child {
margin-right: $gl-padding;
}
}
@include media-breakpoint-down(sm) {
display: block;
width: 100%;
.node-action-container {
width: 100%;
margin: 0;
margin-top: 10px;
padding: 0 10px;
}
.btn-node-action {
.btn-sm {
width: 100%;
margin-top: 1rem;
}
}
}
Loading
Loading
Loading
Loading
@@ -16,7 +16,7 @@ describe 'admin Geo Nodes', :js, :geo do
wait_for_requests
 
expect(page).to have_link('New node', href: new_admin_geo_node_path)
page.within(find('.geo-node-item', match: :first)) do
page.within(find('.card', match: :first)) do
expect(page).to have_content(geo_node.url)
end
end
Loading
Loading
@@ -86,7 +86,7 @@ describe 'admin Geo Nodes', :js, :geo do
expect(current_path).to eq admin_geo_nodes_path
wait_for_requests
 
page.within(find('.geo-node-item', match: :first)) do
page.within(find('.card', match: :first)) do
expect(page).to have_content(geo_node.url)
end
end
Loading
Loading
@@ -167,7 +167,7 @@ describe 'admin Geo Nodes', :js, :geo do
expect(current_path).to eq admin_geo_nodes_path
wait_for_requests
 
page.within(find('.geo-node-item', match: :first)) do
page.within(find('.card', match: :first)) do
expect(page).to have_content('http://newsite.com')
expect(page).to have_content('Primary')
 
Loading
Loading
@@ -196,7 +196,7 @@ describe 'admin Geo Nodes', :js, :geo do
 
expect(current_path).to eq admin_geo_nodes_path
wait_for_requests
expect(page).not_to have_css('.geo-node-item')
expect(page).not_to have_css('.card')
end
end
end
Loading
Loading
@@ -125,8 +125,7 @@ describe('GeoNodeActionsComponent', () => {
describe('template', () => {
it('renders container elements correctly', () => {
expect(vm.$el.classList.contains('geo-node-actions')).toBe(true);
expect(vm.$el.querySelectorAll('.node-action-container').length).not.toBe(0);
expect(vm.$el.querySelectorAll('.btn-node-action').length).not.toBe(0);
expect(vm.$el.querySelectorAll('.btn-sm').length).not.toBe(0);
});
});
});
Loading
Loading
@@ -75,7 +75,7 @@ describe('GeoNodeDetailItemComponent', () => {
itemValueStaleTooltip,
});
 
const iconEl = vm.$el.querySelector('.detail-value-stale-icon');
const iconEl = vm.$el.querySelector('.text-warning-500');
 
expect(iconEl).not.toBeNull();
expect(iconEl.dataset.originalTitle).toBe(itemValueStaleTooltip);
Loading
Loading
Loading
Loading
@@ -87,9 +87,7 @@ describe('GeoNodeDetailsComponent', () => {
vm.errorMessage = 'Foobar';
 
vm.$nextTick(() => {
expect(vm.$el.querySelector('.node-health-message-container a').getAttribute('href')).toBe(
'/foo/bar',
);
expect(vm.$el.querySelector('.bg-danger-100 a').getAttribute('href')).toBe('/foo/bar');
done();
});
});
Loading
Loading
import Vue from 'vue';
 
import geoNodeHealthStatusComponent from 'ee/geo_nodes/components/geo_node_health_status.vue';
import { HEALTH_STATUS_ICON, HEALTH_STATUS_CLASS } from 'ee/geo_nodes/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockNodeDetails } from '../mock_data';
 
Loading
Loading
@@ -16,38 +17,38 @@ describe('GeoNodeHealthStatusComponent', () => {
describe('computed', () => {
describe('healthCssClass', () => {
it('returns CSS class representing `status` prop value', () => {
const vm = createComponent('Healthy');
const vm = createComponent('healthy');
 
expect(vm.healthCssClass).toBe('geo-node-healthy');
expect(vm.healthCssClass).toBe(HEALTH_STATUS_CLASS.healthy);
vm.$destroy();
});
});
 
describe('statusIconName', () => {
it('returns icon name representing `status` prop value', () => {
let vm = createComponent('Healthy');
let vm = createComponent('healthy');
 
expect(vm.statusIconName).toBe('status_success');
expect(vm.statusIconName).toBe(HEALTH_STATUS_ICON.healthy);
vm.$destroy();
 
vm = createComponent('Unhealthy');
vm = createComponent('unhealthy');
 
expect(vm.statusIconName).toBe('status_failed');
expect(vm.statusIconName).toBe(HEALTH_STATUS_ICON.unhealthy);
vm.$destroy();
 
vm = createComponent('Disabled');
vm = createComponent('disabled');
 
expect(vm.statusIconName).toBe('status_canceled');
expect(vm.statusIconName).toBe(HEALTH_STATUS_ICON.disabled);
vm.$destroy();
 
vm = createComponent('Unknown');
vm = createComponent('unknown');
 
expect(vm.statusIconName).toBe('status_warning');
expect(vm.statusIconName).toBe(HEALTH_STATUS_ICON.unknown);
vm.$destroy();
 
vm = createComponent('Offline');
vm = createComponent('offline');
 
expect(vm.statusIconName).toBe('status_canceled');
expect(vm.statusIconName).toBe(HEALTH_STATUS_ICON.offline);
vm.$destroy();
});
});
Loading
Loading
@@ -60,7 +61,7 @@ describe('GeoNodeHealthStatusComponent', () => {
expect(vm.$el.classList.contains('detail-section-item')).toBe(true);
expect(vm.$el.querySelector('.node-detail-title').innerText.trim()).toBe('Health status');
 
const iconContainerEl = vm.$el.querySelector('.node-detail-value.node-health-status');
const iconContainerEl = vm.$el.querySelector('.node-health-status');
 
expect(iconContainerEl).not.toBeNull();
expect(iconContainerEl.querySelector('svg use').getAttribute('xlink:href')).toContain(
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment