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.
This commit is contained in:
Dennis Tang 2019-08-05 09:00:34 +00:00 committed by Kushal Pandya
parent f74387d298
commit 4f12a4dde1
4 changed files with 123 additions and 2 deletions

View File

@ -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.'));

View File

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

View File

@ -0,0 +1,5 @@
---
title: Add support for deferred links in persistent user callouts.
merge_request: 30818
author:
type: added

View File

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