UI Bug fix: Fix DR Secondary view (#31478)

* test coverage and the fix

* not working

* fix failing test

* fix another test

* changelog

* the correct changelog number
This commit is contained in:
Angel Garbarino 2025-08-13 11:16:46 -06:00 committed by GitHub
parent b76a28a1e0
commit b0d8f4f1be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 355 additions and 142 deletions

3
changelog/31478.txt Normal file
View file

@ -0,0 +1,3 @@
```release-note:bug
ui: Fix DR secondary view from not loading/transitioning.
```

View file

@ -110,8 +110,10 @@ export default Route.extend(ModelBoundaryRoute, ClusterRoute, {
poll: task(function* () {
while (true) {
// when testing, the polling loop causes promises to never settle so acceptance tests hang
// to get around that, we just disable the poll in tests
// In test mode, polling causes acceptance tests to hang due to never-settling promises.
// To avoid this, polling is disabled during tests.
// If your test depends on cluster status changes (e.g., replication mode),
// manually trigger polling using pollCluster from 'vault/tests/helpers/poll-cluster'.
if (Ember.testing) {
return;
}
@ -128,7 +130,8 @@ export default Route.extend(ModelBoundaryRoute, ClusterRoute, {
.cancelOn('deactivate')
.keepLatest(),
async afterModel(model, transition) {
// Note: do not make this afterModel hook async, it will break the DR secondary flow.
afterModel(model, transition) {
this._super(...arguments);
this.currentCluster.setCluster(model);
@ -142,7 +145,20 @@ export default Route.extend(ModelBoundaryRoute, ClusterRoute, {
if (this.namespaceService.path && !this.version.hasNamespaces) {
return this.router.transitionTo(this.routeName, { queryParams: { namespace: '' } });
}
// Skip analytics initialization if the cluster is a DR secondary:
// 1. There is little value in collecting analytics in this state.
// 2. The analytics service requires resolving async setup (e.g. await),
// which delays the afterModel hook resolution and breaks the DR secondary flow.
if (model.dr?.isSecondary) {
return this.transitionToTargetRoute(transition);
}
this.addAnalyticsService(model);
return this.transitionToTargetRoute(transition);
},
async addAnalyticsService(model) {
// identify user for analytics service
if (this.analytics.activated) {
let licenseId = '';
@ -172,8 +188,6 @@ export default Route.extend(ModelBoundaryRoute, ClusterRoute, {
console.log('unable to start analytics', e);
}
}
return this.transitionToTargetRoute(transition);
},
setupController() {

View file

@ -8,7 +8,7 @@
<h3 class="title is-5 is-marginless">
Promote cluster
</h3>
<p class="has-top-padding-s">
<p class="has-top-padding-s" data-test-promote-description>
Promote this cluster to a
{{this.model.replicationModeForDisplay}}
primary

View file

@ -37,12 +37,16 @@
{{else}}
<ul>
<li>
<LinkTo @route="vault.cluster.replication-dr-promote.details">
<LinkTo @route="vault.cluster.replication-dr-promote.details" data-test-link-to="Details">
Details
</LinkTo>
</li>
<li>
<LinkTo @route="vault.cluster.replication-dr-promote" @current-when="vault.cluster.replication-dr-promote.index">
<LinkTo
@route="vault.cluster.replication-dr-promote"
@current-when="vault.cluster.replication-dr-promote.index"
data-test-link-to="Manage"
>
Manage
</LinkTo>
</li>

View file

@ -29,7 +29,12 @@
{{/if}}
</div>
{{#if this.cluster.canAddSecondary}}
<LinkTo @route="mode.secondaries.add" @model={{this.cluster.replicationMode}} class="link add-secondaries">
<LinkTo
@route="mode.secondaries.add"
@model={{this.cluster.replicationMode}}
class="link add-secondaries"
data-test-link-to="Add secondary"
>
Add secondary
</LinkTo>
{{/if}}

View file

@ -47,7 +47,7 @@
{{/if}}
<hr class="has-background-gray-100" />
<Hds::ButtonSet>
<Hds::Button @text="Generate token" type="submit" data-test-secondary-add />
<Hds::Button @text="Generate token" type="submit" data-test-submit />
<Hds::Button @text="Cancel" @color="secondary" @route="mode.secondaries" @model={{this.model.replicationMode}} />
</Hds::ButtonSet>
</form>

View file

@ -0,0 +1,162 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { click, visit, settled } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { login, logout } from 'vault/tests/helpers/auth/auth-helpers';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import sinon from 'sinon';
import { disableReplication, enableReplication } from 'vault/tests/helpers/replication';
import { pollCluster } from 'vault/tests/helpers/poll-cluster';
// To allow a user to login and create a secondary dr cluster we demote a primary dr cluster.
// We stub this demotion so we do not break the dev process for all future tests.
// All DR secondary assertions are done in one test to avoid the lengthy setup and teardown process.
module('Acceptance | Enterprise | replication-secondaries', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
hooks.beforeEach(async function () {
await login();
await settled();
await disableReplication('dr');
await settled();
await disableReplication('performance');
await settled();
});
hooks.afterEach(async function () {
// For the tests following this, return to a good state.
// We've reset mirage with this.server.shutdown() but re-poll the cluster to get the latest state.
this.server.shutdown();
await pollCluster(this.owner);
await disableReplication('dr');
await settled();
await pollCluster(this.owner);
await logout();
});
test('DR secondary: manage tab, details tab, and analytics are not run', async function (assert) {
// Log in and set up a DR primary
await login();
await settled();
await enableReplication('dr', 'primary');
await pollCluster(this.owner);
await click('[data-test-replication-link="manage"]');
// Stub the demote action so it does not actually demote the cluster
this.server.post('/sys/replication/dr/demote', () => {
return { request_id: 'fake-demote', data: { success: true } };
});
// Stub endpoints for DR secondary state
this.server.post('/sys/capabilities-self', () => ({ capabilities: [] }));
this.server.get('/sys/replication/status', () => ({
request_id: '2f50313f-be70-493d-5883-c84c2d6f05ce',
lease_id: '',
renewable: false,
lease_duration: 0,
data: {
dr: {
cluster_id: '7222cbbf-3fb3-949b-8e03-cd5a15babde6',
corrupted_merkle_tree: false,
known_primary_cluster_addrs: null,
last_corruption_check_epoch: '-62135596800',
last_reindex_epoch: '0',
merkle_root: 'd3ae75bde029e05d435f92b9ecc5641c1b027cc4',
mode: 'secondary',
primaries: [],
primary_cluster_addr: '',
secondary_id: '',
ssct_generation_counter: 0,
state: 'idle',
},
performance: {
mode: 'disabled',
},
},
wrap_info: null,
warnings: null,
auth: null,
mount_type: '',
}));
this.server.get('/sys/replication/dr/status', () => ({
data: { mode: 'secondary', cluster_id: 'dr-cluster-id' },
}));
this.server.get('/sys/health', () => ({
initialized: true,
sealed: false,
standby: false,
performance_standby: false,
replication_performance_mode: 'disabled',
replication_dr_mode: 'secondary',
server_time_utc: 1754948244,
version: '1.21.0-beta1+ent',
enterprise: true,
cluster_name: 'vault-cluster-64853bcd',
cluster_id: '113a6c47-077f-bea7-0e8e-70a91821e85a',
last_wal: 82,
license: {
state: 'autoloaded',
expiry_time: '2029-01-27T00:00:00Z',
terminated: false,
},
echo_duration_ms: 0,
clock_skew_ms: 0,
replication_primary_canary_age_ms: 0,
removed_from_cluster: false,
}));
this.server.get('/sys/seal-status', () => ({
type: 'shamir',
initialized: true,
sealed: false,
t: 1,
n: 1,
progress: 0,
nonce: '',
version: '1.21.0-beta1+ent',
build_date: '2025-08-11T14:11:00Z',
migration: false,
cluster_name: 'vault-cluster-64853bcd',
cluster_id: '113a6c47-077f-bea7-0e8e-70a91821e85a',
recovery_seal: false,
storage_type: 'raft',
removed_from_cluster: false,
}));
await click(GENERAL.button('demote'));
await pollCluster(this.owner); // We must poll the cluster to stimulate a cluster reload. This is skipped in ember testing so must be forced.
// Spy on the route's addAnalyticsService method
const clusterRoute = this.owner.lookup('route:vault.cluster');
const addAnalyticsSpy = sinon.spy(clusterRoute, 'addAnalyticsService');
// Visit the DR secondary view. This is the route used only by DR secondaries
await visit('/vault/replication-dr-promote');
assert
.dom('[data-test-promote-description]')
.hasText(
'Promote this cluster to a Disaster Recovery primary',
'shows the correct description for a DR secondary'
);
assert.dom('[data-test-mode]').includesText('secondary', 'shows the DR secondary mode badge');
await click(GENERAL.linkTo('Details'));
assert
.dom('[data-test-replication-secondary-card]')
.hasClass(
'has-error-border',
'shows error border on status because the DR secondary is not connected to a primary.'
);
// Assert addAnalyticsService was NOT called
assert.false(addAnalyticsSpy.called, 'addAnalyticsService should not be called on DR secondary');
// Restore spy
addAnalyticsSpy.restore();
});
});

View file

@ -9,9 +9,9 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
import { click, currentURL, settled, visit, waitFor } from '@ember/test-helpers';
import { login } from 'vault/tests/helpers/auth/auth-helpers';
import { STATUS_DISABLED_RESPONSE, mockReplicationBlock } from 'vault/tests/helpers/replication';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
const s = {
navLink: (title) => `[data-test-sidebar-nav-link="${title}"]`,
title: (t) => `[data-test-replication-title="${t}"]`,
detailLink: (mode) => `[data-test-replication-details-link="${mode}"]`,
summaryCard: '[data-test-replication-summary-card]',
@ -53,8 +53,8 @@ module('Acceptance | Enterprise | replication modes', function (hooks) {
await assertTitle(assert, 'Replication unsupported');
// Nav links
assert.dom(s.navLink('Performance')).doesNotExist('hides performance link');
assert.dom(s.navLink('Disaster Recovery')).doesNotExist('hides dr link');
assert.dom(GENERAL.navLink('Performance')).doesNotExist('hides performance link');
assert.dom(GENERAL.navLink('Disaster Recovery')).doesNotExist('hides dr link');
});
test('replication page when disabled', async function (assert) {
@ -64,15 +64,15 @@ module('Acceptance | Enterprise | replication modes', function (hooks) {
await assertTitle(assert, 'Enable Replication');
// Nav links
assert.dom(s.navLink('Performance')).exists('shows performance link');
assert.dom(s.navLink('Disaster Recovery')).exists('shows dr link');
assert.dom(GENERAL.navLink('Performance')).exists('shows performance link');
assert.dom(GENERAL.navLink('Disaster Recovery')).exists('shows dr link');
await click(s.navLink('Performance'));
await click(GENERAL.navLink('Performance'));
assert.strictEqual(currentURL(), '/vault/replication/performance', 'it navigates to the correct page');
await settled();
assert.dom(s.enableForm).exists();
await click(s.navLink('Disaster Recovery'));
await click(GENERAL.navLink('Disaster Recovery'));
await assertTitle(assert, 'Enable Disaster Recovery Replication', 'dr');
});
@ -90,21 +90,21 @@ module('Acceptance | Enterprise | replication modes', function (hooks) {
assert.dom(s.detailLink('dr')).hasText('Enable', 'CTA to enable dr');
// Nav links
assert.dom(s.navLink('Performance')).exists('shows performance link');
assert.dom(s.navLink('Disaster Recovery')).exists('shows dr link');
assert.dom(GENERAL.navLink('Performance')).exists('shows performance link');
assert.dom(GENERAL.navLink('Disaster Recovery')).exists('shows dr link');
await click(s.navLink('Performance'));
await click(GENERAL.navLink('Performance'));
assert.strictEqual(currentURL(), `/vault/replication/performance`, `goes to correct URL`);
await waitFor(s.dashboard);
assert.dom(s.dashboard).exists(`it shows the replication dashboard`);
await click(s.navLink('Disaster Recovery'));
await click(GENERAL.navLink('Disaster Recovery'));
await assertTitle(assert, 'Enable Disaster Recovery Replication', 'dr');
assert.dom(s.enableForm).exists('it shows the enable view for dr');
});
});
// DR secondary mode is a whole other thing, test primary only here
test(`replication page when dr primary only`, async function (assert) {
test('replication page when dr primary only', async function (assert) {
await this.setupMocks({
dr: mockReplicationBlock('primary'),
performance: mockReplicationBlock(),
@ -115,20 +115,20 @@ module('Acceptance | Enterprise | replication modes', function (hooks) {
assert.dom(s.detailLink('dr')).hasText('Details', 'CTA to see dr details');
// Nav links
assert.dom(s.navLink('Performance')).exists('shows performance link');
assert.dom(s.navLink('Disaster Recovery')).exists('shows dr link');
assert.dom(GENERAL.navLink('Performance')).exists('shows performance link');
assert.dom(GENERAL.navLink('Disaster Recovery')).exists('shows dr link');
await click(s.navLink('Performance'));
await click(GENERAL.navLink('Performance'));
assert.strictEqual(currentURL(), `/vault/replication/performance`, `goes to correct URL`);
await waitFor(s.enableForm);
assert.dom(s.enableForm).exists('it shows the enable view for performance');
await click(s.navLink('Disaster Recovery'));
await click(GENERAL.navLink('Disaster Recovery'));
await assertTitle(assert, 'Disaster Recovery primary', 'Disaster Recovery');
assert.dom(s.dashboard).exists(`it shows the replication dashboard`);
});
test(`replication page both primary`, async function (assert) {
test('replication page both primary', async function (assert) {
await this.setupMocks({
dr: mockReplicationBlock('primary'),
performance: mockReplicationBlock('primary'),
@ -137,11 +137,11 @@ module('Acceptance | Enterprise | replication modes', function (hooks) {
await assertTitle(assert, 'Disaster Recovery & Performance primary', 'Disaster Recovery & Performance');
assert.dom(s.summaryCard).exists({ count: 2 }, 'shows 2 summary cards');
await click(s.navLink('Performance'));
await click(GENERAL.navLink('Performance'));
await assertTitle(assert, 'Performance primary', 'Performance');
assert.dom(s.enableForm).doesNotExist();
await click(s.navLink('Disaster Recovery'));
await click(GENERAL.navLink('Disaster Recovery'));
await assertTitle(assert, 'Disaster Recovery primary', 'Disaster Recovery');
assert.dom(s.enableForm).doesNotExist();
});

View file

@ -10,7 +10,9 @@ import { login } from 'vault/tests/helpers/auth/auth-helpers';
import { pollCluster } from 'vault/tests/helpers/poll-cluster';
import { addSecondary, disableReplication, enableReplication } from 'vault/tests/helpers/replication';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import sinon from 'sinon';
// note: dr secondary tests are done in ./replication-dr-secondaries-test.js
module('Acceptance | Enterprise | replication', function (hooks) {
setupApplicationTest(hooks);
@ -30,7 +32,103 @@ module('Acceptance | Enterprise | replication', function (hooks) {
await settled();
});
test('it shows DR empty state when DR is not configured', async function (assert) {
test('DR primary: enables primary and adds secondary', async function (assert) {
const secondaryName = 'drSecondary';
await enableReplication('dr', 'primary');
await pollCluster(this.owner);
await settled();
assert
.dom('[data-test-replication-title="Disaster Recovery"]')
.includesText('Disaster Recovery', 'it displays the replication type correctly');
assert
.dom('[data-test-replication-mode-display]')
.includesText('primary', 'it displays the cluster mode correctly');
await addSecondary(secondaryName);
// modal for copying the token appears
await click(GENERAL.button('Copy token')); // must copy token before escaping the modal.
await click(GENERAL.cancelButton);
await pollCluster(this.owner);
await settled();
await click('[data-test-replication-link="secondaries"]');
assert
.dom('[data-test-secondary-name]')
.includesText(secondaryName, 'it displays the secondary in the list of known secondaries');
// verify overflow-wrap class is applied to secondary name
assert
.dom('[data-test-secondary-name]')
.hasClass('overflow-wrap', 'it applies the overflow-wrap class to the secondary name');
});
test('DR primary: shows demotion warning when Performance replication is active', async function (assert) {
await visit('vault/replication/performance');
// enable perf replication
await fillIn('[data-test-replication-cluster-mode-select]', 'primary');
await click(GENERAL.submitButton);
await pollCluster(this.owner);
// enable dr replication
await visit('/vault/replication/dr');
await fillIn('[data-test-replication-cluster-mode-select]', 'primary');
await click(GENERAL.submitButton);
await pollCluster(this.owner);
await visit('/vault/replication/dr/manage');
await click(GENERAL.button('demote'));
assert.ok(findAll('[data-test-demote-warning]').length, 'displays the demotion warning');
});
test('DR primary: shows empty state when secondary mode is not enabled and we navigated to the secondary details page', async function (assert) {
// enable dr replication
await visit('/vault/replication/dr');
await fillIn('[data-test-replication-cluster-mode-select]', 'primary');
await click(GENERAL.submitButton);
await settled(); // eslint-disable-line
await pollCluster(this.owner);
await visit('/vault/replication-dr-promote/details');
assert
.dom('[data-test-component="empty-state"]')
.exists('Empty state is shown when no secondary is configured');
assert
.dom('[data-test-empty-state-message]')
.hasText(
'This Disaster Recovery secondary has not been enabled. You can do so from the Disaster Recovery Primary.',
'Renders the correct message for when a primary is enabled but no secondary is configured and we have navigated to the secondary details page.'
);
});
test('DR primary: runs analytics service when enabled', async function (assert) {
// Spy on the route's addAnalyticsService method if needed
const clusterRoute = this.owner.lookup('route:vault.cluster');
const addAnalyticsSpy = sinon.spy(clusterRoute, 'addAnalyticsService');
// Set up DR replication as primary
await enableReplication('dr', 'primary');
await pollCluster(this.owner);
await settled();
// Visit the route that triggers analytics
await visit('/vault/replication/replication/dr'); // or the correct route for your app
// Verify that analytics service was called
assert.true(addAnalyticsSpy.called, 'addAnalyticsService should be called on DR primary');
// Clean up spy
addAnalyticsSpy.restore();
});
test('DR secondary: shows empty state when replication is not enabled', async function (assert) {
await visit('/vault/replication-dr-promote/details');
assert.dom('[data-test-component="empty-state"]').exists();
@ -46,7 +144,7 @@ module('Acceptance | Enterprise | replication', function (hooks) {
);
});
test('Performance replication: add secondary and delete config', async function (assert) {
test('Performance primary: add secondary and delete config', async function (assert) {
const secondaryName = `performanceSecondary`;
const mode = 'deny';
@ -56,11 +154,12 @@ module('Acceptance | Enterprise | replication', function (hooks) {
// confirm that the details dashboard shows
await waitFor('[data-test-replication-dashboard]', 2000);
await addSecondary(secondaryName, mode);
await click(GENERAL.button('Copy token')); // must copy token before escaping the modal.
await click(GENERAL.cancelButton);
await pollCluster(this.owner);
await settled();
await click('[data-test-replication-link="secondaries"]');
await click(GENERAL.button('Copy token'));
await click(GENERAL.cancelButton);
await click(GENERAL.menuTrigger);
await click('[data-test-replication-path-filter-link]');
assert.strictEqual(
@ -87,80 +186,7 @@ module('Acceptance | Enterprise | replication', function (hooks) {
.exists('shows a table row the recently added secondary');
});
test('DR replication: enable and add secondary', async function (assert) {
const secondaryName = 'drSecondary';
await enableReplication('dr', 'primary');
await pollCluster(this.owner);
await settled();
assert
.dom('[data-test-replication-title="Disaster Recovery"]')
.includesText('Disaster Recovery', 'it displays the replication type correctly');
assert
.dom('[data-test-replication-mode-display]')
.includesText('primary', 'it displays the cluster mode correctly');
await addSecondary(secondaryName);
await pollCluster(this.owner);
await settled();
await click('[data-test-replication-link="secondaries"]');
await click(GENERAL.button('Copy token'));
await click(GENERAL.cancelButton);
assert
.dom('[data-test-secondary-name]')
.includesText(secondaryName, 'it displays the secondary in the list of known secondaries');
// verify overflow-wrap class is applied to secondary name
assert
.dom('[data-test-secondary-name]')
.hasClass('overflow-wrap', 'it applies the overflow-wrap class to the secondary name');
});
test('disabling dr primary when perf replication is enabled', async function (assert) {
await visit('vault/replication/performance');
// enable perf replication
await fillIn('[data-test-replication-cluster-mode-select]', 'primary');
await click(GENERAL.submitButton);
await pollCluster(this.owner);
// enable dr replication
await visit('/vault/replication/dr');
await fillIn('[data-test-replication-cluster-mode-select]', 'primary');
await click(GENERAL.submitButton);
await pollCluster(this.owner);
await visit('/vault/replication/dr/manage');
await click(GENERAL.button('demote'));
assert.ok(findAll('[data-test-demote-warning]').length, 'displays the demotion warning');
});
test('navigating to dr secondary details page when dr secondary is not enabled', async function (assert) {
// enable dr replication
await visit('/vault/replication/dr');
await fillIn('[data-test-replication-cluster-mode-select]', 'primary');
await click(GENERAL.submitButton);
await settled(); // eslint-disable-line
await pollCluster(this.owner);
await visit('/vault/replication-dr-promote/details');
assert.dom('[data-test-component="empty-state"]').exists();
assert
.dom('[data-test-empty-state-message]')
.hasText(
'This Disaster Recovery secondary has not been enabled. You can do so from the Disaster Recovery Primary.',
'renders message when replication is enabled'
);
});
test('add secondary and navigate through token generation modal', async function (assert) {
test('Performance primary: manages secondary token generation and TTL configuration', async function (assert) {
const secondaryNameFirst = 'firstSecondary';
const secondaryNameSecond = 'secondSecondary';
@ -188,7 +214,7 @@ module('Acceptance | Enterprise | replication', function (hooks) {
await fillIn('[data-test-input="Secondary ID"]', secondaryNameSecond);
await click(GENERAL.toggleInput('Time to Live (TTL) for generated secondary token'));
await fillIn('[data-test-ttl-value]', 3);
await click('[data-test-secondary-add]');
await click(GENERAL.submitButton);
await pollCluster(this.owner);
await settled();
@ -207,8 +233,36 @@ module('Acceptance | Enterprise | replication', function (hooks) {
.includesText(secondaryNameFirst, 'it displays the secondary in the list of secondaries');
});
test('render performance and dr primary and navigate to details page', async function (assert) {
// enable perf primary replication
test('Performance primary: demotes primary to secondary and displays correct status', async function (assert) {
// enable perf replication
await enableReplication('performance', 'primary');
await pollCluster(this.owner);
await settled();
// demote perf primary to a secondary
await click('[data-test-replication-link="manage"]');
// open demote modal
await click(GENERAL.button('demote'));
// enter confirmation text
await fillIn('[data-test-confirmation-modal-input="Demote to secondary?"]', 'Performance');
// Click confirm button
await click(GENERAL.confirmButton);
await pollCluster(this.owner);
await settled();
await click('[data-test-replication-link="details"]');
await waitFor('[data-test-replication-dashboard]');
assert.dom('[data-test-replication-dashboard]').exists();
assert.dom('[data-test-selectable-card-container="secondary"]').exists();
assert
.dom('[data-test-replication-mode-display]')
.hasText('secondary', 'it displays the cluster mode correctly in header');
});
test('Replication Dashboard: displays summary cards for both Performance and DR primaries', async function (assert) {
await enableReplication('performance', 'primary');
await pollCluster(this.owner);
await settled();
@ -246,33 +300,4 @@ module('Acceptance | Enterprise | replication', function (hooks) {
.exists('shows the correct card on the details dashboard');
assert.strictEqual(currentURL(), '/vault/replication/dr');
});
test('render performance secondary and navigate to the details page', async function (assert) {
// enable perf replication
await enableReplication('performance', 'primary');
await pollCluster(this.owner);
await settled();
// demote perf primary to a secondary
await click('[data-test-replication-link="manage"]');
// open demote modal
await click(GENERAL.button('demote'));
// enter confirmation text
await fillIn('[data-test-confirmation-modal-input="Demote to secondary?"]', 'Performance');
// Click confirm button
await click(GENERAL.confirmButton);
await pollCluster(this.owner);
await settled();
await click('[data-test-replication-link="details"]');
await waitFor('[data-test-replication-dashboard]');
assert.dom('[data-test-replication-dashboard]').exists();
assert.dom('[data-test-selectable-card-container="secondary"]').exists();
assert
.dom('[data-test-replication-mode-display]')
.hasText('secondary', 'it displays the cluster mode correctly in header');
});
});

View file

@ -27,7 +27,7 @@ export const GENERAL = {
confirmButton: '[data-test-confirm-button]', // used most often on modal or confirm popups
confirmTrigger: '[data-test-confirm-action-trigger]',
copyButton: '[data-test-copy-button]',
// there should only be one save button per view (e.g. one per form) so this does not need to be dynamic
// there should only be one submit button per view (e.g. one per form) so this does not need to be dynamic
// this button should be used for any kind of "submit" on a form or "save" action.
submitButton: '[data-test-submit]',
button: (label: string) => (label ? `[data-test-button="${label}"]` : '[data-test-button]'),

View file

@ -38,7 +38,7 @@ export async function addSecondary(secondaryName, mountFilterMode = null) {
await searchSelect.options.objectAt(0).click();
}
await click('[data-test-secondary-add]');
await click(GENERAL.submitButton);
}
export const disableReplication = async (type, assert) => {