Commit 4f12a4dd authored by Dennis Tang's avatar Dennis Tang Committed by Kushal Pandya

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.
parent f74387d2
import { parseBoolean } from './lib/utils/common_utils';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import { __ } from './locale'; import { __ } from './locale';
import Flash from './flash'; import Flash from './flash';
const DEFERRED_LINK_CLASS = 'deferred-link';
export default class PersistentUserCallout { export default class PersistentUserCallout {
constructor(container) { constructor(container) {
const { dismissEndpoint, featureId } = container.dataset; const { dismissEndpoint, featureId, deferLinks } = container.dataset;
this.container = container; this.container = container;
this.dismissEndpoint = dismissEndpoint; this.dismissEndpoint = dismissEndpoint;
this.featureId = featureId; this.featureId = featureId;
this.deferLinks = parseBoolean(deferLinks);
this.init(); this.init();
} }
...@@ -15,9 +19,21 @@ export default class PersistentUserCallout { ...@@ -15,9 +19,21 @@ export default class PersistentUserCallout {
init() { init() {
const closeButton = this.container.querySelector('.js-close'); const closeButton = this.container.querySelector('.js-close');
closeButton.addEventListener('click', event => this.dismiss(event)); 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(); event.preventDefault();
axios axios
...@@ -26,6 +42,11 @@ export default class PersistentUserCallout { ...@@ -26,6 +42,11 @@ export default class PersistentUserCallout {
}) })
.then(() => { .then(() => {
this.container.remove(); this.container.remove();
if (deferredLinkOptions) {
const { href, target } = deferredLinkOptions;
window.open(href, target);
}
}) })
.catch(() => { .catch(() => {
Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.')); Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.'));
......
import PersistentUserCallout from '~/persistent_user_callout';
function initPrivacyPolicyUpdateCallout() {
const callout = document.querySelector('.privacy-policy-update-64341');
PersistentUserCallout.factory(callout);
}
export default initPrivacyPolicyUpdateCallout;
---
title: Add support for deferred links in persistent user callouts.
merge_request: 30818
author:
type: added
...@@ -22,6 +22,24 @@ describe('PersistentUserCallout', () => { ...@@ -22,6 +22,24 @@ describe('PersistentUserCallout', () => {
return fixture; 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', () => { describe('dismiss', () => {
let button; let button;
let mockAxios; let mockAxios;
...@@ -74,6 +92,75 @@ describe('PersistentUserCallout', () => { ...@@ -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', () => { describe('factory', () => {
it('returns an instance of PersistentUserCallout with the provided container property', () => { it('returns an instance of PersistentUserCallout with the provided container property', () => {
const fixture = createFixture(); const fixture = createFixture();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment