summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDennis Tang <dtang@gitlab.com>2019-07-16 21:20:22 +0000
committerDennis Tang <dtang@gitlab.com>2019-08-02 18:37:31 +0000
commit75a6142b7a4fdd7b2549a50caa6891cc08e9651c (patch)
treec20e63884dcc1637303764f2b7baa5c34eed0ce9
parentdd05aed1d36160bd035d56ee84524a7ea518ea4f (diff)
downloadgitlab-ce-64341-user-callout-deferred-link-support.tar.gz
Add support for deferred links in persistent user callouts64341-user-callout-deferred-link-support
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.
-rw-r--r--app/assets/javascripts/persistent_user_callout.js25
-rw-r--r--app/assets/javascripts/privacy_policy_update_callout.js8
-rw-r--r--changelogs/unreleased/64341-user-callout-deferred-link-support.yml5
-rw-r--r--spec/javascripts/persistent_user_callout_spec.js87
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();