Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport of UI: Fix enabling replication capabilities bug into release/1.18.x #28376

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/28371.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
ui: Fix UI improperly checking capabilities for enabling performance and dr replication
```
1 change: 1 addition & 0 deletions ui/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default class App extends Application {
dependencies: {
services: [
'auth',
'capabilities',
'flash-messages',
'namespace',
'replication-mode',
Expand Down
17 changes: 8 additions & 9 deletions ui/lib/replication/addon/components/enable-replication-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,15 @@ import { waitFor } from '@ember/test-waiters';
* but otherwise it handles the rest of the form inputs. On success it will clear the form and call the onSuccess callback.
*
* @example
* ```js
* <EnableReplicationForm @replicationMode="dr" @canEnablePrimary={{true}} @canEnableSecondary={{false}} @performanceReplicationDisabled={{false}} @onSuccess={{this.reloadCluster}} />
* @param {string} replicationMode - should be one of "dr" or "performance"
* @param {boolean} canEnablePrimary - if the capabilities allow the user to enable a primary cluster
* @param {boolean} canEnableSecondary - if the capabilities allow the user to enable a secondary cluster
* @param {boolean} performanceMode - should be "primary", "secondary", or "disabled". If enabled, form will show a warning when attempting to enable DR secondary
* @param {Promise} onSuccess - (optional) callback called after successful replication enablement. Must be a promise.
* @param {boolean} doTransition - (optional) if provided, passed to onSuccess callback to determine if a transition should be done
* />
* ```
*
* @param {string} replicationMode - should be one of "dr" or "performance"
* @param {boolean} canEnablePrimary - if the capabilities allow the user to enable a primary cluster, parent getter returns capabilities based on type (i.e. "dr" or "performance")
* @param {boolean} canEnableSecondary - if the capabilities allow the user to enable a secondary cluster, parent getter returns capabilities based on type (i.e. "dr" or "performance")
* @param {boolean} performanceMode - should be "primary", "secondary", or "disabled". If enabled, form will show a warning when attempting to enable DR secondary
* @param {Promise} onSuccess - (optional) callback called after successful replication enablement. Must be a promise.
* @param {boolean} doTransition - (optional) if provided, passed to onSuccess callback to determine if a transition should be done
*
*/
export default class EnableReplicationFormComponent extends Component {
@service version;
Expand Down
4 changes: 2 additions & 2 deletions ui/lib/replication/addon/components/page/mode-index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
</div>
<EnableReplicationForm
@replicationMode={{@replicationMode}}
@canEnablePrimary={{@cluster.canEnablePrimary}}
@canEnableSecondary={{@cluster.canEnableSecondary}}
@canEnablePrimary={{this.canEnable "Primary"}}
@canEnableSecondary={{this.canEnable "Secondary"}}
@performanceReplicationDisabled={{@cluster.performance.replicationDisabled}}
@performanceMode={{if @cluster.performance.replicationDisabled "disabled" @cluster.performance.modeForUrl}}
@onSuccess={{@onEnableSuccess}}
Expand Down
40 changes: 40 additions & 0 deletions ui/lib/replication/addon/components/page/mode-index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/

import Component from '@glimmer/component';

/**
* @module PageModeIndex
*
* @example
* <Page::ModeIndex
* @cluster={{this.model}}
* @onEnableSuccess={{this.onEnableSuccess}}
* @replicationDisabled={{this.replicationForMode.replicationDisabled}
* @replicationMode={{this.replicationMode}}
* />
*
* @param {model} cluster - cluster route model
* @param {function} onEnableSuccess - callback after enabling is successful, handles transition if enabled from the top-level index route
* @param {boolean} replicationDisabled - whether or not replication is enabled
* @param {string} replicationMode - should be "dr" or "performance"
*/
export default class PageModeIndex extends Component {
canEnable = (type) => {
const { cluster, replicationMode } = this.args;
let perm;
if (replicationMode === 'dr') {
// returns canEnablePrimaryDr or canEnableSecondaryDr
perm = `canEnable${type}Dr`;
}
if (replicationMode === 'performance') {
// returns canEnablePrimaryPerformance or canEnableSecondaryPerformance
perm = `canEnable${type}Performance`;
}
// if there's a problem checking capabilities, default to true
// since the backend can gate as a fallback
return cluster[perm] ?? true;
};
}
20 changes: 20 additions & 0 deletions ui/lib/replication/addon/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,24 @@ import { tracked } from '@glimmer/tracking';

export default class ReplicationIndexController extends ReplicationModeBaseController {
@tracked modeSelection = 'dr';

getPerm(type) {
if (this.modeSelection === 'dr') {
// returns canEnablePrimaryDr or canEnableSecondaryDr
return `canEnable${type}Dr`;
}
if (this.modeSelection === 'performance') {
// returns canEnablePrimaryPerformance or canEnableSecondaryPerformance
return `canEnable${type}Performance`;
}
}

// if there's a problem checking capabilities, default to true
// since the backend will gate as a fallback
get canEnablePrimary() {
return this.model[this.getPerm('Primary')] ?? true;
}
get canEnableSecondary() {
return this.model[this.getPerm('Secondary')] ?? true;
}
}
1 change: 1 addition & 0 deletions ui/lib/replication/addon/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const Eng = Engine.extend({
dependencies: {
services: [
'auth',
'capabilities',
'flash-messages',
'namespace',
'replication-mode',
Expand Down
46 changes: 31 additions & 15 deletions ui/lib/replication/addon/routes/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import { service } from '@ember/service';
import { setProperties } from '@ember/object';
import { hash } from 'rsvp';
import Route from '@ember/routing/route';
import ClusterRoute from 'vault/mixins/cluster-route';

Expand All @@ -14,6 +13,23 @@ export default Route.extend(ClusterRoute, {
store: service(),
auth: service(),
router: service(),
capabilities: service(),

async fetchCapabilities() {
const enablePath = (type, cluster) => `sys/replication/${type}/${cluster}/enable`;
const perms = await this.capabilities.fetchMultiplePaths([
enablePath('dr', 'primary'),
enablePath('dr', 'primary'),
enablePath('performance', 'secondary'),
enablePath('performance', 'secondary'),
]);
return {
canEnablePrimaryDr: perms[enablePath('dr', 'primary')].canUpdate,
canEnableSecondaryDr: perms[enablePath('dr', 'primary')].canUpdate,
canEnablePrimaryPerformance: perms[enablePath('performance', 'secondary')].canUpdate,
canEnableSecondaryPerformance: perms[enablePath('performance', 'secondary')].canUpdate,
};
},

beforeModel() {
if (this.auth.activeCluster.replicationRedacted) {
Expand All @@ -29,21 +45,21 @@ export default Route.extend(ClusterRoute, {
return this.auth.activeCluster;
},

afterModel(model) {
return hash({
canEnablePrimary: this.store
.findRecord('capabilities', 'sys/replication/primary/enable')
.then((c) => c.canUpdate),
canEnableSecondary: this.store
.findRecord('capabilities', 'sys/replication/secondary/enable')
.then((c) => c.canUpdate),
}).then(({ canEnablePrimary, canEnableSecondary }) => {
setProperties(model, {
canEnablePrimary,
canEnableSecondary,
});
return model;
async afterModel(model) {
const {
canEnablePrimaryDr,
canEnableSecondaryDr,
canEnablePrimaryPerformance,
canEnableSecondaryPerformance,
} = await this.fetchCapabilities();

setProperties(model, {
canEnablePrimaryDr,
canEnableSecondaryDr,
canEnablePrimaryPerformance,
canEnableSecondaryPerformance,
});
return model;
},
actions: {
refresh() {
Expand Down
4 changes: 2 additions & 2 deletions ui/lib/replication/addon/templates/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@
</div>
<EnableReplicationForm
@replicationMode={{this.modeSelection}}
@canEnablePrimary={{this.model.canEnablePrimary}}
@canEnableSecondary={{this.model.canEnableSecondary}}
@canEnablePrimary={{this.canEnablePrimary}}
@canEnableSecondary={{this.canEnableSecondary}}
@performanceReplicationDisabled={{this.model.performance.replicationDisabled}}
@performanceMode={{if this.model.performance.replicationDisabled "disabled" this.model.performance.modeForUrl}}
@onSuccess={{this.onEnableSuccess}}
Expand Down
42 changes: 42 additions & 0 deletions ui/tests/integration/components/page/mode-index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ const S = {
title: 'h1',
subtitle: 'h2',
enableForm: '[data-test-replication-enable-form]',
enableBtn: '[data-test-replication-enable]',
summary: '[data-test-replication-summary]',
notAllowed: '[data-test-not-allowed]',
};
module('Integration | Component | replication page/mode-index', function (hooks) {
setupRenderingTest(hooks);
Expand Down Expand Up @@ -43,6 +45,8 @@ module('Integration | Component | replication page/mode-index', function (hooks)

assert.dom(S.title).hasText('Enable Disaster Recovery Replication');
assert.dom(S.enableForm).exists();
assert.dom(S.notAllowed).doesNotExist();
assert.dom(S.enableBtn).exists('Enable button shows by default if no permissions available');
});
test('it renders correctly when replication enabled', async function (assert) {
this.replicationDisabled = false;
Expand All @@ -51,6 +55,24 @@ module('Integration | Component | replication page/mode-index', function (hooks)
assert.dom(S.enableForm).doesNotExist();
assert.dom(S.summary).exists();
});

test('it hides enable button if no permissions', async function (assert) {
this.clusterModel.canEnablePrimaryDr = false;
await this.renderComponent();

assert.dom(S.enableForm).exists();
assert.dom(S.notAllowed).exists();
assert.dom(S.enableBtn).doesNotExist();
});

test('it shows enable button if has permissions', async function (assert) {
this.clusterModel.canEnablePrimaryDr = true;
await this.renderComponent();

assert.dom(S.enableForm).exists();
assert.dom(S.notAllowed).doesNotExist();
assert.dom(S.enableBtn).exists();
});
});

module('Performance mode', function (hooks) {
Expand All @@ -62,6 +84,8 @@ module('Integration | Component | replication page/mode-index', function (hooks)

assert.dom(S.title).hasText('Enable Performance Replication');
assert.dom(S.enableForm).exists();
assert.dom(S.notAllowed).doesNotExist();
assert.dom(S.enableBtn).exists('Enable button shows by default if no permissions available');
});
test('it renders correctly when replication enabled', async function (assert) {
this.replicationDisabled = false;
Expand All @@ -70,5 +94,23 @@ module('Integration | Component | replication page/mode-index', function (hooks)
assert.dom(S.enableForm).doesNotExist();
assert.dom(S.summary).exists();
});

test('it hides enable button if no permissions', async function (assert) {
this.clusterModel.canEnablePrimaryPerformance = false;
await this.renderComponent();

assert.dom(S.enableForm).exists();
assert.dom(S.notAllowed).exists();
assert.dom(S.enableBtn).doesNotExist();
});

test('it shows enable button if has permissions', async function (assert) {
this.clusterModel.canEnablePrimaryPerformance = true;
await this.renderComponent();

assert.dom(S.enableForm).exists();
assert.dom(S.notAllowed).doesNotExist();
assert.dom(S.enableBtn).exists();
});
});
});
Loading