Skip to content

Commit 629b04c

Browse files
UI: Separate flag and version service concerns (#30001)
* move sync activation and capabilities from flag service to version service and components * cleanup unnecessary args * remove permissions service * un-nest (some) modules * simplify overview test so logic is easier to follow * modal test * update service tests * change back to 403 because maybe it is permissions related? * fix tests running on CE * small cleanup * fix logic based on feedback * add logic for hvd specifically, update cluster tests
1 parent c49978e commit 629b04c

File tree

17 files changed

+593
-681
lines changed

17 files changed

+593
-681
lines changed

ui/app/components/clients/page/counts.hbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
</Hds::Alert>
119119
{{/if}}
120120

121-
<Clients::Counts::NavBar @showSecretsSyncClientCounts={{or this.hasSecretsSyncClients this.flags.showSecretsSync}} />
121+
<Clients::Counts::NavBar @showSecretsSyncClientCounts={{or this.hasSecretsSyncClients this.version.hasSecretsSync}} />
122122

123123
{{! CLIENT COUNT PAGE COMPONENTS RENDER HERE }}
124124
{{yield}}

ui/app/components/sidebar/nav/cluster.hbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
@text="Secrets Engines"
1414
data-test-sidebar-nav-link="Secrets Engines"
1515
/>
16-
{{#if this.flags.showSecretsSync}}
16+
{{#if this.showSecretsSync}}
1717
<Nav.Link
1818
@route="vault.cluster.sync"
1919
@text="Secrets Sync"

ui/app/components/sidebar/nav/cluster.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default class SidebarNavClusterComponent extends Component {
1212
@service version;
1313
@service auth;
1414
@service namespace;
15+
@service permissions;
1516

1617
get cluster() {
1718
return this.currentCluster.cluster;
@@ -25,4 +26,19 @@ export default class SidebarNavClusterComponent extends Component {
2526
// should only return true if we're in the true root namespace
2627
return this.namespace.inRootNamespace && !this.hasChrootNamespace;
2728
}
29+
30+
get showSecretsSync() {
31+
// always show for HVD managed clusters
32+
if (this.flags.isHvdManaged) return true;
33+
34+
if (this.flags.secretsSyncIsActivated) {
35+
// activating the feature requires different permissions than using the feature.
36+
// we want to show the link to allow activation regardless of permissions to sys/sync
37+
// and only check permissions if the feature has been activated
38+
return this.permissions.hasNavPermission('sync');
39+
}
40+
41+
// otherwise we show the link depending on whether or not the feature exists
42+
return this.version.hasSecretsSync;
43+
}
2844
}

ui/app/services/flags.ts

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,9 @@ import Service, { inject as service } from '@ember/service';
77
import { tracked } from '@glimmer/tracking';
88
import { keepLatestTask } from 'ember-concurrency';
99
import { DEBUG } from '@glimmer/env';
10-
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
1110

1211
import type Store from '@ember-data/store';
1312
import type VersionService from 'vault/services/version';
14-
import type PermissionsService from 'vault/services/permissions';
15-
import type CapabilitiesModel from 'vault/models/capabilities';
1613

1714
const FLAGS = {
1815
vaultCloudNamespace: 'VAULT_CLOUD_ADMIN_NAMESPACE',
@@ -24,10 +21,9 @@ const FLAGS = {
2421
* The activation-flags endpoint returns which features are enabled.
2522
*/
2623

27-
export default class flagsService extends Service {
24+
export default class FlagsService extends Service {
2825
@service declare readonly version: VersionService;
2926
@service declare readonly store: Store;
30-
@service declare readonly permissions: PermissionsService;
3127

3228
@tracked activatedFlags: string[] = [];
3329
@tracked featureFlags: string[] = [];
@@ -36,6 +32,7 @@ export default class flagsService extends Service {
3632
return this.featureFlags?.includes(FLAGS.vaultCloudNamespace);
3733
}
3834

35+
// for non-managed clusters the root namespace path is technically an empty string so we return null
3936
get hvdManagedNamespaceRoot(): string | null {
4037
return this.isHvdManaged ? 'admin' : null;
4138
}
@@ -81,31 +78,4 @@ export default class flagsService extends Service {
8178
fetchActivatedFlags() {
8279
return this.getActivatedFlags.perform();
8380
}
84-
85-
@lazyCapabilities(apiPath`sys/activation-flags/secrets-sync/activate`)
86-
declare secretsSyncActivatePath: CapabilitiesModel;
87-
88-
get canActivateSecretsSync() {
89-
return (
90-
this.secretsSyncActivatePath.get('canCreate') !== false ||
91-
this.secretsSyncActivatePath.get('canUpdate') !== false
92-
);
93-
}
94-
95-
get showSecretsSync() {
96-
const isHvdManaged = this.isHvdManaged;
97-
const onLicense = this.version.hasSecretsSync;
98-
const isEnterprise = this.version.isEnterprise;
99-
const isActivated = this.secretsSyncIsActivated;
100-
101-
if (!isEnterprise) return false;
102-
if (isHvdManaged) return true;
103-
if (isEnterprise && !onLicense) return false;
104-
if (isActivated) {
105-
// if the feature is activated but the user does not have permissions on the `sys/sync` endpoint, hide navigation link.
106-
return this.permissions.hasNavPermission('sync');
107-
}
108-
// only remaining option is Enterprise with Secrets Sync on the license but the feature is not activated. In this case, we want to show the upsell page and message about either activating or having an admin activate.
109-
return true;
110-
}
11181
}

ui/app/services/version.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,14 @@ export default class VersionService extends Service {
4848
}
4949

5050
get hasSecretsSync() {
51-
return this.features.includes('Secrets Sync');
51+
const isEnterprise = this.isEnterprise;
52+
const isHvdManaged = this.flags.isHvdManaged;
53+
const onLicense = this.features.includes('Secrets Sync');
54+
55+
if (!isEnterprise) return false;
56+
if (isHvdManaged) return true;
57+
if (isEnterprise && onLicense) return true;
58+
return false;
5259
}
5360

5461
get versionDisplay() {

ui/lib/sync/addon/components/secrets/page/overview.hbs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,26 @@
22
Copyright (c) HashiCorp, Inc.
33
SPDX-License-Identifier: BUSL-1.1
44
}}
5-
{{#unless @isActivated}}
6-
{{#if (or @licenseHasSecretsSync @isHvdManaged)}}
5+
{{#unless this.flags.secretsSyncIsActivated}}
6+
{{#if this.version.hasSecretsSync}}
77
{{#unless this.hideOptIn}}
88
{{! Allows users to dismiss activation banner if they have permissions to activate. }}
99
<Hds::Alert
1010
@type="inline"
1111
@color="warning"
12-
@onDismiss={{if this.flags.canActivateSecretsSync (fn (mut this.hideOptIn) true) undefined}}
12+
@onDismiss={{if @canActivateSecretsSync (fn (mut this.hideOptIn) true) undefined}}
1313
data-test-secrets-sync-opt-in-banner
1414
as |A|
1515
>
1616
<A.Title>Enable Secrets Sync feature</A.Title>
1717
<A.Description data-test-secrets-sync-opt-in-banner-description>To use this feature, specific activation is required.
1818
{{if
19-
this.flags.canActivateSecretsSync
19+
@canActivateSecretsSync
2020
"Please review the feature documentation and
2121
enable it. If you're upgrading from beta, your previous data will be accessible after activation."
2222
"Please contact your administrator to activate."
2323
}}</A.Description>
24-
{{#if this.flags.canActivateSecretsSync}}
24+
{{#if @canActivateSecretsSync}}
2525
<A.Button
2626
@text="Enable"
2727
@color="secondary"
@@ -211,14 +211,13 @@
211211
</OverviewCard>
212212
</div>
213213
{{else}}
214-
<Secrets::LandingCta @isActivated={{@isActivated}} />
214+
<Secrets::LandingCta @isActivated={{this.flags.secretsSyncIsActivated}} />
215215
{{/if}}
216216

217217
{{#if this.showActivateSecretsSyncModal}}
218218
<Secrets::SyncActivationModal
219219
@onClose={{fn (mut this.showActivateSecretsSyncModal) false}}
220220
@onError={{this.onModalError}}
221221
@onConfirm={{this.clearActivationErrors}}
222-
@isHvdManaged={{@isHvdManaged}}
223222
/>
224223
{{/if}}

ui/lib/sync/addon/components/secrets/page/overview.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ import type SyncDestinationModel from 'vault/vault/models/sync/destination';
2121
interface Args {
2222
destinations: Array<SyncDestinationModel>;
2323
totalVaultSecrets: number;
24-
isActivated: boolean;
25-
licenseHasSecretsSync: boolean;
26-
isHvdManaged: boolean;
2724
}
2825

2926
export default class SyncSecretsDestinationsPageComponent extends Component<Args> {
@@ -73,7 +70,7 @@ export default class SyncSecretsDestinationsPageComponent extends Component<Args
7370

7471
const errors = [errorMsg];
7572

76-
if (this.args.isHvdManaged) {
73+
if (this.flags.isHvdManaged) {
7774
errors.push(
7875
'Secrets Sync is available for Plus tier clusters only. Please check the tier of your cluster to enable Secrets Sync.'
7976
);

ui/lib/sync/addon/components/secrets/sync-activation-modal.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,22 @@ import { task } from 'ember-concurrency';
1010
import { waitFor } from '@ember/test-waiters';
1111
import errorMessage from 'vault/utils/error-message';
1212

13+
import type FlagsService from 'vault/services/flags';
1314
import type FlashMessageService from 'vault/services/flash-messages';
14-
import type Store from '@ember-data/store';
1515
import type RouterService from '@ember/routing/router-service';
16+
import type Store from '@ember-data/store';
1617

1718
interface Args {
1819
onClose: () => void;
1920
onError: (errorMessage: string) => void;
2021
onConfirm: () => void;
21-
isHvdManaged: boolean;
2222
}
2323

2424
export default class SyncActivationModal extends Component<Args> {
25+
@service declare readonly flags: FlagsService;
2526
@service declare readonly flashMessages: FlashMessageService;
26-
@service declare readonly store: Store;
2727
@service('app-router') declare readonly router: RouterService;
28+
@service declare readonly store: Store;
2829

2930
@tracked hasConfirmedDocs = false;
3031

@@ -34,9 +35,10 @@ export default class SyncActivationModal extends Component<Args> {
3435
// clear any previous errors in the parent component
3536
this.args.onConfirm();
3637

37-
// must return null instead of root for non managed cluster.
38-
// child namespaces are not sent.
39-
const namespace = this.args.isHvdManaged ? 'admin' : null;
38+
// sync activation is managed by the root/administrative namespace so child namespaces are not sent.
39+
// for non-managed clusters the root namespace path is technically an empty string so we pass null
40+
// otherwise we pass 'admin' if HVD managed.
41+
const namespace = this.flags.hvdManagedNamespaceRoot;
4042

4143
try {
4244
yield this.store

ui/lib/sync/addon/routes/secrets/overview.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,29 @@
66
import Route from '@ember/routing/route';
77
import { service } from '@ember/service';
88
import { hash } from 'rsvp';
9+
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
910

1011
import type FlagsService from 'vault/services/flags';
1112
import type RouterService from '@ember/routing/router-service';
1213
import type Store from '@ember-data/store';
1314
import type VersionService from 'vault/services/version';
15+
import type CapabilitiesModel from 'vault/models/capabilities';
1416

1517
export default class SyncSecretsOverviewRoute extends Route {
1618
@service('app-router') declare readonly router: RouterService;
1719
@service declare readonly store: Store;
1820
@service declare readonly flags: FlagsService;
1921
@service declare readonly version: VersionService;
2022

23+
@lazyCapabilities(apiPath`sys/activation-flags/secrets-sync/activate`)
24+
declare syncPath: CapabilitiesModel;
25+
2126
async model() {
2227
const isActivated = this.flags.secretsSyncIsActivated;
23-
const licenseHasSecretsSync = this.version.hasSecretsSync;
24-
const isHvdManaged = this.flags.isHvdManaged;
2528

2629
return hash({
27-
licenseHasSecretsSync,
28-
isActivated,
29-
isHvdManaged,
30+
canActivateSecretsSync:
31+
this.syncPath.get('canCreate') !== false || this.syncPath.get('canUpdate') !== false,
3032
destinations: isActivated ? this.store.query('sync/destination', {}).catch(() => []) : [],
3133
associations: isActivated
3234
? this.store
@@ -38,7 +40,7 @@ export default class SyncSecretsOverviewRoute extends Route {
3840
}
3941

4042
redirect() {
41-
if (!this.flags.showSecretsSync) {
43+
if (!this.version.hasSecretsSync) {
4244
this.router.replaceWith('vault.cluster.dashboard');
4345
}
4446
}

ui/lib/sync/addon/templates/secrets/overview.hbs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,5 @@
66
<Secrets::Page::Overview
77
@destinations={{this.model.destinations}}
88
@totalVaultSecrets={{this.model.associations.total_secrets}}
9-
@isActivated={{this.model.isActivated}}
10-
@licenseHasSecretsSync={{this.model.licenseHasSecretsSync}}
11-
@isHvdManaged={{this.model.isHvdManaged}}
9+
@canActivateSecretsSync={{this.model.canActivateSecretsSync}}
1210
/>

0 commit comments

Comments
 (0)