diff options
author | Dennis Tang <750946-dennis@users.noreply.gitlab.com> | 2019-08-05 09:00:34 +0000 |
---|---|---|
committer | Kushal Pandya <kushalspandya@gmail.com> | 2019-08-05 09:00:34 +0000 |
commit | 4f12a4dde1aac5c5c7fa83e42180d01a1fdb5b30 (patch) | |
tree | 35ab4588496416a20f15f27bf4cb0e6b1c0d784e | |
parent | f74387d2982845b0fb5219ccf3fd128d75400472 (diff) | |
download | gitlab-ce-4f12a4dde1aac5c5c7fa83e42180d01a1fdb5b30.tar.gz |
Add support for deferred links in persistent user callouts
Persistent user callouts now support deferred links, which are links
that can be used to dismiss the callout, and then proceed to follow
the link's original location.
This ensures that the callout dismissal is properly recorded
before the user leaves the page.
4 files changed, 123 insertions, 2 deletions
diff --git a/app/assets/javascripts/persistent_user_callout.js b/app/assets/javascripts/persistent_user_callout.js index 4a08e158f6b..8d6a3781048 100644 --- a/app/assets/javascripts/persistent_user_callout.js +++ b/app/assets/javascripts/persistent_user_callout.js @@ -1,13 +1,17 @@ +import { parseBoolean } from './lib/utils/common_utils'; import axios from './lib/utils/axios_utils'; import { __ } from './locale'; import Flash from './flash'; +const DEFERRED_LINK_CLASS = 'deferred-link'; + export default class PersistentUserCallout { constructor(container) { - const { dismissEndpoint, featureId } = container.dataset; + const { dismissEndpoint, featureId, deferLinks } = container.dataset; this.container = container; this.dismissEndpoint = dismissEndpoint; this.featureId = featureId; + this.deferLinks = parseBoolean(deferLinks); this.init(); } @@ -15,9 +19,21 @@ export default class PersistentUserCallout { init() { const closeButton = this.container.querySelector('.js-close'); closeButton.addEventListener('click', event => this.dismiss(event)); + + if (this.deferLinks) { + this.container.addEventListener('click', event => { + const isDeferredLink = event.target.classList.contains(DEFERRED_LINK_CLASS); + + if (isDeferredLink) { + const { href, target } = event.target; + + this.dismiss(event, { href, target }); + } + }); + } } - dismiss(event) { + dismiss(event, deferredLinkOptions = null) { event.preventDefault(); axios @@ -26,6 +42,11 @@ export default class PersistentUserCallout { }) .then(() => { this.container.remove(); + + if (deferredLinkOptions) { + const { href, target } = deferredLinkOptions; + window.open(href, target); + } }) .catch(() => { Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.')); diff --git a/app/assets/javascripts/privacy_policy_update_callout.js b/app/assets/javascripts/privacy_policy_update_callout.js new file mode 100644 index 00000000000..126b1ee1132 --- /dev/null +++ b/app/assets/javascripts/privacy_policy_update_callout.js @@ -0,0 +1,8 @@ +import PersistentUserCallout from '~/persistent_user_callout'; + +function initPrivacyPolicyUpdateCallout() { + const callout = document.querySelector('.privacy-policy-update-64341'); + PersistentUserCallout.factory(callout); +} + +export default initPrivacyPolicyUpdateCallout; diff --git a/changelogs/unreleased/64341-user-callout-deferred-link-support.yml b/changelogs/unreleased/64341-user-callout-deferred-link-support.yml new file mode 100644 index 00000000000..05230ddc124 --- /dev/null +++ b/changelogs/unreleased/64341-user-callout-deferred-link-support.yml @@ -0,0 +1,5 @@ +--- +title: Add support for deferred links in persistent user callouts. +merge_request: 30818 +author: +type: added diff --git a/spec/javascripts/persistent_user_callout_spec.js b/spec/javascripts/persistent_user_callout_spec.js index 2fdfff3db03..d15758be5d2 100644 --- a/spec/javascripts/persistent_user_callout_spec.js +++ b/spec/javascripts/persistent_user_callout_spec.js @@ -22,6 +22,24 @@ describe('PersistentUserCallout', () => { return fixture; } + function createDeferredLinkFixture() { + const fixture = document.createElement('div'); + fixture.innerHTML = ` + <div + class="container" + data-dismiss-endpoint="${dismissEndpoint}" + data-feature-id="${featureName}" + data-defer-links="true" + > + <button type="button" class="js-close"></button> + <a href="/somewhere-pleasant" target="_blank" class="deferred-link">A link</a> + <a href="/somewhere-else" target="_blank" class="normal-link">Another link</a> + </div> + `; + + return fixture; + } + describe('dismiss', () => { let button; let mockAxios; @@ -74,6 +92,75 @@ describe('PersistentUserCallout', () => { }); }); + describe('deferred links', () => { + let button; + let deferredLink; + let normalLink; + let mockAxios; + let persistentUserCallout; + let windowSpy; + + beforeEach(() => { + const fixture = createDeferredLinkFixture(); + const container = fixture.querySelector('.container'); + button = fixture.querySelector('.js-close'); + deferredLink = fixture.querySelector('.deferred-link'); + normalLink = fixture.querySelector('.normal-link'); + mockAxios = new MockAdapter(axios); + persistentUserCallout = new PersistentUserCallout(container); + spyOn(persistentUserCallout.container, 'remove'); + windowSpy = spyOn(window, 'open').and.callFake(() => {}); + }); + + afterEach(() => { + mockAxios.restore(); + }); + + it('defers loading of a link until callout is dismissed', done => { + const { href, target } = deferredLink; + mockAxios.onPost(dismissEndpoint).replyOnce(200); + + deferredLink.click(); + + setTimeoutPromise() + .then(() => { + expect(windowSpy).toHaveBeenCalledWith(href, target); + expect(persistentUserCallout.container.remove).toHaveBeenCalled(); + expect(mockAxios.history.post[0].data).toBe( + JSON.stringify({ feature_name: featureName }), + ); + }) + .then(done) + .catch(done.fail); + }); + + it('does not dismiss callout on non-deferred links', done => { + normalLink.click(); + + setTimeoutPromise() + .then(() => { + expect(windowSpy).not.toHaveBeenCalled(); + expect(persistentUserCallout.container.remove).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + + it('does not follow link when notification is closed', done => { + mockAxios.onPost(dismissEndpoint).replyOnce(200); + + button.click(); + + setTimeoutPromise() + .then(() => { + expect(windowSpy).not.toHaveBeenCalled(); + expect(persistentUserCallout.container.remove).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + }); + describe('factory', () => { it('returns an instance of PersistentUserCallout with the provided container property', () => { const fixture = createFixture(); |