From a92bffe5cefec3f2c909483ff42c8281ba56bb34 Mon Sep 17 00:00:00 2001 From: Vault Automation Date: Mon, 17 Nov 2025 10:11:14 -0500 Subject: [PATCH] UI: Hide Client Count Dashboard for PKI Only Clusters (#10513) (#10831) * hide client counting dashboard for PKI only clusters * add test and hide client count card * convert to ts, add pki only license banner, add comments, and support for multiple dismissed types * add banner description * add changelog entry * update tests + add test coverage for multiple banners + info banner * update tests * add doc link * Apply suggestions from code review * naming updates * update feature key format * update casing * add enum, revert naming, move helper to util * test hidden nav link and dashboard card --------- Co-authored-by: lane-wetmore Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> --- changelog/_10513.txt | 2 + ui/app/components/dashboard/overview.ts | 5 +- ui/app/components/license-banners.hbs | 48 +++++-- ui/app/components/license-banners.js | 77 ------------ ui/app/components/license-banners.ts | 118 ++++++++++++++++++ ui/app/components/license-info.js | 2 +- ui/app/components/sidebar/nav/cluster.hbs | 7 +- ui/app/services/version.js | 4 + .../core/addon/utils/all-features.ts} | 5 +- .../enterprise-license-banner-test.js | 9 +- ui/tests/helpers/components/sidebar-nav.js | 2 +- ui/tests/helpers/general-selectors.ts | 6 +- .../components/dashboard/overview-test.js | 13 +- .../components/license-banners-test.js | 104 +++++++++++---- .../components/license-info-test.js | 2 +- .../components/sidebar/nav/cluster-test.js | 47 ++++--- ui/tests/unit/services/version-test.js | 7 ++ 17 files changed, 302 insertions(+), 156 deletions(-) create mode 100644 changelog/_10513.txt delete mode 100644 ui/app/components/license-banners.js create mode 100644 ui/app/components/license-banners.ts rename ui/{app/helpers/all-features.js => lib/core/addon/utils/all-features.ts} (79%) diff --git a/changelog/_10513.txt b/changelog/_10513.txt new file mode 100644 index 0000000000..0afb38dba8 --- /dev/null +++ b/changelog/_10513.txt @@ -0,0 +1,2 @@ +```release-note:improvement +ui: Adds license banner indicating when cluster is operating in PKI-only mode and hides client counting dashboards for PKI-only clusters. diff --git a/ui/app/components/dashboard/overview.ts b/ui/app/components/dashboard/overview.ts index 28eafed29a..f6bc041b4c 100644 --- a/ui/app/components/dashboard/overview.ts +++ b/ui/app/components/dashboard/overview.ts @@ -13,7 +13,7 @@ export type Args = { replication: unknown; secretsEngines: unknown; vaultConfiguration: unknown; - version: { isEnterprise: boolean }; + version: { isEnterprise: boolean; hasPKIOnly: boolean }; }; export default class OverviewComponent extends Component { @@ -33,6 +33,9 @@ export default class OverviewComponent extends Component { // don't show client count if this isn't an enterprise cluster if (!version.isEnterprise) return false; + // don't show client count if this is a PKI-only Secrets cluster + if (version.hasPKIOnly) return false; + // HVD clusters if (namespace.inHvdAdminNamespace) return true; diff --git a/ui/app/components/license-banners.hbs b/ui/app/components/license-banners.hbs index c309a1b5cc..264ef27210 100644 --- a/ui/app/components/license-banners.hbs +++ b/ui/app/components/license-banners.hbs @@ -7,8 +7,8 @@ License expired @@ -17,18 +17,20 @@ {{date-format @expiry "MMM d, yyyy"}}. Add a new license to your configuration and restart Vault. - - Read documentation - - + {{else if (and (lte this.licenseExpiringInDays 30) (not this.warningDismissed))}} Vault license expiring @@ -46,10 +48,32 @@ }} - - Read documentation - - + + + +{{/if}} + +{{#if (and this.isPKIOnly (not this.infoDismissed))}} + + Cluster operating in PKI-only mode + + This cluster is operating under PKI-only mode. Other than the built-in Vault PKI engine, all secrets engines are + disabled. The + + number of certificates + + issued is the relevant license utilization metric. {{/if}} \ No newline at end of file diff --git a/ui/app/components/license-banners.js b/ui/app/components/license-banners.js deleted file mode 100644 index 8fc2756a9b..0000000000 --- a/ui/app/components/license-banners.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright IBM Corp. 2016, 2025 - * SPDX-License-Identifier: BUSL-1.1 - */ - -import Component from '@glimmer/component'; -import { action } from '@ember/object'; -import { tracked } from '@glimmer/tracking'; -import { service } from '@ember/service'; -import isAfter from 'date-fns/isAfter'; -import differenceInDays from 'date-fns/differenceInDays'; -import localStorage from 'vault/lib/local-storage'; -import timestamp from 'core/utils/timestamp'; - -/** - * @module LicenseBanners - * LicenseBanners components are used to display Vault-specific license expiry messages - * - * @example - * ```js - * - * ``` - * @param {string} expiry - RFC3339 date timestamp - */ - -export default class LicenseBanners extends Component { - @service version; - - @tracked warningDismissed; - @tracked expiredDismissed; - - constructor() { - super(...arguments); - // reset and show a previously dismissed license banner if: - // the version has been updated or the license has been updated (indicated by a change in the expiry date). - const bannerType = localStorage.getItem(this.dismissedBannerKey); // returns either warning or expired - - this.updateDismissType(bannerType); - } - - get currentVersion() { - return this.version.version; - } - - get dismissedBannerKey() { - return `dismiss-license-banner-${this.currentVersion}-${this.args.expiry}`; - } - - get licenseExpired() { - if (!this.args.expiry) return false; - return isAfter(timestamp.now(), new Date(this.args.expiry)); - } - - get licenseExpiringInDays() { - // Anything more than 30 does not render a warning - if (!this.args.expiry) return 99; - return differenceInDays(new Date(this.args.expiry), timestamp.now()); - } - - @action - dismissBanner(dismissAction) { - // if a client's version changed their old localStorage key will still exists. - localStorage.cleanupStorage('dismiss-license-banner', this.dismissedBannerKey); - // updates localStorage and then updates the template by calling updateDismissType - localStorage.setItem(this.dismissedBannerKey, dismissAction); - this.updateDismissType(dismissAction); - } - - updateDismissType(dismissType) { - // updates tracked properties to update template - if (dismissType === 'warning') { - this.warningDismissed = true; - } else if (dismissType === 'expired') { - this.expiredDismissed = true; - } - } -} diff --git a/ui/app/components/license-banners.ts b/ui/app/components/license-banners.ts new file mode 100644 index 0000000000..74c375d924 --- /dev/null +++ b/ui/app/components/license-banners.ts @@ -0,0 +1,118 @@ +/** + * Copyright IBM Corp. 2016, 2025 + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Component from '@glimmer/component'; +import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; +import { service } from '@ember/service'; +import isAfter from 'date-fns/isAfter'; +import differenceInDays from 'date-fns/differenceInDays'; +import localStorage from 'vault/lib/local-storage'; +import timestamp from 'core/utils/timestamp'; +import type VersionService from 'vault/services/version'; + +enum Banners { + EXPIRED = 'expired', + WARNING = 'warning', + PKI = 'pki-only-info', +} + +interface Args { + expiry: string; + autoloaded: boolean; +} + +/** + * @module LicenseBanners + * LicenseBanners components are used to display Vault-specific license messages + * + * @example + * ```js + * + * ``` + * @param {string} expiry - RFC3339 date timestamp + */ + +export default class LicenseBanners extends Component { + @service declare readonly version: VersionService; + + @tracked warningDismissed = false; + @tracked expiredDismissed = false; + @tracked infoDismissed = false; + + banners = Banners; + + constructor(owner: unknown, args: Args) { + super(owner, args); + + // reset and show a previously dismissed license banner if: + // the version has been updated or the license has been updated (indicated by a change in the expiry date). + const item = localStorage.getItem(this.dismissedBannerKey) ?? []; // returns warning, expired and/or pki-only-info + // older entries will not be an array as it was either "expired" OR "warning" + // with the addition of "pki-only-info", it can hold all values + // this check maintains backwards compatibility with the previous format + const bannerTypes = Array.isArray(item) ? item : [item]; + + bannerTypes.forEach((type) => { + this.updateDismissType(type); + }); + } + + get currentVersion() { + return this.version.version; + } + + get dismissedBannerKey() { + return `dismiss-license-banner-${this.currentVersion}-${this.args.expiry}`; + } + + get licenseExpired() { + if (!this.args.expiry) return false; + return isAfter(timestamp.now(), new Date(this.args.expiry)); + } + + get licenseExpiringInDays() { + // Anything more than 30 does not render a warning + if (!this.args.expiry) return 99; + return differenceInDays(new Date(this.args.expiry), timestamp.now()); + } + + get isPKIOnly() { + return this.version.hasPKIOnly; + } + + @action + dismissBanner(dismissAction: Banners) { + // if a client's version changed their old localStorage key will still exists. + localStorage.cleanupStorage('dismiss-license-banner', this.dismissedBannerKey); + + // updates localStorage and then updates the template by calling updateDismissType + const item = localStorage.getItem(this.dismissedBannerKey) ?? []; + // older entries will not be an array as it was either "expired" OR "warning" + // with the addition of "pki-only-info", it can hold all values + // this check maintains backwards compatibility with the previous format + const bannerTypes = Array.isArray(item) ? item : [item]; + localStorage.setItem(this.dismissedBannerKey, [...bannerTypes, dismissAction]); + + this.updateDismissType(dismissAction); + } + + updateDismissType(dismissType?: Banners) { + // updates tracked properties to update template + switch (dismissType) { + case this.banners.WARNING: + this.warningDismissed = true; + break; + case this.banners.EXPIRED: + this.expiredDismissed = true; + break; + case this.banners.PKI: + this.infoDismissed = true; + break; + default: + break; + } + } +} diff --git a/ui/app/components/license-info.js b/ui/app/components/license-info.js index c62d186944..d41b7b36d0 100644 --- a/ui/app/components/license-info.js +++ b/ui/app/components/license-info.js @@ -4,7 +4,7 @@ */ import Component from '@glimmer/component'; -import { allFeatures } from 'vault/helpers/all-features'; +import { allFeatures } from 'core/utils/all-features'; /** * @module LicenseInfo * diff --git a/ui/app/components/sidebar/nav/cluster.hbs b/ui/app/components/sidebar/nav/cluster.hbs index a1c2504102..93a48b9b89 100644 --- a/ui/app/components/sidebar/nav/cluster.hbs +++ b/ui/app/components/sidebar/nav/cluster.hbs @@ -85,7 +85,12 @@ /> {{/if}} {{#if - (and (has-permission "clients" routeParams="activity") (not this.cluster.dr.isSecondary) (not this.hasChrootNamespace)) + (and + (has-permission "clients" routeParams="activity") + (not this.cluster.dr.isSecondary) + (not this.hasChrootNamespace) + (not this.version.hasPKIOnly) + ) }} { let expiry; @@ -58,15 +59,15 @@ module('Acceptance | Enterprise | License banner warnings', function (hooks) { const healthResp = generateHealthResponse(this.now); this.server.get('/sys/health', () => healthResp); await visit('/vault/auth'); - assert.dom('[data-test-license-banner-expired]').doesNotExist('expired banner does not show'); - assert.dom('[data-test-license-banner-warning]').doesNotExist('warning banner does not show'); + assert.dom(GENERAL.licenseBanner('expired')).doesNotExist('expired banner does not show'); + assert.dom(GENERAL.licenseBanner('warning')).doesNotExist('warning banner does not show'); this.server.shutdown(); }); test('it shows license banner warning if license expires within 30 days', async function (assert) { const healthResp = generateHealthResponse(this.now, 'expiring'); this.server.get('/sys/health', () => healthResp); await visit('/vault/auth'); - assert.dom('[data-test-license-banner-warning]').exists('license warning shows'); + assert.dom(GENERAL.licenseBanner('warning')).exists('license warning shows'); this.server.shutdown(); }); @@ -74,7 +75,7 @@ module('Acceptance | Enterprise | License banner warnings', function (hooks) { const healthResp = generateHealthResponse(this.now, 'expired'); this.server.get('/sys/health', () => healthResp); await visit('/vault/auth'); - assert.dom('[data-test-license-banner-expired]').exists('expired license message shows'); + assert.dom(GENERAL.licenseBanner('expired')).exists('expired license message shows'); this.server.shutdown(); }); }); diff --git a/ui/tests/helpers/components/sidebar-nav.js b/ui/tests/helpers/components/sidebar-nav.js index 760cbf1bd0..d9d3816a7d 100644 --- a/ui/tests/helpers/components/sidebar-nav.js +++ b/ui/tests/helpers/components/sidebar-nav.js @@ -3,7 +3,7 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { allFeatures } from 'vault/helpers/all-features'; +import { allFeatures } from 'core/utils/all-features'; import sinon from 'sinon'; /** diff --git a/ui/tests/helpers/general-selectors.ts b/ui/tests/helpers/general-selectors.ts index 98d11d48a2..9761b5e404 100644 --- a/ui/tests/helpers/general-selectors.ts +++ b/ui/tests/helpers/general-selectors.ts @@ -21,7 +21,10 @@ export const GENERAL = { tab: (name: string) => `[data-test-tab="${name}"]`, hdsTab: (name: string) => `[data-test-tab="${name}"] button`, // HDS tab buttons secretTab: (name: string) => `[data-test-secret-list-tab="${name}"]`, - navLink: (label: string) => `[data-test-sidebar-nav-link="${label}"]`, + navLink: (label: string) => + label ? `[data-test-sidebar-nav-link="${label}"]` : '[data-test-sidebar-nav-link]', + navHeading: (label: string) => + label ? `[data-test-sidebar-nav-heading="${label}"]` : '[data-test-sidebar-nav-heading]', linkTo: (label: string) => `[data-test-link-to="${label}"]`, /* ────── Buttons ────── */ @@ -177,6 +180,7 @@ export const GENERAL = { /* ────── Misc ────── */ icon: (name: string) => (name ? `[data-test-icon="${name}"]` : '[data-test-icon]'), badge: (name: string) => (name ? `[data-test-badge="${name}"]` : '[data-test-badge]'), + licenseBanner: (name: string) => `[data-test-license-banner="${name}"]`, tooltip: (label: string) => `[data-test-tooltip="${label}"]`, tooltipText: '.hds-tooltip-container', manageDropdown: '[data-test-manage-dropdown]', diff --git a/ui/tests/integration/components/dashboard/overview-test.js b/ui/tests/integration/components/dashboard/overview-test.js index e2287938e0..94ce005fa1 100644 --- a/ui/tests/integration/components/dashboard/overview-test.js +++ b/ui/tests/integration/components/dashboard/overview-test.js @@ -178,7 +178,7 @@ module('Integration | Component | dashboard/overview', function (hooks) { assert.dom(DASHBOARD.cardName('client-count')).exists(); }); - test('it should show not show client count on enterprise in child namespaces called "admin" when running a managed mode', async function (assert) { + test('it should hide client count on enterprise in child namespaces called "admin" when running a managed mode', async function (assert) { this.permissions.exactPaths = { 'admin/sys/internal/counters/activity': { capabilities: ['read'], @@ -218,6 +218,17 @@ module('Integration | Component | dashboard/overview', function (hooks) { assert.dom(DASHBOARD.cardName('client-count')).doesNotExist(); }); + test('it should hide client count on PKI-only Secrets clusters', async function (assert) { + this.permissions.exactPaths = { + 'sys/internal/counters/activity': { + capabilities: ['read'], + }, + }; + this.version.features = ['PKI-only Secrets']; + await this.renderComponent(); + assert.dom(DASHBOARD.cardName('client-count')).doesNotExist(); + }); + test('it should hide cards on enterprise in root namespace but no permission', async function (assert) { await this.renderComponent(); assert.dom(DASHBOARD.cardName('client-count')).doesNotExist(); diff --git a/ui/tests/integration/components/license-banners-test.js b/ui/tests/integration/components/license-banners-test.js index d574240425..ce40fd83f1 100644 --- a/ui/tests/integration/components/license-banners-test.js +++ b/ui/tests/integration/components/license-banners-test.js @@ -12,6 +12,7 @@ import subDays from 'date-fns/subDays'; import addDays from 'date-fns/addDays'; import formatRFC3339 from 'date-fns/formatRFC3339'; import timestamp from 'core/utils/timestamp'; +import { GENERAL } from 'vault/tests/helpers/general-selectors'; module('Integration | Component | license-banners', function (hooks) { setupRenderingTest(hooks); @@ -31,17 +32,17 @@ module('Integration | Component | license-banners', function (hooks) { test('it does not render if no expiry', async function (assert) { assert.expect(2); await render(hbs``); - assert.dom('[data-test-license-banner-expired]').doesNotExist(); - assert.dom('[data-test-license-banner-warning]').doesNotExist(); + assert.dom(GENERAL.licenseBanner('expired')).doesNotExist(); + assert.dom(GENERAL.licenseBanner('warning')).doesNotExist(); }); test('it renders an error if expiry is before now', async function (assert) { assert.expect(2); this.set('expiry', formatRFC3339(this.yesterday)); await render(hbs``); - assert.dom('[data-test-license-banner-expired]').exists('Expired license banner renders'); + assert.dom(GENERAL.licenseBanner('expired')).exists('Expired license banner renders'); assert - .dom('[data-test-license-banner-expired] .hds-alert__title') + .dom(`${GENERAL.licenseBanner('expired')} .hds-alert__title`) .hasText('License expired', 'Shows correct title on alert'); }); @@ -49,9 +50,9 @@ module('Integration | Component | license-banners', function (hooks) { assert.expect(2); this.set('expiry', formatRFC3339(this.nextMonth)); await render(hbs``); - assert.dom('[data-test-license-banner-warning]').exists('Warning license banner renders'); + assert.dom(GENERAL.licenseBanner('warning')).exists('Warning license banner renders'); assert - .dom('[data-test-license-banner-warning] .hds-alert__title') + .dom(`${GENERAL.licenseBanner('warning')} .hds-alert__title`) .hasText('Vault license expiring', 'Shows correct title on alert'); }); @@ -59,8 +60,8 @@ module('Integration | Component | license-banners', function (hooks) { assert.expect(2); this.set('expiry', formatRFC3339(this.outside30)); await render(hbs``); - assert.dom('[data-test-license-banner-expired]').doesNotExist(); - assert.dom('[data-test-license-banner-warning]').doesNotExist(); + assert.dom(GENERAL.licenseBanner('expired')).doesNotExist(); + assert.dom(GENERAL.licenseBanner('warning')).doesNotExist(); }); test('it does not render the expired banner if it has been dismissed', async function (assert) { @@ -68,14 +69,14 @@ module('Integration | Component | license-banners', function (hooks) { this.set('expiry', formatRFC3339(this.yesterday)); const key = `dismiss-license-banner-${this.version.version}-${this.expiry}`; await render(hbs``); - await click('[data-test-license-banner-expired] [data-test-icon="x"]'); - assert.dom('[data-test-license-banner-expired]').doesNotExist('Expired license banner does not render'); + await click(`${GENERAL.licenseBanner('expired')} ${GENERAL.icon('x')}`); + assert.dom(GENERAL.licenseBanner('expired')).doesNotExist('Expired license banner does not render'); await render(hbs``); const localStorageResult = JSON.parse(localStorage.getItem(key)); - assert.strictEqual(localStorageResult, 'expired'); + assert.deepEqual(localStorageResult, ['expired']); assert - .dom('[data-test-license-banner-expired]') + .dom(GENERAL.licenseBanner('expired')) .doesNotExist('The expired banner still does not render after a re-render.'); localStorage.removeItem(key); }); @@ -85,14 +86,14 @@ module('Integration | Component | license-banners', function (hooks) { this.set('expiry', formatRFC3339(this.nextMonth)); const key = `dismiss-license-banner-${this.version.version}-${this.expiry}`; await render(hbs``); - await click('[data-test-license-banner-warning] [data-test-icon="x"]'); - assert.dom('[data-test-license-banner-warning]').doesNotExist('Warning license banner does not render'); + await click(`${GENERAL.licenseBanner('warning')} ${GENERAL.icon('x')}`); + assert.dom(GENERAL.licenseBanner('warning')).doesNotExist('Warning license banner does not render'); await render(hbs``); const localStorageResult = JSON.parse(localStorage.getItem(key)); - assert.strictEqual(localStorageResult, 'warning'); + assert.deepEqual(localStorageResult, ['warning']); assert - .dom('[data-test-license-banner-warning]') + .dom(GENERAL.licenseBanner('warning')) .doesNotExist('The warning banner still does not render after a re-render.'); localStorage.removeItem(key); }); @@ -103,22 +104,23 @@ module('Integration | Component | license-banners', function (hooks) { this.set('expiry', formatRFC3339(this.nextMonth)); const keyOldVersion = `dismiss-license-banner-${this.version.version}-${this.expiry}`; await render(hbs``); - await click('[data-test-license-banner-warning] [data-test-icon="x"]'); + await click(`${GENERAL.licenseBanner('warning')} ${GENERAL.icon('x')}`); this.version.version = '1.13.1+ent'; const keyNewVersion = `dismiss-license-banner-${this.version.version}-${this.expiry}`; await render(hbs``); assert - .dom('[data-test-license-banner-warning]') + .dom(GENERAL.licenseBanner('warning')) .exists('The warning banner shows even though we have dismissed it earlier.'); - await click('[data-test-license-banner-warning] [data-test-icon="x"]'); + await click(`${GENERAL.licenseBanner('warning')} ${GENERAL.icon('x')}`); const localStorageResultNewVersion = JSON.parse(localStorage.getItem(keyNewVersion)); const localStorageResultOldVersion = JSON.parse(localStorage.getItem(keyOldVersion)); + // Check that localStorage was cleaned and no longer contains the old version storage key. assert.strictEqual(localStorageResultOldVersion, null, 'local storage was cleared for the old version'); - assert.strictEqual( + assert.deepEqual( localStorageResultNewVersion, - 'warning', + ['warning'], 'local storage holds the new version with a warning' ); // If debugging this test remember to clear localStorage if the test was not run to completion. @@ -130,25 +132,75 @@ module('Integration | Component | license-banners', function (hooks) { this.set('expiry', formatRFC3339(this.tomorrow)); const keyOldExpiry = `dismiss-license-banner-${this.version.version}-${this.expiry}`; await render(hbs``); - await click('[data-test-license-banner-warning] [data-test-icon="x"]'); + await click(`${GENERAL.licenseBanner('warning')} ${GENERAL.icon('x')}`); this.set('expiry', formatRFC3339(this.nextMonth)); const keyNewExpiry = `dismiss-license-banner-${this.version.version}-${this.expiry}`; await render(hbs``); assert - .dom('[data-test-license-banner-warning]') + .dom(GENERAL.licenseBanner('warning')) .exists('The warning banner shows even though we have dismissed it earlier.'); - await click('[data-test-license-banner-warning] [data-test-icon="x"]'); + await click(`${GENERAL.licenseBanner('warning')} ${GENERAL.icon('x')}`); const localStorageResultNewExpiry = JSON.parse(localStorage.getItem(keyNewExpiry)); const localStorageResultOldExpiry = JSON.parse(localStorage.getItem(keyOldExpiry)); // Check that localStorage was cleaned and no longer contains the old version storage key. assert.strictEqual(localStorageResultOldExpiry, null, 'local storage was cleared for the old expiry'); - assert.strictEqual( + assert.deepEqual( localStorageResultNewExpiry, - 'warning', + ['warning'], 'local storage holds the new expiry with a warning' ); // If debugging this test remember to clear localStorage if the test was not run to completion. localStorage.removeItem(keyNewExpiry); }); + + test('it renders a banner if the cluster is in PKI-only mode', async function (assert) { + assert.expect(3); + this.version.features = ['PKI-only Secrets']; + this.set('expiry', formatRFC3339(this.outside30)); + const key = `dismiss-license-banner-${this.version.version}-${this.expiry}`; + await render(hbs``); + assert.dom(GENERAL.licenseBanner('pki-only-info')).exists('The info banner renders'); + await click(`${GENERAL.licenseBanner('pki-only-info')} ${GENERAL.icon('x')}`); + assert + .dom(GENERAL.licenseBanner('pki-only-info')) + .doesNotExist('The info banner does not show after being dismissed.'); + const localStorageResult = JSON.parse(localStorage.getItem(key)); + assert.deepEqual(localStorageResult, ['pki-only-info']); + // If debugging this test remember to clear localStorage if the test was not run to completion. + localStorage.removeItem(key); + }); + + test('it renders multiple banners', async function (assert) { + assert.expect(8); + this.version.features = ['PKI-only Secrets']; + this.set('expiry', formatRFC3339(this.tomorrow)); + const key = `dismiss-license-banner-${this.version.version}-${this.expiry}`; + await render(hbs``); + assert.dom(GENERAL.licenseBanner('warning')).exists('The warning banner renders'); + assert.dom(GENERAL.licenseBanner('pki-only-info')).exists('The info banner renders'); + await click(`${GENERAL.licenseBanner('pki-only-info')} ${GENERAL.icon('x')}`); + assert + .dom(GENERAL.licenseBanner('pki-only-info')) + .doesNotExist('The info banner does not show after being dismissed.'); + assert + .dom(GENERAL.licenseBanner('warning')) + .exists('The warning banner is still shown after an info banner is dismissed.'); + + const localStorageResult = JSON.parse(localStorage.getItem(key)); + assert.deepEqual(localStorageResult, ['pki-only-info']); + + await click(`${GENERAL.licenseBanner('warning')} ${GENERAL.icon('x')}`); + assert + .dom(GENERAL.licenseBanner('pki-only-info')) + .doesNotExist('The info banner still does not show after another banner type dismissed.'); + assert + .dom(GENERAL.licenseBanner('warning')) + .doesNotExist('The warning banner does not show after being dismissed.'); + + const updatedLocalStorageResult = JSON.parse(localStorage.getItem(key)); + assert.deepEqual(updatedLocalStorageResult, ['pki-only-info', 'warning']); + // If debugging this test remember to clear localStorage if the test was not run to completion. + localStorage.removeItem(key); + }); }); diff --git a/ui/tests/integration/components/license-info-test.js b/ui/tests/integration/components/license-info-test.js index 87785cc981..0c93855600 100644 --- a/ui/tests/integration/components/license-info-test.js +++ b/ui/tests/integration/components/license-info-test.js @@ -10,7 +10,7 @@ import { render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; import { create } from 'ember-cli-page-object'; import license from '../../pages/components/license-info'; -import { allFeatures } from 'vault/helpers/all-features'; +import { allFeatures } from 'core/utils/all-features'; import { setRunOptions } from 'ember-a11y-testing/test-support'; const FEATURES = allFeatures(); diff --git a/ui/tests/integration/components/sidebar/nav/cluster-test.js b/ui/tests/integration/components/sidebar/nav/cluster-test.js index 2cf9bcc25c..2166807c70 100644 --- a/ui/tests/integration/components/sidebar/nav/cluster-test.js +++ b/ui/tests/integration/components/sidebar/nav/cluster-test.js @@ -10,6 +10,7 @@ import hbs from 'htmlbars-inline-precompile'; import { stubFeaturesAndPermissions } from 'vault/tests/helpers/components/sidebar-nav'; import { setRunOptions } from 'ember-a11y-testing/test-support'; import { GENERAL } from 'vault/tests/helpers/general-selectors'; +import { allFeatures } from 'core/utils/all-features'; const renderComponent = () => { return render(hbs` @@ -39,13 +40,9 @@ module('Integration | Component | sidebar-nav-cluster', function (hooks) { stubFeaturesAndPermissions(this.owner, true, true); await renderComponent(); - assert - .dom('[data-test-sidebar-nav-heading]') - .exists({ count: headings.length }, 'Correct number of headings render'); + assert.dom(GENERAL.navHeading()).exists({ count: headings.length }, 'Correct number of headings render'); headings.forEach((heading) => { - assert - .dom(`[data-test-sidebar-nav-heading="${heading}"]`) - .hasText(heading, `${heading} heading renders`); + assert.dom(GENERAL.navHeading(heading)).hasText(heading, `${heading} heading renders`); }); }); @@ -53,11 +50,9 @@ module('Integration | Component | sidebar-nav-cluster', function (hooks) { await renderComponent(); assert - .dom('[data-test-sidebar-nav-link]') + .dom(GENERAL.navLink()) .exists({ count: 3 }, 'Nav links are hidden other than secrets, recovery and dashboard'); - assert - .dom('[data-test-sidebar-nav-heading]') - .exists({ count: 1 }, 'Headings are hidden other than Vault'); + assert.dom(GENERAL.navHeading()).exists({ count: 1 }, 'Headings are hidden other than Vault'); }); test('it should render nav links', async function (assert) { @@ -78,14 +73,14 @@ module('Integration | Component | sidebar-nav-cluster', function (hooks) { 'Custom Messages', 'UI Login Settings', ]; - stubFeaturesAndPermissions(this.owner, true, true); + // do not add PKI-only Secrets feature as it hides Client Count nav link + const features = allFeatures().filter((feat) => feat !== 'PKI-only Secrets'); + stubFeaturesAndPermissions(this.owner, true, true, features); await renderComponent(); - assert - .dom('[data-test-sidebar-nav-link]') - .exists({ count: links.length }, 'Correct number of links render'); + assert.dom(GENERAL.navLink()).exists({ count: links.length }, 'Correct number of links render'); links.forEach((link) => { - assert.dom(`[data-test-sidebar-nav-link="${link}"]`).hasText(link, `${link} link renders`); + assert.dom(GENERAL.navLink(link)).hasText(link, `${link} link renders`); }); }); @@ -105,15 +100,13 @@ module('Integration | Component | sidebar-nav-cluster', function (hooks) { await renderComponent(); assert - .dom('[data-test-sidebar-nav-heading="Monitoring"]') + .dom(GENERAL.navHeading('Monitoring')) .doesNotExist( 'Monitoring heading is hidden in child namespace when user does not have access to Client Count' ); links.forEach((link) => { - assert - .dom(`[data-test-sidebar-nav-link="${link}"]`) - .doesNotExist(`${link} is hidden in child namespace`); + assert.dom(GENERAL.navLink(link)).doesNotExist(`${link} is hidden in child namespace`); }); }); @@ -134,15 +127,19 @@ module('Integration | Component | sidebar-nav-cluster', function (hooks) { await renderComponent(); assert - .dom('[data-test-sidebar-nav-heading="Monitoring"]') + .dom(GENERAL.navHeading('Monitoring')) .doesNotExist('Monitoring heading is hidden in chroot namespace'); links.forEach((link) => { - assert - .dom(`[data-test-sidebar-nav-link="${link}"]`) - .doesNotExist(`${link} is hidden in chroot namespace`); + assert.dom(GENERAL.navLink(link)).doesNotExist(`${link} is hidden in chroot namespace`); }); }); + test('it should hide client counts link in PKI-only Secrets clusters', async function (assert) { + stubFeaturesAndPermissions(this.owner, true, false); + await renderComponent(); + assert.dom(GENERAL.navHeading('Client Counts')).doesNotExist('Client count link is hidden.'); + }); + test('it should render badge for promotional links on managed clusters', async function (assert) { this.flags.featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE']; const promotionalLinks = ['Secrets Sync']; @@ -150,9 +147,7 @@ module('Integration | Component | sidebar-nav-cluster', function (hooks) { await renderComponent(); promotionalLinks.forEach((link) => { - assert - .dom(`[data-test-sidebar-nav-link="${link}"]`) - .hasText(`${link} Plus`, `${link} link renders Plus badge`); + assert.dom(GENERAL.navLink(link)).hasText(`${link} Plus`, `${link} link renders Plus badge`); }); }); diff --git a/ui/tests/unit/services/version-test.js b/ui/tests/unit/services/version-test.js index 434c5cb282..5dfe85c9f7 100644 --- a/ui/tests/unit/services/version-test.js +++ b/ui/tests/unit/services/version-test.js @@ -47,6 +47,13 @@ module('Unit | Service | version', function (hooks) { assert.true(service.hasDRReplication); }); + test('hasPKIOnly', function (assert) { + const service = this.owner.lookup('service:version'); + assert.false(service.hasPKIOnly); + service.features = ['PKI-only Secrets']; + assert.true(service.hasPKIOnly); + }); + // SHOW SECRETS SYNC TESTS test('hasSecretsSync: it returns false when version is community', function (assert) { const service = this.owner.lookup('service:version');