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

UI Hide Secrets Sync from nav if not on license and/or no policy permissions #27262

Merged
merged 24 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
64c2fd0
intial changes, haven't tested client counts or done test coverage
Monkeychip May 29, 2024
931a926
client count rename getter to clairfy
Monkeychip May 29, 2024
ac6b262
fix has-permission api-paths
Monkeychip May 29, 2024
b1ad18f
wip
Monkeychip May 29, 2024
09162c4
wip
Monkeychip May 29, 2024
e67076e
fix: explicitly refresh vault.cluster model to re-fetch activatedFeat…
May 29, 2024
e69075b
tests: fix # of assertions for verifying that activation was called
May 29, 2024
27df01a
tests: tidy overview-test
May 29, 2024
7fc2fd7
add additional api permission path and move fetch back to application
Monkeychip May 30, 2024
36fbb78
add test coverage for the service
Monkeychip May 30, 2024
1f2a88b
Merge branch 'main' into ui/VAULT-27608/hide-secrets-sync-when-no-access
Monkeychip May 30, 2024
462d258
cleanup
Monkeychip May 30, 2024
9f1ab09
remove test that checked for upsell without license or on community
Monkeychip May 30, 2024
e4e1080
small comment change
Monkeychip May 30, 2024
3ec1646
welp missed component getter
Monkeychip May 30, 2024
628ea6b
flaky test fix
Monkeychip May 30, 2024
dccc476
flaky test
Monkeychip May 30, 2024
22ce68a
small nit changes from pr reviews
Monkeychip May 31, 2024
6fd9b21
add defaults to sync mirage handler
Monkeychip May 31, 2024
51bf477
Gate sync overview route for users without access (#27320)
Jun 3, 2024
e6b9811
Merge branch 'main' into ui/VAULT-27608/hide-secrets-sync-when-no-access
Monkeychip Jun 4, 2024
8f0fed0
add type enterprise required now because we do a check for this first
Monkeychip Jun 4, 2024
0e598d8
Merge branch 'main' into ui/VAULT-27608/hide-secrets-sync-when-no-access
Monkeychip Jun 10, 2024
e8bf2c4
fix oss test
Monkeychip Jun 10, 2024
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/27262.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
ui/secrets-sync: Hide Secrets Sync from the sidebar nav if user does not have access to the feature.
```
2 changes: 1 addition & 1 deletion ui/app/components/clients/page/counts.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
</Hds::Alert>
{{/if}}

<Clients::Counts::NavBar @showSecretsSync={{this.showSecretsSync}} />
<Clients::Counts::NavBar @showSecretsSyncClientCounts={{this.showSecretsSyncClientCounts}} />

{{! CLIENT COUNT PAGE COMPONENTS RENDER HERE }}
{{yield}}
Expand Down
2 changes: 1 addition & 1 deletion ui/app/components/clients/page/counts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export default class ClientsCountsPageComponent extends Component<Args> {
return activity?.total;
}

get showSecretsSync(): boolean {
get showSecretsSyncClientCounts(): boolean {
const { activity } = this.args;
// if there is any sync client data, show it
if (activity && activity?.total?.secret_syncs > 0) return true;
Expand Down
14 changes: 8 additions & 6 deletions ui/app/components/sidebar/nav/cluster.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
@text="Secrets Engines"
data-test-sidebar-nav-link="Secrets Engines"
/>
<Nav.Link
@route="vault.cluster.sync"
@text="Secrets Sync"
@badge={{this.badgeText}}
data-test-sidebar-nav-link="Secrets Sync"
/>
{{#if this.flags.showSecretsSync}}
<Nav.Link
@route="vault.cluster.sync"
@text="Secrets Sync"
@badge={{if this.flags.isHvdManaged "Plus" ""}}
data-test-sidebar-nav-link="Secrets Sync"
/>
{{/if}}
{{#if (has-permission "access")}}
<Nav.Link
@route={{get (route-params-for "access") "route"}}
Expand Down
12 changes: 0 additions & 12 deletions ui/app/components/sidebar/nav/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,4 @@ export default class SidebarNavClusterComponent extends Component {
// should only return true if we're in the true root namespace
return this.namespace.inRootNamespace && !this.cluster?.hasChrootNamespace;
}

get badgeText() {
const isHvdManaged = this.flags.isHvdManaged;
const onLicense = this.version.hasSecretsSync;
const isEnterprise = this.version.isEnterprise;

if (isHvdManaged) return 'Plus';
if (isEnterprise && !onLicense) return 'Premium';
if (!isEnterprise) return 'Enterprise';
// no badge for Enterprise clusters with Secrets Sync on their license--the only remaining option.
return '';
}
}
7 changes: 5 additions & 2 deletions ui/app/routes/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ export default Route.extend({
},
},

beforeModel() {
return this.flagsService.fetchFeatureFlags();
async beforeModel() {
// activatedFlags are called this high up in routing to return a response used to show/hide Secrets sync on sidebar nav.
// featureFlags are called this high in routing because to determine isHvdManaged things, etc.
await this.flagsService.fetchActivatedFlags();
await this.flagsService.fetchFeatureFlags();
},
});
20 changes: 19 additions & 1 deletion ui/app/services/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { DEBUG } from '@glimmer/env';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
import type StoreService from 'vault/services/store';
import type VersionService from 'vault/services/version';
import type PermissionsService from 'vault/services/permissions';

const FLAGS = {
vaultCloudNamespace: 'VAULT_CLOUD_ADMIN_NAMESPACE',
Expand All @@ -24,6 +25,7 @@ const FLAGS = {
export default class flagsService extends Service {
@service declare readonly version: VersionService;
@service declare readonly store: StoreService;
@service declare readonly permissions: PermissionsService;

@tracked activatedFlags: string[] = [];
@tracked featureFlags: string[] = [];
Expand Down Expand Up @@ -60,7 +62,6 @@ export default class flagsService extends Service {
}

getActivatedFlags = keepLatestTask(async () => {
if (this.version.isCommunity) return;
// Response could change between user sessions.
// Fire off endpoint without checking if activated features are already set.
try {
Expand All @@ -86,4 +87,21 @@ export default class flagsService extends Service {
this.secretsSyncActivatePath.get('canUpdate') !== false
);
}

get showSecretsSync() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain your reasoning for putting this getter here? Since it's compiling logic from flags, permissions and versions services for just secret sync, it feels a little arbitrary living in the flag service, which I understood to be just for returning info about cluster flags.

Though looking at the other getter here canActivateSecretsSync it seems like this flags service is also sort of a secrets sync permissions service 😅

I realize we're limited on time for this PR, but want to suggest that a future improvement is maybe separating or clarifying concerns here. Perhaps that means having another service (auth service?) responsible for managing user state related logic

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noelle and I chatted about this. Because we're using it in more than one places, and the concept is to keep access to this route consistent in both clients and sidebar nav we opted to leave it in the service.

But yes agree, flags (similar to what has happened to version service) is growing past it's original scope.

const isHvdManaged = this.isHvdManaged;
const onLicense = this.version.hasSecretsSync;
const isEnterprise = this.version.isEnterprise;
const isActivated = this.secretsSyncIsActivated;

if (isHvdManaged) return true;
if (isEnterprise && !onLicense) return false;
if (!isEnterprise) return false;
if (isActivated) {
// if activated but the user does not have permissions to do anything on the `sys/sync` endpoint, hide navigation link.
return this.permissions.hasNavPermission('sync');
}
// The 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.
return true;
}
}
3 changes: 3 additions & 0 deletions ui/app/services/permissions.js
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The list here came from me scanning through the sync API. We should test lots of policies to make sure I've gotten all the options.

Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ const API_PATHS = {
settings: {
customMessages: 'sys/config/ui/custom-messages',
},
sync: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still learning out this service works...but do you know why just having

sync: 'sys/sync/',

which ends in a / isn't sufficient here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope! 🙃 This is what I pinged Chelsea about in the dev channel. The logic of comparison is backwards from what you'd think they be comparing. I'll ping you on that thread which has links, etc. But it's also something that I can more easily clarify in standup.

sync: 'sys/sync',
},
};

const API_PATHS_TO_ROUTE_PARAMS = {
Expand Down
4 changes: 2 additions & 2 deletions ui/lib/sync/addon/components/sync-header.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
<Icon @name={{@icon}} @size="24" />
{{/if}}
{{@title}}
{{#if this.badgeText}}
<Hds::Badge @text={{this.badgeText}} @color="highlight" @size="large" />
{{#if this.flags.isHvdManaged}}
<Hds::Badge @text={{"Plus"}} @color="highlight" @size="large" />
{{/if}}
</h1>
</p.levelLeft>
Expand Down
14 changes: 0 additions & 14 deletions ui/lib/sync/addon/components/sync-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import Component from '@glimmer/component';
import { service } from '@ember/service';

import type VersionService from 'vault/services/version';
import type FlagsService from 'vault/services/flags';
import type { Breadcrumb } from 'vault/vault/app-types';

Expand All @@ -17,18 +16,5 @@ interface Args {
}

export default class SyncHeaderComponent extends Component<Args> {
@service declare readonly version: VersionService;
@service declare readonly flags: FlagsService;

get badgeText() {
const isHvdManaged = this.flags.isHvdManaged;
const onLicense = this.version.hasSecretsSync;
const isEnterprise = this.version.isEnterprise;

if (isHvdManaged) return 'Plus feature';
if (isEnterprise && !onLicense) return 'Premium feature';
if (!isEnterprise) return 'Enterprise feature';
// no badge for Enterprise clusters with Secrets Sync on their license--the only remaining option.
return '';
}
}
5 changes: 0 additions & 5 deletions ui/lib/sync/addon/routes/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,8 @@ export default class SyncSecretsRoute extends Route {
@service declare readonly router: RouterService;
@service declare readonly flags: FlagService;

beforeModel() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented in slack about refresh ramifications removing this fetch to only happen in the cluster.js route

return this.flags.fetchActivatedFlags();
}

model() {
return {
// TODO will modify when we use the persona service.
activatedFeatures: this.flags.activatedFlags,
};
}
Expand Down
14 changes: 8 additions & 6 deletions ui/tests/integration/components/clients/counts/nav-bar-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ module('Integration | Component | clients/counts/nav-bar', function (hooks) {
setupRenderingTest(hooks);

hooks.beforeEach(function () {
this.showSecretsSync = false;
this.showSecretsSyncClientCounts = false;

this.renderComponent = async () => {
await render(hbs`<Clients::Counts::NavBar @showSecretsSync={{this.showSecretsSync}} />`);
await render(
hbs`<Clients::Counts::NavBar @showSecretsSyncClientCounts={{this.showSecretsSyncClientCounts}} />`
);
};
});

Expand All @@ -28,15 +30,15 @@ module('Integration | Component | clients/counts/nav-bar', function (hooks) {
assert.dom(GENERAL.tab('acme')).hasText('ACME clients');
});

test('it shows secrets sync tab if showSecretsSync is true', async function (assert) {
this.showSecretsSync = true;
test('it shows secrets sync tab if showSecretsSyncClientCounts is true', async function (assert) {
this.showSecretsSyncClientCounts = true;
await this.renderComponent();

assert.dom(GENERAL.tab('sync')).exists();
});

test('it should not show secrets sync tab if showSecretsSync is false', async function (assert) {
this.showSecretsSync = false;
test('it should not show secrets sync tab if showSecretsSyncClientCounts is false', async function (assert) {
this.showSecretsSyncClientCounts = false;
await this.renderComponent();

assert.dom(GENERAL.tab('sync')).doesNotExist();
Expand Down
1 change: 1 addition & 0 deletions ui/types/vault/services/permissions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export default class PermissionsService extends Service {
canViewAll: boolean | null;
permissionsBanner: string | null;
chrootNamespace: string | null | undefined;
hasNavPermission: (string) => boolean;
}
Loading