diff --git a/changelog/19116.txt b/changelog/19116.txt
new file mode 100644
index 000000000..5dfcd9ecf
--- /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 91f4d2b15..2839a3a29 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 86556835c..5447118e3 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 8c043d37e..0c9c09c60 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))}}
-{{else if (lte this.licenseExpiringInDays 30)}}
+{{else if (and (lte this.licenseExpiringInDays 30) (not this.warningDismissed))}}
{{/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 8fceba13f..d2159a761 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 000000000..9d22fb3fe
--- /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();
+ });
+});