Skip to content

Commit

Permalink
Replication Primary Dashboard: handle errors (#8845)
Browse files Browse the repository at this point in the history
* use h3 instead of code elements

* use correct property names for StateDisplay

* WIP

* remove todo

* move cluster states into a map; make status menu icon match cluster state

* show error in state card using the same state map in the cluster model

* whitespace

* move cluster-states into a helper and update usage

* use circle success icon for stream-wals because that is the ideal state

* more refactoring of cluster state display

* use new cluster-states helper

* whitespace

* use clusterStates helper in replication secondary card

* remove extra import

* add default values for when state isn't recognized

* make sure that state exists before getting state details from clusterStates helper

* be more strict when state cannot be found

* use brace expansion to fix linting error

* add tests for error states

* fix text wrapping issue on secondary cards; make titles match mocks

* use unknown if metric isn't foudn

* remove extra border on selectable card when there is an error

* use outline square in status menu for error
  • Loading branch information
Noelle Daley authored Apr 28, 2020
1 parent 2f78a9a commit 36f0a80
Show file tree
Hide file tree
Showing 18 changed files with 293 additions and 200 deletions.
43 changes: 3 additions & 40 deletions ui/app/models/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,46 +49,9 @@ export default DS.Model.extend({

anyReplicationEnabled: or('{dr,performance}.replicationEnabled'),

stateDisplay(state) {
if (!state) {
return null;
}
const defaultDisp = 'Synced';
const displays = {
'stream-wals': 'Streaming',
'merkle-diff': 'Determining sync status',
'merkle-sync': 'Syncing',
};

return displays[state] || defaultDisp;
},

drStateDisplay: computed('dr.state', function() {
return this.stateDisplay(this.get('dr.state'));
}),

performanceStateDisplay: computed('performance.state', function() {
return this.stateDisplay(this.get('performance.state'));
}),

stateGlyph(state) {
const glyph = 'check-circle-outline';

const glyphs = {
'stream-wals': 'check-circle-outline',
'merkle-diff': 'android-sync',
'merkle-sync': 'android-sync',
};

return glyphs[state] || glyph;
},

drStateGlyph: computed('dr.state', function() {
return this.stateGlyph(this.get('dr.state'));
}),

performanceStateGlyph: computed('performance.state', function() {
return this.stateGlyph(this.get('performance.state'));
modeState: computed('dr.{mode,state}', 'performance.{mode,state}', 'replicationMode', function() {
const mode = this.replicationMode;
return this.get(`${mode}.state`);
}),

dr: fragment('replication-attributes'),
Expand Down
13 changes: 6 additions & 7 deletions ui/app/styles/components/replication-dashboard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@
&:hover {
box-shadow: 0 0 0 1px rgba($grey-dark, 0.3);
}

.title-number {
font-size: $size-3;
align-self: end;
}
}

.helper-text {
font-weight: $font-weight-normal;
}

.title.is-6 {
margin-bottom: $spacing-xs;
}

.selectable-card-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
Expand All @@ -24,9 +23,10 @@

.card-container {
display: grid;
grid-gap: 2rem;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr 0.8fr;
padding: $spacing-l 0 18px $spacing-l;
padding: $spacing-l;
line-height: 1.5;

height: 230px;
Expand All @@ -38,7 +38,6 @@

.grid-item-title {
grid-column: 1 / span 2;

display: flex;
}
.grid-item-left {
Expand Down
4 changes: 4 additions & 0 deletions ui/app/styles/components/selectable-card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@
border-radius: $radius;
}

.selectable-card.has-border-danger {
box-shadow: none;
}

.change-metric-icon.is-decrease {
transform: rotate(135deg);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<section class="section">
<div class="container is-widescreen">
<ReplicationPage @model={{model}} as |Page|>
<Page.header
<Page.header
@showTabs={{false}}
/>
{{#if Page.isDisabled}}
Expand Down Expand Up @@ -29,7 +29,7 @@
</EmptyState>
{{else}}
<Page.toggle />
<Page.dashboard
<Page.dashboard
{{!-- passing in component to render so that the yielded components are flexible based on the dashboard --}}
@componentToRender='replication-secondary-card' as |Dashboard|>
<Dashboard.card
Expand All @@ -39,7 +39,7 @@
@title="Write-Ahead Logs (WALs)"
/>
{{#if Dashboard.isSyncing }}
<AlertBanner
<AlertBanner
@title="Syncing in progress"
@type="info"
@secondIconType="loading"
Expand Down
15 changes: 10 additions & 5 deletions ui/lib/core/addon/components/replication-dashboard.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import { clusterStates } from 'core/helpers/cluster-states';
import layout from '../templates/components/replication-dashboard';

const MERKLE_STATES = { sync: 'merkle-sync', diff: 'merkle-diff' };

export default Component.extend({
layout,
data: null,
isSyncing: computed('data', function() {
if (this.dr.state === MERKLE_STATES.sync || this.dr.state === MERKLE_STATES.diff) {
return true;
dr: computed('data', function() {
let dr = this.data.dr;
if (!dr) {
return false;
}
return dr;
}),
isSyncing: computed('dr', function() {
const { state } = this.dr;
return state && clusterStates([state]).isSyncing;
}),
});
3 changes: 3 additions & 0 deletions ui/lib/core/addon/components/replication-mode-summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,7 @@ export default Component.extend({
clusterIdDisplay: replicationAttr('clusterIdDisplay'),
mode: null,
cluster: null,
modeState: computed('cluster', function() {
return this.cluster.modeState;
}),
});
31 changes: 13 additions & 18 deletions ui/lib/core/addon/components/replication-secondary-card.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import layout from '../templates/components/replication-secondary-card';

const STATES = {
streamWals: 'stream-wals',
idle: 'idle',
transientFailure: 'transient_failure',
shutdown: 'shutdown',
};
import { clusterStates } from 'core/helpers/cluster-states';

export default Component.extend({
layout,
Expand All @@ -28,19 +22,20 @@ export default Component.extend({
return Math.abs(this.get('lastWAL') - this.get('lastRemoteWAL'));
}),
inSyncState: computed('state', function() {
if (this.state === STATES.streamWals) {
return true;
}
// if our definition of what is considered 'synced' changes,
// we should use the clusterStates helper instead
return this.state === 'stream-wals';
}),

hasErrorClass: computed('data', 'title', 'state', 'connection', function() {
if (this.title === 'States') {
if (
this.state === STATES.idle ||
this.connection === STATES.transientFailure ||
this.connection === STATES.shutdown
) {
return true;
}
const { title, state, connection } = this;

// only show errors on the state card
if (title === 'States') {
const currentClusterisOk = clusterStates([state]).isOk;
const primaryIsOk = clusterStates([connection]).isOk;
return !(currentClusterisOk && primaryIsOk);
}
return false;
}),
});
1 change: 1 addition & 0 deletions ui/lib/core/addon/components/toggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default Component.extend({
tagName: '',
checked: false,
disabled: false,
name: '',
size: 'normal',
status: 'normal',
safeId: computed('name', function() {
Expand Down
69 changes: 69 additions & 0 deletions ui/lib/core/addon/helpers/cluster-states.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { helper as buildHelper } from '@ember/component/helper';

// A hash of cluster states to ensure that the status menu and replication dashboards
// display states and glyphs consistently
// this includes states for the primary vault cluster and the connection_state

export const CLUSTER_STATES = {
running: {
glyph: 'check-circle-outline',
isOk: true,
isSyncing: false,
},
ready: {
glyph: 'check-circle-outline',
isOk: true,
isSyncing: false,
},
'stream-wals': {
glyph: 'check-circle-outline',
display: 'Streaming',
isOk: true,
isSyncing: false,
},
'merkle-diff': {
glyph: 'android-sync',
display: 'Determining sync status',
isOk: true,
isSyncing: true,
},
connecting: {
glyph: 'android-sync',
display: 'Streaming',
isOk: true,
isSyncing: true,
},
'merkle-sync': {
glyph: 'android-sync',
display: 'Syncing',
isOk: true,
isSyncing: true,
},
idle: {
glyph: 'cancel-square-outline',
isOk: false,
isSyncing: false,
},
transient_failure: {
glyph: 'cancel-circle-outline',
isOk: false,
isSyncing: false,
},
shutdown: {
glyph: 'cancel-circle-outline',
isOk: false,
isSyncing: false,
},
};

export function clusterStates([state]) {
const defaultDisplay = {
glyph: '',
display: '',
isOk: null,
isSyncing: null,
};
return CLUSTER_STATES[state] || defaultDisplay;
}

export default buildHelper(clusterStates);
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@
</div>
<div class="level-right">
{{#if replicationEnabled}}
{{#if (get cluster (concat mode 'StateGlyph'))}}
<span class="has-text-success">
{{#if (cluster-states modeState)}}
<span class="{{if (get (cluster-states modeState) "isOk") "has-text-success" "has-text-danger"}}">
<Icon
aria-hidden="true"
@glyph={{get cluster (concat mode 'StateGlyph')}}
@glyph={{get (cluster-states modeState) "glyph"}}
/>
</span>
{{else if syncProgress}}
Expand Down Expand Up @@ -104,10 +104,8 @@
{{#link-to "vault.cluster.replication.mode.index" cluster.name mode class="button is-primary"}}
Enable
{{/link-to}}
{{else if (eq mode 'dr')}}
{{cluster.drReplicationStateDisplay}}
{{else if (eq mode 'performance')}}
{{cluster.perfReplicationStateDisplay}}
{{else}}
{{get (cluster-states modeState) "display"}}
{{/if}}
</div>
{{/if}}
Loading

0 comments on commit 36f0a80

Please sign in to comment.