From e1c56a300fd9935d86effa1a020673cb823641b2 Mon Sep 17 00:00:00 2001
From: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>
Date: Wed, 11 Sep 2024 09:19:33 -0500
Subject: [PATCH] UI: reorg replication (#28332)
* Add replication-overview-mode component + tests
* Move both primary view higher to template
* simplify replication-summary component
* remove replication-mode-summary
* Add jsdocs to replication-overview-mode
* fix overview-mode test
* fix page/mode-index test
* copyright
* address PR comments
* note to devs
---
.../components/replication-mode-summary.scss | 16 ---
ui/app/styles/core.scss | 1 -
.../components/replication-mode-summary.js | 68 ----------
.../components/replication-mode-summary.hbs | 122 -----------------
.../components/replication-mode-summary.js | 6 -
.../components/replication-overview-mode.hbs | 49 +++++++
.../components/replication-overview-mode.js | 39 ++++++
.../addon/components/replication-summary.js | 8 +-
.../components/replication-summary.hbs | 113 +++++-----------
ui/lib/replication/addon/templates/index.hbs | 31 ++++-
.../components/page/mode-index-test.js | 2 +-
.../replication-overview-mode-test.js | 124 ++++++++++++++++++
12 files changed, 282 insertions(+), 297 deletions(-)
delete mode 100644 ui/app/styles/components/replication-mode-summary.scss
delete mode 100644 ui/lib/core/addon/components/replication-mode-summary.js
delete mode 100644 ui/lib/core/addon/templates/components/replication-mode-summary.hbs
delete mode 100644 ui/lib/core/app/components/replication-mode-summary.js
create mode 100644 ui/lib/replication/addon/components/replication-overview-mode.hbs
create mode 100644 ui/lib/replication/addon/components/replication-overview-mode.js
create mode 100644 ui/tests/integration/components/replication-overview-mode-test.js
diff --git a/ui/app/styles/components/replication-mode-summary.scss b/ui/app/styles/components/replication-mode-summary.scss
deleted file mode 100644
index a96a3d15c6..0000000000
--- a/ui/app/styles/components/replication-mode-summary.scss
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-.replication-description {
- flex-shrink: 1;
-
- .title {
- margin-bottom: $spacing-8;
- }
-
- .detail-tags {
- margin-bottom: $spacing-16;
- }
-}
diff --git a/ui/app/styles/core.scss b/ui/app/styles/core.scss
index 2d020970b7..73d9e17963 100644
--- a/ui/app/styles/core.scss
+++ b/ui/app/styles/core.scss
@@ -91,7 +91,6 @@
@import './components/read-more';
@import './components/regex-validator';
@import './components/replication-dashboard';
-@import './components/replication-mode-summary';
@import './components/replication-page';
@import './components/replication-summary';
@import './components/role-item';
diff --git a/ui/lib/core/addon/components/replication-mode-summary.js b/ui/lib/core/addon/components/replication-mode-summary.js
deleted file mode 100644
index 0938b0399d..0000000000
--- a/ui/lib/core/addon/components/replication-mode-summary.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { service } from '@ember/service';
-import { equal } from '@ember/object/computed';
-import { get, computed } from '@ember/object';
-import Component from '@ember/component';
-import layout from '../templates/components/replication-mode-summary';
-
-const replicationAttr = function (attr) {
- return computed(`cluster.{dr,performance}.${attr}`, 'cluster', 'mode', function () {
- const { mode, cluster } = this;
- return get(cluster, `${mode}.${attr}`);
- });
-};
-export default Component.extend({
- layout,
- version: service(),
- router: service(),
- namespace: service(),
- classNameBindings: ['isMenu::box'],
- attributeBindings: ['href', 'target'],
- display: 'banner',
- isMenu: equal('display', 'menu'),
- href: computed(
- 'cluster.id',
- 'display',
- 'mode',
- 'replicationEnabled',
- 'version.hasPerfReplication',
- function () {
- const display = this.display;
- const mode = this.mode;
- if (mode === 'performance' && display === 'menu' && this.version.hasPerfReplication === false) {
- return 'https://www.hashicorp.com/products/vault';
- }
- if (this.replicationEnabled || display === 'menu') {
- return this.router.urlFor('vault.cluster.replication.mode.index', this.cluster.id, mode);
- }
- return null;
- }
- ),
- target: computed('isPerformance', 'version.hasPerfReplication', function () {
- if (this.isPerformance && this.version.hasPerfReplication === false) {
- return '_blank';
- }
- return null;
- }),
- internalLink: false,
- isPerformance: equal('mode', 'performance'),
- replicationEnabled: replicationAttr('replicationEnabled'),
- replicationUnsupported: equal('cluster.mode', 'unsupported'),
- replicationDisabled: replicationAttr('replicationDisabled'),
- syncProgressPercent: replicationAttr('syncProgressPercent'),
- syncProgress: replicationAttr('syncProgress'),
- secondaryId: replicationAttr('secondaryId'),
- modeForUrl: replicationAttr('modeForUrl'),
- clusterIdDisplay: replicationAttr('clusterIdDisplay'),
- mode: null,
- cluster: null,
- modeState: computed('cluster', 'mode', function () {
- const { cluster, mode } = this;
- const clusterState = cluster[mode].state;
- return clusterState;
- }),
-});
diff --git a/ui/lib/core/addon/templates/components/replication-mode-summary.hbs b/ui/lib/core/addon/templates/components/replication-mode-summary.hbs
deleted file mode 100644
index bd2eec9950..0000000000
--- a/ui/lib/core/addon/templates/components/replication-mode-summary.hbs
+++ /dev/null
@@ -1,122 +0,0 @@
-{{!
- Copyright (c) HashiCorp, Inc.
- SPDX-License-Identifier: BUSL-1.1
-~}}
-
-{{#if this.isMenu}}
- {{! this is the status menu }}
-
-
- {{#if this.replicationUnsupported}}
- Unsupported
- {{else if this.replicationEnabled}}
-
- {{concat (if (eq this.mode "performance") "Performance " "Disaster Recovery ") (capitalize this.modeForUrl)}}
-
- {{#if this.secondaryId}}
-
-
- {{this.secondaryId}}
-
-
- {{/if}}
-
-
- {{this.clusterIdDisplay}}
-
-
- {{else if (and (eq this.mode "performance") (not (has-feature "Performance Replication")))}}
- Learn more
- {{else if this.auth.currentToken}}
- Enable
- {{if (eq this.mode "performance") "Performance" "Disaster Recovery"}}
- {{else}}
-
- {{if (eq this.mode "performance") "Performance" "Disaster Recovery"}}
-
- {{/if}}
-
-
- {{#if this.replicationEnabled}}
- {{#if (cluster-states this.modeState)}}
-
-
-
- {{else if this.syncProgress}}
-
- {{/if}}
- {{else}}
-
- {{/if}}
-
-
-{{else}}
- {{! this is the replication index page }}
-
-
-
- {{#if (and (eq this.mode "performance") (not (has-feature "Performance Replication")))}}
-
- Performance Replication is a feature of Vault Enterprise Premium.
-
- Upgrade
-
-
- {{else if (and (eq this.mode "dr") (not (has-feature "DR Replication")))}}
-
- Disaster Recovery is a feature of Vault Enterprise Premium.
-
- Upgrade
-
-
- {{else if this.replicationEnabled}}
-
- Enabled
-
-
-
- {{capitalize this.modeForUrl}}
-
- {{#if this.secondaryId}}
-
-
- {{this.secondaryId}}
-
-
- {{/if}}
-
-
- {{this.clusterIdDisplay}}
-
-
-
- {{/if}}
-
- {{replication-mode-description this.mode}}
-
-
-
-
-
-
-
-{{/if}}
\ No newline at end of file
diff --git a/ui/lib/core/app/components/replication-mode-summary.js b/ui/lib/core/app/components/replication-mode-summary.js
deleted file mode 100644
index 605fceb563..0000000000
--- a/ui/lib/core/app/components/replication-mode-summary.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-export { default } from 'core/components/replication-mode-summary';
diff --git a/ui/lib/replication/addon/components/replication-overview-mode.hbs b/ui/lib/replication/addon/components/replication-overview-mode.hbs
new file mode 100644
index 0000000000..424b895fec
--- /dev/null
+++ b/ui/lib/replication/addon/components/replication-overview-mode.hbs
@@ -0,0 +1,49 @@
+{{!
+ Copyright (c) HashiCorp, Inc.
+ SPDX-License-Identifier: BUSL-1.1
+~}}
+
+
+
+
+
+ {{this.details.blockTitle}}
+
+
+ {{#if (not (has-feature this.details.feature))}}
+
+ {{this.details.upgradeTitle}}
+
+ Upgrade
+
+
+ {{else if @model.replicationEnabled}}
+
ENABLED
+
+ {{capitalize @model.modeForUrl}}
+ {{#if @model.secondaryId}}
+
+ {{/if}}
+ {{#if @model.clusterIdDisplay}}
+
+ {{/if}}
+
+ {{/if}}
+
+
{{replication-mode-description @mode}}
+
+ {{#if (has-feature this.details.feature)}}
+
+ {{/if}}
+
+
\ No newline at end of file
diff --git a/ui/lib/replication/addon/components/replication-overview-mode.js b/ui/lib/replication/addon/components/replication-overview-mode.js
new file mode 100644
index 0000000000..0a733d90c1
--- /dev/null
+++ b/ui/lib/replication/addon/components/replication-overview-mode.js
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import Component from '@glimmer/component';
+
+/**
+ * @module ReplicationOverviewModeComponent
+ * ReplicationOverviewMode components are used on the Replication index page to display
+ * details about a given mode (DR or Performance) status.
+ *
+ * @example
+ *
+ *
+ * @param {string} mode - should be "dr" or "performance"
+ * @param {ReplicationAttributesModel} model - either the dr or performance attribute of the cluster model
+ * @param {string} clusterName - used for the link to the mode details
+ */
+export default class ReplicationOverviewModeComponent extends Component {
+ get details() {
+ if (this.args.mode === 'dr') {
+ return {
+ blockTitle: 'Disaster Recovery (DR)',
+ upgradeTitle: 'Disaster Recovery is a feature of Vault Enterprise Premium.',
+ upgradeLink: 'https://hashicorp.com/products/vault/trial?source=vaultui_DR%20Replication',
+ feature: 'DR Replication',
+ icon: 'replication-direct',
+ };
+ }
+ return {
+ blockTitle: 'Performance',
+ upgradeTitle: 'Performance Replication is a feature of Vault Enterprise Premium.',
+ upgradeLink: 'https://hashicorp.com/products/vault/trial?source=vaultui_Performance%20Replication',
+ feature: 'Performance Replication',
+ icon: 'replication-perf',
+ };
+ }
+}
diff --git a/ui/lib/replication/addon/components/replication-summary.js b/ui/lib/replication/addon/components/replication-summary.js
index 6c9e1f576d..43632c6463 100644
--- a/ui/lib/replication/addon/components/replication-summary.js
+++ b/ui/lib/replication/addon/components/replication-summary.js
@@ -8,6 +8,13 @@ import { computed } from '@ember/object';
import Component from '@ember/component';
import ReplicationActions from 'core/mixins/replication-actions';
+/**
+ * @module ReplicationSummary
+ * ReplicationSummary component is a component to show the mode-specific summary for replication
+ *
+ * @param {ClusterModel} cluster - the cluster ember-data model
+ * @param {string} initialReplicationMode - mode for replication details we want to see, either "dr" or "performance"
+ */
export default Component.extend(ReplicationActions, {
'data-test-replication-summary': true,
attributeBindings: ['data-test-replication-summary'],
@@ -22,7 +29,6 @@ export default Component.extend(ReplicationActions, {
this.set('replicationMode', initialReplicationMode);
}
},
- showModeSummary: false,
initialReplicationMode: null,
cluster: null,
diff --git a/ui/lib/replication/addon/templates/components/replication-summary.hbs b/ui/lib/replication/addon/templates/components/replication-summary.hbs
index 133144adbb..138b364581 100644
--- a/ui/lib/replication/addon/templates/components/replication-summary.hbs
+++ b/ui/lib/replication/addon/templates/components/replication-summary.hbs
@@ -3,88 +3,39 @@
SPDX-License-Identifier: BUSL-1.1
~}}
-{{#if (not (has-feature "DR Replication"))}}
-
-{{else if this.showModeSummary}}
- {{#if (not (and this.cluster.dr.replicationEnabled this.cluster.performance.replicationEnabled))}}
-
-
-
- Replication
-
-
-
- {{/if}}
-
- {{#if (and (eq this.cluster.dr.mode "primary") (eq this.cluster.performance.mode "primary"))}}
+{{#if (eq this.attrsForCurrentMode.mode "initializing")}}
+ The cluster is initializing replication. This may take some time.
+{{else}}
+ {{this.cluster.replicationModeStatus.cluster_id}}
+
-
-
-
-
+
+ {{#if (eq this.attrsForCurrentMode.mode "secondary")}}
+
+
+ {{else}}
+
+
+
+ {{/if}}
- {{else}}
-
-
-
- Disaster Recovery (DR)
-
- {{#if this.cluster.dr.replicationEnabled}}
- {{#if this.submit.isRunning}}
-
- {{else}}
-
- {{/if}}
- {{else}}
-
- {{/if}}
-
- {{#if (not (and this.submit.isRunning (eq this.cluster.dr.mode "bootstrapping")))}}
-
-
-
- Performance
-
-
-
- {{/if}}
- {{/if}}
-{{else}}
- {{#if (eq this.attrsForCurrentMode.mode "initializing")}}
- The cluster is initializing replication. This may take some time.
- {{else}}
-
{{this.cluster.replicationModeStatus.cluster_id}}
-
-
-
- {{#if (eq this.attrsForCurrentMode.mode "secondary")}}
-
-
- {{else}}
-
-
-
- {{/if}}
-
-
-
- {{/if}}
+
{{/if}}
\ No newline at end of file
diff --git a/ui/lib/replication/addon/templates/index.hbs b/ui/lib/replication/addon/templates/index.hbs
index 79aefcc044..e2dc0b70e5 100644
--- a/ui/lib/replication/addon/templates/index.hbs
+++ b/ui/lib/replication/addon/templates/index.hbs
@@ -6,6 +6,7 @@
{{#if (eq this.model.mode "unsupported")}}
+ {{! Replication is unsupported in non-enterprise or when using non-transactional storage (eg inmem) }}
@@ -98,8 +99,36 @@
@onSuccess={{this.onEnableSuccess}}
@doTransition={{true}}
/>
+ {{else if (not (has-feature "DR Replication"))}}
+
+ {{else if (and (eq this.model.dr.mode "primary") (eq this.model.performance.mode "primary"))}}
+ {{! Renders when cluster is primary for both replication modes }}
+
+
+
+
+
+
+
{{else}}
-
+ {{! Renders when at least one mode is not enabled }}
+
+
+
+ Replication
+
+
+
+
+
+
+
+
{{/if}}
\ No newline at end of file
diff --git a/ui/tests/integration/components/page/mode-index-test.js b/ui/tests/integration/components/page/mode-index-test.js
index c1a8db07ad..1dca929ab7 100644
--- a/ui/tests/integration/components/page/mode-index-test.js
+++ b/ui/tests/integration/components/page/mode-index-test.js
@@ -22,7 +22,7 @@ module('Integration | Component | replication page/mode-index', function (hooks)
hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store');
this.onEnable = () => {};
- this.clusterModel = {};
+ this.clusterModel = { replicationAttrs: {} };
this.replicationMode = '';
this.replicationDisabled = true;
diff --git a/ui/tests/integration/components/replication-overview-mode-test.js b/ui/tests/integration/components/replication-overview-mode-test.js
new file mode 100644
index 0000000000..dc8729dc6d
--- /dev/null
+++ b/ui/tests/integration/components/replication-overview-mode-test.js
@@ -0,0 +1,124 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'vault/tests/helpers';
+import { render, settled } from '@ember/test-helpers';
+import { hbs } from 'ember-cli-htmlbars';
+import { setupEngine } from 'ember-engines/test-support';
+
+const OVERVIEW_MODE = {
+ title: '[data-test-overview-mode-title]',
+ body: '[data-test-overview-mode-body]',
+ detailsLink: '[data-test-replication-details-link]',
+};
+module('Integration | Component | replication-overview-mode', function (hooks) {
+ setupRenderingTest(hooks);
+ setupEngine(hooks, 'replication');
+
+ hooks.beforeEach(function () {
+ this.versionService = this.owner.lookup('service:version');
+ this.versionService.features = [];
+ this.mode = 'dr';
+ this.clusterName = 'foobar';
+ this.modeDetails = { mode: 'disabled' };
+
+ this.renderComponent = async () => {
+ return render(
+ hbs`
+ `,
+ { owner: this.engine }
+ );
+ };
+ });
+
+ test('without features', async function (assert) {
+ await this.renderComponent();
+ assert.dom(OVERVIEW_MODE.title).hasText('Disaster Recovery (DR)');
+ assert
+ .dom(OVERVIEW_MODE.body)
+ .includesText('Disaster Recovery is a feature of Vault Enterprise Premium. Upgrade');
+ assert.dom(OVERVIEW_MODE.detailsLink).doesNotExist('does not show link to replication (dr)');
+
+ this.set('mode', 'performance');
+ await settled();
+ assert.dom(OVERVIEW_MODE.title).hasText('Performance');
+ assert
+ .dom(OVERVIEW_MODE.body)
+ .includesText('Performance Replication is a feature of Vault Enterprise Premium. Upgrade');
+ assert.dom(OVERVIEW_MODE.detailsLink).doesNotExist('does not show link to replication (perf)');
+ });
+
+ module('with features', function (hooks) {
+ hooks.beforeEach(function () {
+ this.versionService.features = ['DR Replication', 'Performance Replication'];
+ });
+
+ test('it renders when replication disabled', async function (assert) {
+ await this.renderComponent();
+ assert.dom(OVERVIEW_MODE.title).hasText('Disaster Recovery (DR)');
+ assert
+ .dom(OVERVIEW_MODE.body)
+ .hasText(
+ 'Disaster Recovery Replication is designed to protect against catastrophic failure of entire clusters. Secondaries do not forward service requests until they are elected and become a new primary.'
+ );
+ assert.dom(OVERVIEW_MODE.detailsLink).hasText('Enable');
+
+ this.set('mode', 'performance');
+ await settled();
+ assert.dom(OVERVIEW_MODE.title).hasText('Performance');
+ assert
+ .dom(OVERVIEW_MODE.body)
+ .hasText(
+ 'Performance Replication scales workloads horizontally across clusters to make requests faster. Local secondaries handle read requests but forward writes to the primary to be handled.'
+ );
+ assert.dom(OVERVIEW_MODE.detailsLink).hasText('Enable');
+ });
+
+ test('it renders when replication enabled', async function (assert) {
+ this.mode = 'performance';
+ this.modeDetails = {
+ replicationEnabled: true,
+ mode: 'primary',
+ modeForUrl: 'primary',
+ clusterIdDisplay: 'foobar12',
+ };
+ await this.renderComponent();
+ assert.dom(OVERVIEW_MODE.title).hasText('Performance');
+ assert
+ .dom(OVERVIEW_MODE.body)
+ .includesText('ENABLED Primary foobar12', 'renders mode type and cluster ID if passed');
+ assert.dom(OVERVIEW_MODE.detailsLink).hasText('Details');
+
+ this.set('modeDetails', {
+ replicationEnabled: true,
+ mode: 'secondary',
+ modeForUrl: 'secondary',
+ clusterIdDisplay: 'foobar12',
+ secondaryId: 'some-secondary',
+ });
+ await settled();
+ assert.dom(OVERVIEW_MODE.title).hasText('Performance');
+ assert.dom(OVERVIEW_MODE.body).includesText('ENABLED Secondary some-secondary foobar12');
+ assert.dom(OVERVIEW_MODE.detailsLink).hasText('Details');
+ });
+
+ test('it renders when replication bootstrapping', async function (assert) {
+ this.modeDetails = {
+ replicationEnabled: true,
+ mode: 'bootstrapping',
+ modeForUrl: 'bootstrapping',
+ };
+ await this.renderComponent();
+ assert.dom(OVERVIEW_MODE.title).hasText('Disaster Recovery (DR)');
+ assert.dom(OVERVIEW_MODE.body).includesText('ENABLED Bootstrapping');
+ assert.dom(OVERVIEW_MODE.detailsLink).hasText('Details');
+ });
+ });
+});