Disabling License Banners (#19116)

* work in progress: got the expired banner set with license check

* wip: got the logic for both banners, need to test and write tests

* add notes

* prep for test writing

* test coverage

* add changelog

* clean up

* clarify dismissTypes and conditionals

* updates

* update comment

* update comment

* address pr comments

* update test

* small naming change

* small naming changes

* clean localStorage

* comment clean up

* another comment clean up

* remove meep

* add test coverage for new method in localStorage
This commit is contained in:
Angel Garbarino 2023-02-14 10:00:24 -07:00 committed by GitHub
parent d0925f3661
commit 3003ff85ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 183 additions and 11 deletions

3
changelog/19116.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
ui: Allows license-banners to be dismissed. Saves preferences in localStorage.
```

View File

@ -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;
}
}
}

View File

@ -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);
}
});
},
};

View File

@ -1,4 +1,4 @@
{{#if this.licenseExpired}}
{{#if (and this.licenseExpired (not this.expiredDismissed))}}
<div class="license-banner-wrapper" data-test-license-banner data-test-license-banner-expired>
<AlertBanner
@type="danger"
@ -13,9 +13,12 @@
<Icon @name="learn-link" />
Read documentation
</DocLink>
<button type="button" class="close-button" {{on "click" (fn this.dismissBanner "expired")}} data-test-dismiss-expired>
<Icon @name="x" aria-label="dismiss license expired warning" />
</button>
</AlertBanner>
</div>
{{else if (lte this.licenseExpiringInDays 30)}}
{{else if (and (lte this.licenseExpiringInDays 30) (not this.warningDismissed))}}
<div class="license-banner-wrapper" data-test-license-banner data-test-license-banner-warning>
<AlertBanner
@type="warning"
@ -34,6 +37,9 @@
<Icon @name="learn-link" />
Read documentation
</DocLink>
<button type="button" class="close-button" {{on "click" (fn this.dismissBanner "warning")}} data-test-dismiss-warning>
<Icon @name="x" aria-label="dismiss license expire soon warning" />
</button>
</AlertBanner>
</div>
{{/if}}

View File

@ -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`<LicenseBanners />`);
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`<LicenseBanners @expiry={{this.expiry}} />`);
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`<LicenseBanners @expiry={{this.expiry}} />`);
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`<LicenseBanners @expiry={{this.expiry}} />`);
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`<LicenseBanners @expiry={{this.expiry}} />`);
await click('[data-test-dismiss-expired]');
assert.dom('[data-test-license-banner-expired]').doesNotExist('Expired license banner does not render');
await render(hbs`<LicenseBanners @expiry={{this.expiry}} />`);
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`<LicenseBanners @expiry={{this.expiry}} />`);
await click('[data-test-dismiss-warning]');
assert.dom('[data-test-license-banner-warning]').doesNotExist('Warning license banner does not render');
await render(hbs`<LicenseBanners @expiry={{this.expiry}} />`);
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`<LicenseBanners @expiry={{this.expiry}} />`);
await click('[data-test-dismiss-warning]');
this.version.version = '1.13.1+ent';
await render(hbs`<LicenseBanners @expiry={{this.expiry}} />`);
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`);
});
});

View File

@ -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();
});
});