Skip to content

Commit

Permalink
UI: Fix enabling replication capabilities bug (#28371)
Browse files Browse the repository at this point in the history
* add capabilities service to replication engine

* fix capabilities paths in route file

* pass updated capabilities using getters

* add changelog

* fix logic so default is based on undefined capabilities (not no mode)
  • Loading branch information
hellobontempo committed Sep 12, 2024
1 parent 2b4e99f commit 49b46ea
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 28 deletions.
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 @@ -92,8 +92,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();
});
});
});

0 comments on commit 49b46ea

Please sign in to comment.