diff --git a/changelog/19116.txt b/changelog/19116.txt new file mode 100644 index 000000000000..5dfcd9ecfada --- /dev/null +++ b/changelog/19116.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: Allows license-banners to be dismissed. Saves preferences in localStorage. +``` \ No newline at end of file diff --git a/ui/app/components/license-banners.js b/ui/app/components/license-banners.js index 91f4d2b15044..2839a3a29421 100644 --- a/ui/app/components/license-banners.js +++ b/ui/app/components/license-banners.js @@ -1,3 +1,11 @@ +import Component from '@glimmer/component'; +import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; +import { inject as service } from '@ember/service'; +import isAfter from 'date-fns/isAfter'; +import differenceInDays from 'date-fns/differenceInDays'; +import localStorage from 'vault/lib/local-storage'; + /** * @module LicenseBanners * LicenseBanners components are used to display Vault-specific license expiry messages @@ -9,11 +17,23 @@ * @param {string} expiry - RFC3339 date timestamp */ -import Component from '@glimmer/component'; -import isAfter from 'date-fns/isAfter'; -import differenceInDays from 'date-fns/differenceInDays'; - export default class LicenseBanners extends Component { + @service version; + + @tracked warningDismissed; + @tracked expiredDismissed; + + constructor() { + super(...arguments); + // do not dismiss any banners if the user has updated their version + const dismissedBanner = localStorage.getItem(`dismiss-license-banner-${this.currentVersion}`); // returns either warning or expired + this.updateDismissType(dismissedBanner); + } + + get currentVersion() { + return this.version.version; + } + get licenseExpired() { if (!this.args.expiry) return false; return isAfter(new Date(), new Date(this.args.expiry)); @@ -24,4 +44,22 @@ export default class LicenseBanners extends Component { if (!this.args.expiry) return 99; return differenceInDays(new Date(this.args.expiry), new Date()); } + + @action + dismissBanner(dismissAction) { + // if a client's version changed their old localStorage key will still exists. + localStorage.cleanUpStorage('dismiss-license-banner', `dismiss-license-banner-${this.currentVersion}`); + // updates localStorage and then updates the template by calling updateDismissType + localStorage.setItem(`dismiss-license-banner-${this.currentVersion}`, 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/lib/local-storage.js b/ui/app/lib/local-storage.js index 86556835c7f8..5447118e3281 100644 --- a/ui/app/lib/local-storage.js +++ b/ui/app/lib/local-storage.js @@ -15,4 +15,14 @@ export default { keys() { return Object.keys(window.localStorage); }, + + cleanUpStorage(string, keyToKeep) { + if (!string) return; + const relevantKeys = this.keys().filter((str) => str.startsWith(string)); + relevantKeys?.forEach((key) => { + if (key !== keyToKeep) { + localStorage.removeItem(key); + } + }); + }, }; diff --git a/ui/app/templates/components/license-banners.hbs b/ui/app/templates/components/license-banners.hbs index 8c043d37e34c..0c9c09c60eec 100644 --- a/ui/app/templates/components/license-banners.hbs +++ b/ui/app/templates/components/license-banners.hbs @@ -1,4 +1,4 @@ -{{#if this.licenseExpired}} +{{#if (and this.licenseExpired (not this.expiredDismissed))}}
Read documentation +
-{{else if (lte this.licenseExpiringInDays 30)}} +{{else if (and (lte this.licenseExpiringInDays 30) (not this.warningDismissed))}}
Read documentation +
{{/if}} \ No newline at end of file diff --git a/ui/tests/integration/components/license-banners-test.js b/ui/tests/integration/components/license-banners-test.js index 8fceba13f1e9..d2159a7613bb 100644 --- a/ui/tests/integration/components/license-banners-test.js +++ b/ui/tests/integration/components/license-banners-test.js @@ -1,39 +1,107 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; -import { render } from '@ember/test-helpers'; +import { render, click } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import subDays from 'date-fns/subDays'; import addDays from 'date-fns/addDays'; import formatRFC3339 from 'date-fns/formatRFC3339'; +const YESTERDAY = subDays(new Date(), 1); +const NEXT_MONTH = addDays(new Date(), 30); + module('Integration | Component | license-banners', function (hooks) { setupRenderingTest(hooks); + hooks.beforeEach(function () { + this.version = this.owner.lookup('service:version'); + this.version.version = '1.13.1+ent'; + }); + test('it does not render if no expiry', async function (assert) { + assert.expect(1); await render(hbs``); assert.dom('[data-test-license-banner]').doesNotExist('License banner does not render'); }); test('it renders an error if expiry is before now', async function (assert) { - const yesterday = subDays(new Date(), 1); - this.set('expiry', formatRFC3339(yesterday)); + assert.expect(2); + this.set('expiry', formatRFC3339(YESTERDAY)); await render(hbs``); assert.dom('[data-test-license-banner-expired]').exists('Expired license banner renders'); assert.dom('.message-title').hasText('License expired', 'Shows correct title on alert'); }); test('it renders a warning if expiry is within 30 days', async function (assert) { - const nextMonth = addDays(new Date(), 30); - this.set('expiry', formatRFC3339(nextMonth)); + assert.expect(2); + this.set('expiry', formatRFC3339(NEXT_MONTH)); await render(hbs``); assert.dom('[data-test-license-banner-warning]').exists('Warning license banner renders'); assert.dom('.message-title').hasText('Vault license expiring', 'Shows correct title on alert'); }); test('it does not render a banner if expiry is outside 30 days', async function (assert) { + assert.expect(1); const outside30 = addDays(new Date(), 32); this.set('expiry', formatRFC3339(outside30)); await render(hbs``); assert.dom('[data-test-license-banner]').doesNotExist('License banner does not render'); }); + + test('it does not render the expired banner if it has been dismissed', async function (assert) { + assert.expect(3); + this.set('expiry', formatRFC3339(YESTERDAY)); + await render(hbs``); + await click('[data-test-dismiss-expired]'); + assert.dom('[data-test-license-banner-expired]').doesNotExist('Expired license banner does not render'); + + await render(hbs``); + const localStorageResult = JSON.parse(localStorage.getItem(`dismiss-license-banner-1.13.1+ent`)); + assert.strictEqual(localStorageResult, 'expired'); + assert + .dom('[data-test-license-banner-expired]') + .doesNotExist('The expired banner still does not render after a re-render.'); + localStorage.removeItem(`dismiss-license-banner-1.13.1+ent`); + }); + + test('it does not render the warning banner if it has been dismissed', async function (assert) { + assert.expect(3); + this.set('expiry', formatRFC3339(NEXT_MONTH)); + await render(hbs``); + await click('[data-test-dismiss-warning]'); + assert.dom('[data-test-license-banner-warning]').doesNotExist('Warning license banner does not render'); + + await render(hbs``); + const localStorageResult = JSON.parse(localStorage.getItem(`dismiss-license-banner-1.13.1+ent`)); + assert.strictEqual(localStorageResult, 'warning'); + assert + .dom('[data-test-license-banner-warning]') + .doesNotExist('The warning banner still does not render after a re-render.'); + localStorage.removeItem(`dismiss-license-banner-1.13.1+ent`); + }); + + test('it renders a banner if the vault license has changed', async function (assert) { + assert.expect(3); + this.version.version = '1.12.1+ent'; + this.set('expiry', formatRFC3339(NEXT_MONTH)); + await render(hbs``); + await click('[data-test-dismiss-warning]'); + this.version.version = '1.13.1+ent'; + await render(hbs``); + assert + .dom('[data-test-license-banner-warning]') + .exists('The warning banner shows even though we have dismissed it earlier.'); + + await click('[data-test-dismiss-warning]'); + const localStorageResultNewVersion = JSON.parse( + localStorage.getItem(`dismiss-license-banner-1.13.1+ent`) + ); + const localStorageResultOldVersion = JSON.parse( + localStorage.getItem(`dismiss-license-banner-1.12.1+ent`) + ); + // Check that localStorage was cleaned and no longer contains the old version storage key. + assert.strictEqual(localStorageResultOldVersion, null); + assert.strictEqual(localStorageResultNewVersion, 'warning'); + // If debugging this test remember to clear localStorage if the test was not run to completion. + localStorage.removeItem(`dismiss-license-banner-1.13.1+ent`); + }); }); diff --git a/ui/tests/unit/lib/local-storage-test.js b/ui/tests/unit/lib/local-storage-test.js new file mode 100644 index 000000000000..9d22fb3fe07a --- /dev/null +++ b/ui/tests/unit/lib/local-storage-test.js @@ -0,0 +1,47 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import LocalStorage from 'vault/lib/local-storage'; + +module('Unit | lib | local-storage', function (hooks) { + setupTest(hooks); + + hooks.beforeEach(function () { + window.localStorage.clear(); + }); + + test('it does not error if nothing is in local storage', async function (assert) { + assert.expect(1); + assert.strictEqual( + LocalStorage.cleanUpStorage('something', 'something-key'), + undefined, + 'returns undefined and does not throw an error when method is called and nothing exist in localStorage.' + ); + }); + + test('it does not remove anything in localStorage that does not start with the string or we have specified to keep.', async function (assert) { + assert.expect(3); + LocalStorage.setItem('string-key-remove', 'string-key-remove-value'); + LocalStorage.setItem('beep-boop-bop-key', 'beep-boop-bop-value'); + LocalStorage.setItem('string-key', 'string-key-value'); + const storageLengthBefore = window.localStorage.length; + LocalStorage.cleanUpStorage('string', 'string-key'); + const storageLengthAfter = window.localStorage.length; + assert.strictEqual( + storageLengthBefore - storageLengthAfter, + 1, + 'the method should only remove one key from localStorage.' + ); + assert.strictEqual( + LocalStorage.getItem('string-key'), + 'string-key-value', + 'the key we asked to keep still exists in localStorage.' + ); + assert.strictEqual( + LocalStorage.getItem('string-key-remove'), + null, + 'the key we did not specify to keep was removed from localStorage.' + ); + // clear storage + window.localStorage.clear(); + }); +});