@financial-times/cp-content-pipeline-ui 6.13.0 → 6.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/lib/client.d.ts +1 -0
- package/lib/client.js +3 -1
- package/lib/client.js.map +1 -1
- package/lib/components/Flourish/index.js +3 -3
- package/lib/components/Flourish/index.js.map +1 -1
- package/lib/components/Topper/client/index.d.ts +2 -0
- package/lib/components/Topper/client/index.js +6 -0
- package/lib/components/Topper/client/index.js.map +1 -0
- package/lib/components/Topper/client/tracking.d.ts +17 -0
- package/lib/components/Topper/client/tracking.js +68 -0
- package/lib/components/Topper/client/tracking.js.map +1 -0
- package/lib/components/Topper/client/tracking.spec.d.ts +1 -0
- package/lib/components/Topper/client/tracking.spec.js +115 -0
- package/lib/components/Topper/client/tracking.spec.js.map +1 -0
- package/package.json +1 -1
- package/src/client.ts +1 -0
- package/src/components/Flourish/index.tsx +5 -2
- package/src/components/Flourish/test/__snapshots__/snapshot.spec.tsx.snap +12 -0
- package/src/components/Topper/client/index.ts +3 -0
- package/src/components/Topper/client/tracking.spec.ts +132 -0
- package/src/components/Topper/client/tracking.ts +88 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -574,6 +574,21 @@
|
|
|
574
574
|
* @financial-times/cp-content-pipeline-client bumped from ^3.7.2 to ^3.7.3
|
|
575
575
|
* @financial-times/cp-content-pipeline-schema bumped from ^2.10.1 to ^2.10.2
|
|
576
576
|
|
|
577
|
+
## [6.14.0](https://github.com/Financial-Times/cp-content-pipeline/compare/cp-content-pipeline-ui-v6.13.0...cp-content-pipeline-ui-v6.14.0) (2024-07-16)
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
### Features
|
|
581
|
+
|
|
582
|
+
* tracking for flourish toppers ([4c19db6](https://github.com/Financial-Times/cp-content-pipeline/commit/4c19db61bf28e3a2f04c6eae8eb7344711076ea6))
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
### Bug Fixes
|
|
586
|
+
|
|
587
|
+
* decrease amount of element needed to be seen ([ba8264a](https://github.com/Financial-Times/cp-content-pipeline/commit/ba8264aa99c75a30db7b878c64766816d1464bdc))
|
|
588
|
+
* genericise class to be used on all toppers ([1021468](https://github.com/Financial-Times/cp-content-pipeline/commit/1021468cfdd6d1f75a1d7a45ec7f7c142da6f9a8))
|
|
589
|
+
* remove unneeded data attribute ([6ce8529](https://github.com/Financial-Times/cp-content-pipeline/commit/6ce8529588984839dccf21d29375450671ad2841))
|
|
590
|
+
* return at first opportunity ([508b07c](https://github.com/Financial-Times/cp-content-pipeline/commit/508b07c7af834b6f2d02d2644ec126da195201e3))
|
|
591
|
+
|
|
577
592
|
## [6.13.0](https://github.com/Financial-Times/cp-content-pipeline/compare/cp-content-pipeline-ui-v6.12.0...cp-content-pipeline-ui-v6.13.0) (2024-07-10)
|
|
578
593
|
|
|
579
594
|
|
package/lib/client.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { default as Clip } from './components/Clip/client/index';
|
|
2
2
|
export { default as BackToTopButton } from './components/BackToTopButton/client';
|
|
3
3
|
export { default as FlourishSSREmbed } from './components/Flourish/client/index';
|
|
4
|
+
export { TopperTracker } from './components/Topper/client/index';
|
package/lib/client.js
CHANGED
|
@@ -3,11 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.FlourishSSREmbed = exports.BackToTopButton = exports.Clip = void 0;
|
|
6
|
+
exports.TopperTracker = exports.FlourishSSREmbed = exports.BackToTopButton = exports.Clip = void 0;
|
|
7
7
|
var index_1 = require("./components/Clip/client/index");
|
|
8
8
|
Object.defineProperty(exports, "Clip", { enumerable: true, get: function () { return __importDefault(index_1).default; } });
|
|
9
9
|
var client_1 = require("./components/BackToTopButton/client");
|
|
10
10
|
Object.defineProperty(exports, "BackToTopButton", { enumerable: true, get: function () { return __importDefault(client_1).default; } });
|
|
11
11
|
var index_2 = require("./components/Flourish/client/index");
|
|
12
12
|
Object.defineProperty(exports, "FlourishSSREmbed", { enumerable: true, get: function () { return __importDefault(index_2).default; } });
|
|
13
|
+
var index_3 = require("./components/Topper/client/index");
|
|
14
|
+
Object.defineProperty(exports, "TopperTracker", { enumerable: true, get: function () { return index_3.TopperTracker; } });
|
|
13
15
|
//# sourceMappingURL=client.js.map
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;;;;;AAAA,wDAAgE;AAAvD,8GAAA,OAAO,OAAQ;AACxB,8DAAgF;AAAvE,0HAAA,OAAO,OAAmB;AACnC,4DAAgF;AAAvE,0HAAA,OAAO,OAAoB"}
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;;;;;AAAA,wDAAgE;AAAvD,8GAAA,OAAO,OAAQ;AACxB,8DAAgF;AAAvE,0HAAA,OAAO,OAAmB;AACnC,4DAAgF;AAAvE,0HAAA,OAAO,OAAoB;AACpC,0DAAgE;AAAvD,sGAAA,aAAa,OAAA"}
|
|
@@ -10,6 +10,8 @@ const DisclaimerNotice = ({ id }) => (react_1.default.createElement("div", { id:
|
|
|
10
10
|
react_1.default.createElement("div", { className: "o-message__content" },
|
|
11
11
|
react_1.default.createElement("p", { className: "o-message__content-main" }, "You are seeing a snapshot of an interactive graphic. This is most likely due to being offline or JavaScript being disabled in your browser.")))));
|
|
12
12
|
function Flourish({ id, flourishType, description, layoutWidth, fallbackImage, iFrame = false, inArticleBody = true, }) {
|
|
13
|
+
if (!id)
|
|
14
|
+
return null;
|
|
13
15
|
const anchorHref = `#${id}`;
|
|
14
16
|
const fullGrid = layoutWidth === 'full-grid' || layoutWidth === 'grid';
|
|
15
17
|
const figureClassnames = (0, classnames_1.default)({
|
|
@@ -27,13 +29,11 @@ function Flourish({ id, flourishType, description, layoutWidth, fallbackImage, i
|
|
|
27
29
|
aspectRatio: `${fallbackImage.width}/${fallbackImage.height}`,
|
|
28
30
|
}
|
|
29
31
|
: {};
|
|
30
|
-
if (!id)
|
|
31
|
-
return null;
|
|
32
32
|
return (react_1.default.createElement("div", { className: (0, classnames_1.default)({
|
|
33
33
|
'n-content-layout': inArticleBody,
|
|
34
34
|
flourish: iFrame,
|
|
35
35
|
'flourish--iFrame': iFrame,
|
|
36
|
-
}), "data-layout-width": fullGrid ? 'full-grid' : null, "data-component": "flourish" },
|
|
36
|
+
}), "data-layout-width": fullGrid ? 'full-grid' : null, "data-component": "flourish", "data-component-id": id, "data-component-type": inArticleBody ? 'flourish-in-article' : 'flourish-topper' },
|
|
37
37
|
iFrame && (react_1.default.createElement("iframe", { src: `https://flo.uri.sh/visualisation/${id}/embed?hideTitle=${!inArticleBody}`, style: { width: '100%', aspectRatio: iframeAspectRatio }, className: "flourish__i-frame" })),
|
|
38
38
|
react_1.default.createElement("figure", { className: figureClassnames, "data-original-image-width": fullGrid ? fallbackImage?.width : null, "data-original-image-height": fullGrid ? fallbackImage?.height : null },
|
|
39
39
|
react_1.default.createElement("a", { href: anchorHref },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/Flourish/index.tsx"],"names":[],"mappings":";;;;;AAAA,kDAAyB;AACzB,4DAAmC;AAqBnC,MAAM,gBAAgB,GAAG,CAAC,EAAE,EAAE,EAAmB,EAAE,EAAE,CAAC,CACpD,uCACE,EAAE,EAAE,EAAE,EACN,SAAS,EAAC,mEAAmE,sBAC5D,WAAW;IAE5B,uCAAK,SAAS,EAAC,sBAAsB;QACnC,uCAAK,SAAS,EAAC,oBAAoB;YACjC,qCAAG,SAAS,EAAC,yBAAyB,kJAIlC,CACA,CACF,CACF,CACP,CAAA;AACD,SAAwB,QAAQ,CAAC,EAC/B,EAAE,EACF,YAAY,EACZ,WAAW,EACX,WAAW,EACX,aAAa,EACb,MAAM,GAAG,KAAK,EACd,aAAa,GAAG,IAAI,GACN;IACd,MAAM,UAAU,GAAG,IAAI,EAAE,EAAE,CAAA;IAC3B,MAAM,QAAQ,GAAG,WAAW,KAAK,WAAW,IAAI,WAAW,KAAK,MAAM,CAAA;IACtE,MAAM,gBAAgB,GAAG,IAAA,oBAAU,EAAC;QAClC,mBAAmB,EAAE,IAAI;QACzB,6BAA6B,EAAE,IAAI;QACnC,yBAAyB,EAAE,QAAQ;QACnC,gBAAgB,EAAE,MAAM;KACzB,CAAC,CAAA;IAEF,MAAM,iBAAiB,GACrB,MAAM,IAAI,aAAa,EAAE,KAAK,IAAI,aAAa,EAAE,MAAM;QACrD,CAAC,CAAC,GAAG,aAAa,CAAC,KAAK,IAAI,aAAa,CAAC,MAAM,EAAE;QAClD,CAAC,CAAC,MAAM,CAAA;IAEZ,MAAM,gBAAgB,GACpB,CAAC,aAAa,IAAI,aAAa,EAAE,KAAK,IAAI,aAAa,EAAE,MAAM;QAC7D,CAAC,CAAC;YACE,KAAK,EAAE,MAAM;YACb,WAAW,EAAE,GAAG,aAAa,CAAC,KAAK,IAAI,aAAa,CAAC,MAAM,EAAE;SAC9D;QACH,CAAC,CAAC,EAAE,CAAA;IAER,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/Flourish/index.tsx"],"names":[],"mappings":";;;;;AAAA,kDAAyB;AACzB,4DAAmC;AAqBnC,MAAM,gBAAgB,GAAG,CAAC,EAAE,EAAE,EAAmB,EAAE,EAAE,CAAC,CACpD,uCACE,EAAE,EAAE,EAAE,EACN,SAAS,EAAC,mEAAmE,sBAC5D,WAAW;IAE5B,uCAAK,SAAS,EAAC,sBAAsB;QACnC,uCAAK,SAAS,EAAC,oBAAoB;YACjC,qCAAG,SAAS,EAAC,yBAAyB,kJAIlC,CACA,CACF,CACF,CACP,CAAA;AACD,SAAwB,QAAQ,CAAC,EAC/B,EAAE,EACF,YAAY,EACZ,WAAW,EACX,WAAW,EACX,aAAa,EACb,MAAM,GAAG,KAAK,EACd,aAAa,GAAG,IAAI,GACN;IACd,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAA;IACpB,MAAM,UAAU,GAAG,IAAI,EAAE,EAAE,CAAA;IAC3B,MAAM,QAAQ,GAAG,WAAW,KAAK,WAAW,IAAI,WAAW,KAAK,MAAM,CAAA;IACtE,MAAM,gBAAgB,GAAG,IAAA,oBAAU,EAAC;QAClC,mBAAmB,EAAE,IAAI;QACzB,6BAA6B,EAAE,IAAI;QACnC,yBAAyB,EAAE,QAAQ;QACnC,gBAAgB,EAAE,MAAM;KACzB,CAAC,CAAA;IAEF,MAAM,iBAAiB,GACrB,MAAM,IAAI,aAAa,EAAE,KAAK,IAAI,aAAa,EAAE,MAAM;QACrD,CAAC,CAAC,GAAG,aAAa,CAAC,KAAK,IAAI,aAAa,CAAC,MAAM,EAAE;QAClD,CAAC,CAAC,MAAM,CAAA;IAEZ,MAAM,gBAAgB,GACpB,CAAC,aAAa,IAAI,aAAa,EAAE,KAAK,IAAI,aAAa,EAAE,MAAM;QAC7D,CAAC,CAAC;YACE,KAAK,EAAE,MAAM;YACb,WAAW,EAAE,GAAG,aAAa,CAAC,KAAK,IAAI,aAAa,CAAC,MAAM,EAAE;SAC9D;QACH,CAAC,CAAC,EAAE,CAAA;IAER,OAAO,CACL,uCACE,SAAS,EAAE,IAAA,oBAAU,EAAC;YACpB,kBAAkB,EAAE,aAAa;YACjC,QAAQ,EAAE,MAAM;YAChB,kBAAkB,EAAE,MAAM;SAC3B,CAAC,uBACiB,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,oBACjC,UAAU,uBACN,EAAE,yBAEnB,aAAa,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,iBAAiB;QAG1D,MAAM,IAAI,CACT,0CACE,GAAG,EAAE,oCAAoC,EAAE,oBAAoB,CAAC,aAAa,EAAE,EAC/E,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,iBAAiB,EAAE,EACxD,SAAS,EAAC,mBAAmB,GACrB,CACX;QACD,0CACE,SAAS,EAAE,gBAAgB,+BACA,QAAQ,CAAC,CAAC,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,gCACrC,QAAQ,CAAC,CAAC,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI;YAEnE,qCAAG,IAAI,EAAE,UAAU;gBACjB,8DAGmB,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,UAAU,sBACrC,EAAE,wBACA,YAAY;oBAEhC,8BAAC,gBAAgB,IAAC,EAAE,EAAE,EAAE,GAAI;oBAC5B,uCACE,GAAG,EAAE,aAAa,EAAE,GAAG,IAAI,EAAE,EAC7B,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,gBAAgB,GACvB,CACM,CACR,CACG,CACL,CACP,CAAA;AACH,CAAC;AA7ED,2BA6EC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TopperTracker = void 0;
|
|
4
|
+
const tracking_1 = require("./tracking");
|
|
5
|
+
Object.defineProperty(exports, "TopperTracker", { enumerable: true, get: function () { return tracking_1.TopperTracker; } });
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/Topper/client/index.ts"],"names":[],"mappings":";;;AAAA,yCAA0C;AAEjC,8FAFA,wBAAa,OAEA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare class TopperTracker {
|
|
2
|
+
private startTime;
|
|
3
|
+
private totalVisibleTime;
|
|
4
|
+
private timeElapsedSeconds;
|
|
5
|
+
private type;
|
|
6
|
+
private component;
|
|
7
|
+
private id;
|
|
8
|
+
private observer;
|
|
9
|
+
constructor({ type }?: {
|
|
10
|
+
type?: string | undefined;
|
|
11
|
+
});
|
|
12
|
+
init(): void;
|
|
13
|
+
private dispatchEvent;
|
|
14
|
+
private onChange;
|
|
15
|
+
disconnect(): void;
|
|
16
|
+
}
|
|
17
|
+
export { TopperTracker };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TopperTracker = void 0;
|
|
4
|
+
class TopperTracker {
|
|
5
|
+
constructor({ type = 'flourish-topper' } = {}) {
|
|
6
|
+
this.startTime = 0;
|
|
7
|
+
this.totalVisibleTime = 0;
|
|
8
|
+
this.timeElapsedSeconds = null;
|
|
9
|
+
this.type = type;
|
|
10
|
+
this.component = document.querySelector(`[data-component-type="${this.type}"]`);
|
|
11
|
+
this.id = this.component?.getAttribute('data-component-id') || null;
|
|
12
|
+
this.observer = null;
|
|
13
|
+
}
|
|
14
|
+
init() {
|
|
15
|
+
if (!window.IntersectionObserver || !this.component) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
this.dispatchEvent('mount');
|
|
19
|
+
this.observer = new IntersectionObserver(this.onChange.bind(this), {
|
|
20
|
+
threshold: [0.75],
|
|
21
|
+
});
|
|
22
|
+
this.observer.observe(this.component);
|
|
23
|
+
}
|
|
24
|
+
dispatchEvent(action) {
|
|
25
|
+
const component = {
|
|
26
|
+
name: this.type,
|
|
27
|
+
id: this.id,
|
|
28
|
+
};
|
|
29
|
+
if (this.timeElapsedSeconds) {
|
|
30
|
+
component.timeElapsedSeconds = this.timeElapsedSeconds;
|
|
31
|
+
}
|
|
32
|
+
const event = new CustomEvent('oTracking.event', {
|
|
33
|
+
detail: {
|
|
34
|
+
category: 'component',
|
|
35
|
+
action: action,
|
|
36
|
+
component,
|
|
37
|
+
},
|
|
38
|
+
bubbles: true,
|
|
39
|
+
});
|
|
40
|
+
document.body.dispatchEvent(event);
|
|
41
|
+
}
|
|
42
|
+
onChange(changes) {
|
|
43
|
+
changes.forEach((change) => {
|
|
44
|
+
if (change.target !== this.component) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (change.isIntersecting || change.intersectionRatio >= 1) {
|
|
48
|
+
this.dispatchEvent('view');
|
|
49
|
+
this.startTime = performance.now();
|
|
50
|
+
}
|
|
51
|
+
if (!change.isIntersecting || change.intersectionRatio === 0) {
|
|
52
|
+
this.totalVisibleTime = performance.now() - this.startTime;
|
|
53
|
+
this.timeElapsedSeconds = parseFloat((this.totalVisibleTime / 1000).toFixed(2));
|
|
54
|
+
this.dispatchEvent('stop-view');
|
|
55
|
+
this.totalVisibleTime = 0;
|
|
56
|
+
this.timeElapsedSeconds = null;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
disconnect() {
|
|
61
|
+
if (this.observer && this.component) {
|
|
62
|
+
this.observer.unobserve(this.component);
|
|
63
|
+
this.observer.disconnect();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.TopperTracker = TopperTracker;
|
|
68
|
+
//# sourceMappingURL=tracking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracking.js","sourceRoot":"","sources":["../../../../src/components/Topper/client/tracking.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("./index");
|
|
4
|
+
describe('TopperTracker', () => {
|
|
5
|
+
let mockIntersectionObserver;
|
|
6
|
+
let mockObserve;
|
|
7
|
+
let mockUnobserve;
|
|
8
|
+
let mockDisconnect;
|
|
9
|
+
let mockComponent;
|
|
10
|
+
let dispatchEventSpy;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
mockObserve = jest.fn();
|
|
13
|
+
mockUnobserve = jest.fn();
|
|
14
|
+
mockDisconnect = jest.fn();
|
|
15
|
+
mockIntersectionObserver = jest.fn(() => {
|
|
16
|
+
return {
|
|
17
|
+
observe: mockObserve,
|
|
18
|
+
unobserve: mockUnobserve,
|
|
19
|
+
disconnect: mockDisconnect,
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
window.IntersectionObserver = mockIntersectionObserver;
|
|
23
|
+
mockComponent = document.createElement('div');
|
|
24
|
+
mockComponent.setAttribute('data-component-type', 'flourish-topper');
|
|
25
|
+
mockComponent.setAttribute('data-component-id', 'test-id');
|
|
26
|
+
document.body.appendChild(mockComponent);
|
|
27
|
+
jest.spyOn(document, 'querySelector').mockReturnValue(mockComponent);
|
|
28
|
+
jest
|
|
29
|
+
.spyOn(performance, 'now')
|
|
30
|
+
.mockImplementationOnce(() => 1)
|
|
31
|
+
.mockImplementationOnce(() => 12344);
|
|
32
|
+
dispatchEventSpy = jest.spyOn(document.body, 'dispatchEvent');
|
|
33
|
+
});
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
jest.clearAllMocks();
|
|
36
|
+
document.body.removeChild(mockComponent);
|
|
37
|
+
});
|
|
38
|
+
it('should initialise and observe the component', () => {
|
|
39
|
+
const tracker = new index_1.TopperTracker();
|
|
40
|
+
tracker.init();
|
|
41
|
+
expect(mockIntersectionObserver).toHaveBeenCalled();
|
|
42
|
+
expect(mockObserve).toHaveBeenCalledWith(mockComponent);
|
|
43
|
+
});
|
|
44
|
+
it('should dispatch a mount event on intialisation', () => {
|
|
45
|
+
const tracker = new index_1.TopperTracker();
|
|
46
|
+
tracker.init();
|
|
47
|
+
const mountEvent = dispatchEventSpy.mock.calls[0][0];
|
|
48
|
+
expect(mountEvent.type).toBe('oTracking.event');
|
|
49
|
+
expect(mountEvent.detail).toEqual({
|
|
50
|
+
category: 'component',
|
|
51
|
+
action: 'mount',
|
|
52
|
+
component: {
|
|
53
|
+
name: 'flourish-topper',
|
|
54
|
+
id: 'test-id',
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
it('should handle component visibility changes', () => {
|
|
59
|
+
const tracker = new index_1.TopperTracker();
|
|
60
|
+
tracker.init();
|
|
61
|
+
const mockChanges = [
|
|
62
|
+
{
|
|
63
|
+
target: mockComponent,
|
|
64
|
+
isIntersecting: true,
|
|
65
|
+
intersectionRatio: 1.0,
|
|
66
|
+
boundingClientRect: {},
|
|
67
|
+
intersectionRect: {},
|
|
68
|
+
rootBounds: null,
|
|
69
|
+
time: 1,
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
tracker['onChange'](mockChanges);
|
|
73
|
+
const viewEvent = dispatchEventSpy.mock.calls[1][0];
|
|
74
|
+
expect(viewEvent.type).toBe('oTracking.event');
|
|
75
|
+
expect(viewEvent.detail).toEqual({
|
|
76
|
+
category: 'component',
|
|
77
|
+
action: 'view',
|
|
78
|
+
component: {
|
|
79
|
+
name: 'flourish-topper',
|
|
80
|
+
id: 'test-id',
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
const mockChangesAfter = [
|
|
84
|
+
{
|
|
85
|
+
target: mockComponent,
|
|
86
|
+
isIntersecting: false,
|
|
87
|
+
intersectionRatio: 0.0,
|
|
88
|
+
boundingClientRect: {},
|
|
89
|
+
intersectionRect: {},
|
|
90
|
+
rootBounds: null,
|
|
91
|
+
time: 12.3444,
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
tracker['onChange'](mockChangesAfter);
|
|
95
|
+
const stopViewEvent = dispatchEventSpy.mock.calls[2][0];
|
|
96
|
+
expect(stopViewEvent.type).toBe('oTracking.event');
|
|
97
|
+
expect(stopViewEvent.detail).toEqual({
|
|
98
|
+
category: 'component',
|
|
99
|
+
action: 'stop-view',
|
|
100
|
+
component: {
|
|
101
|
+
name: 'flourish-topper',
|
|
102
|
+
id: 'test-id',
|
|
103
|
+
timeElapsedSeconds: 12.34,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
it('should disconnect the observer', () => {
|
|
108
|
+
const tracker = new index_1.TopperTracker();
|
|
109
|
+
tracker.init();
|
|
110
|
+
tracker.disconnect();
|
|
111
|
+
expect(mockUnobserve).toHaveBeenCalledWith(mockComponent);
|
|
112
|
+
expect(mockDisconnect).toHaveBeenCalled();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
//# sourceMappingURL=tracking.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracking.spec.js","sourceRoot":"","sources":["../../../../src/components/Topper/client/tracking.spec.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { default as Clip } from './components/Clip/client/index'
|
|
2
2
|
export { default as BackToTopButton } from './components/BackToTopButton/client'
|
|
3
3
|
export { default as FlourishSSREmbed } from './components/Flourish/client/index'
|
|
4
|
+
export { TopperTracker } from './components/Topper/client/index'
|
|
@@ -46,6 +46,7 @@ export default function Flourish({
|
|
|
46
46
|
iFrame = false,
|
|
47
47
|
inArticleBody = true,
|
|
48
48
|
}: FlourishProps) {
|
|
49
|
+
if (!id) return null
|
|
49
50
|
const anchorHref = `#${id}`
|
|
50
51
|
const fullGrid = layoutWidth === 'full-grid' || layoutWidth === 'grid'
|
|
51
52
|
const figureClassnames = classnames({
|
|
@@ -68,8 +69,6 @@ export default function Flourish({
|
|
|
68
69
|
}
|
|
69
70
|
: {}
|
|
70
71
|
|
|
71
|
-
if (!id) return null
|
|
72
|
-
|
|
73
72
|
return (
|
|
74
73
|
<div
|
|
75
74
|
className={classnames({
|
|
@@ -79,6 +78,10 @@ export default function Flourish({
|
|
|
79
78
|
})}
|
|
80
79
|
data-layout-width={fullGrid ? 'full-grid' : null}
|
|
81
80
|
data-component="flourish"
|
|
81
|
+
data-component-id={id}
|
|
82
|
+
data-component-type={
|
|
83
|
+
inArticleBody ? 'flourish-in-article' : 'flourish-topper'
|
|
84
|
+
}
|
|
82
85
|
>
|
|
83
86
|
{iFrame && (
|
|
84
87
|
<iframe
|
|
@@ -5,6 +5,8 @@ exports[`Flourish component renders ignoring layout different from full-grid 1`]
|
|
|
5
5
|
<div
|
|
6
6
|
class="n-content-layout"
|
|
7
7
|
data-component="flourish"
|
|
8
|
+
data-component-id="123"
|
|
9
|
+
data-component-type="flourish-in-article"
|
|
8
10
|
>
|
|
9
11
|
<figure
|
|
10
12
|
class="n-content-picture n-content-layout__container"
|
|
@@ -52,6 +54,8 @@ exports[`Flourish component renders with a full-grid layout and fallback image 1
|
|
|
52
54
|
<div
|
|
53
55
|
class="n-content-layout"
|
|
54
56
|
data-component="flourish"
|
|
57
|
+
data-component-id="123"
|
|
58
|
+
data-component-type="flourish-in-article"
|
|
55
59
|
data-layout-width="full-grid"
|
|
56
60
|
>
|
|
57
61
|
<figure
|
|
@@ -102,6 +106,8 @@ exports[`Flourish component renders with an iframe rather than using the embed s
|
|
|
102
106
|
<div
|
|
103
107
|
class="n-content-layout flourish flourish--iFrame"
|
|
104
108
|
data-component="flourish"
|
|
109
|
+
data-component-id="123"
|
|
110
|
+
data-component-type="flourish-in-article"
|
|
105
111
|
data-layout-width="full-grid"
|
|
106
112
|
>
|
|
107
113
|
<iframe
|
|
@@ -157,6 +163,8 @@ exports[`Flourish component renders without n-content-layout class for charts no
|
|
|
157
163
|
<div
|
|
158
164
|
class=""
|
|
159
165
|
data-component="flourish"
|
|
166
|
+
data-component-id="123"
|
|
167
|
+
data-component-type="flourish-topper"
|
|
160
168
|
data-layout-width="full-grid"
|
|
161
169
|
>
|
|
162
170
|
<figure
|
|
@@ -208,6 +216,8 @@ exports[`does not set aspect ratios when in article body 1`] = `
|
|
|
208
216
|
<div
|
|
209
217
|
class="n-content-layout"
|
|
210
218
|
data-component="flourish"
|
|
219
|
+
data-component-id="123"
|
|
220
|
+
data-component-type="flourish-in-article"
|
|
211
221
|
data-layout-width="full-grid"
|
|
212
222
|
>
|
|
213
223
|
<figure
|
|
@@ -258,6 +268,8 @@ exports[`sets the hideTitle param in the iframe url to true when not in article
|
|
|
258
268
|
<div
|
|
259
269
|
class="flourish flourish--iFrame"
|
|
260
270
|
data-component="flourish"
|
|
271
|
+
data-component-id="123"
|
|
272
|
+
data-component-type="flourish-topper"
|
|
261
273
|
data-layout-width="full-grid"
|
|
262
274
|
>
|
|
263
275
|
<iframe
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { TopperTracker } from './index'
|
|
2
|
+
|
|
3
|
+
describe('TopperTracker', () => {
|
|
4
|
+
let mockIntersectionObserver: jest.Mock
|
|
5
|
+
let mockObserve: jest.Mock
|
|
6
|
+
let mockUnobserve: jest.Mock
|
|
7
|
+
let mockDisconnect: jest.Mock
|
|
8
|
+
let mockComponent: HTMLElement
|
|
9
|
+
let dispatchEventSpy: jest.SpyInstance
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
mockObserve = jest.fn()
|
|
13
|
+
mockUnobserve = jest.fn()
|
|
14
|
+
mockDisconnect = jest.fn()
|
|
15
|
+
|
|
16
|
+
mockIntersectionObserver = jest.fn(() => {
|
|
17
|
+
return {
|
|
18
|
+
observe: mockObserve,
|
|
19
|
+
unobserve: mockUnobserve,
|
|
20
|
+
disconnect: mockDisconnect,
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
window.IntersectionObserver = mockIntersectionObserver
|
|
25
|
+
|
|
26
|
+
mockComponent = document.createElement('div')
|
|
27
|
+
mockComponent.setAttribute('data-component-type', 'flourish-topper')
|
|
28
|
+
mockComponent.setAttribute('data-component-id', 'test-id')
|
|
29
|
+
document.body.appendChild(mockComponent)
|
|
30
|
+
|
|
31
|
+
jest.spyOn(document, 'querySelector').mockReturnValue(mockComponent)
|
|
32
|
+
jest
|
|
33
|
+
.spyOn(performance, 'now')
|
|
34
|
+
.mockImplementationOnce(() => 1)
|
|
35
|
+
.mockImplementationOnce(() => 12344)
|
|
36
|
+
dispatchEventSpy = jest.spyOn(document.body, 'dispatchEvent')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
jest.clearAllMocks()
|
|
41
|
+
document.body.removeChild(mockComponent)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('should initialise and observe the component', () => {
|
|
45
|
+
const tracker = new TopperTracker()
|
|
46
|
+
tracker.init()
|
|
47
|
+
|
|
48
|
+
expect(mockIntersectionObserver).toHaveBeenCalled()
|
|
49
|
+
expect(mockObserve).toHaveBeenCalledWith(mockComponent)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should dispatch a mount event on intialisation', () => {
|
|
53
|
+
const tracker = new TopperTracker()
|
|
54
|
+
tracker.init()
|
|
55
|
+
|
|
56
|
+
const mountEvent = dispatchEventSpy.mock.calls[0][0] as CustomEvent
|
|
57
|
+
expect(mountEvent.type).toBe('oTracking.event')
|
|
58
|
+
expect(mountEvent.detail).toEqual({
|
|
59
|
+
category: 'component',
|
|
60
|
+
action: 'mount',
|
|
61
|
+
component: {
|
|
62
|
+
name: 'flourish-topper',
|
|
63
|
+
id: 'test-id',
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('should handle component visibility changes', () => {
|
|
69
|
+
const tracker = new TopperTracker()
|
|
70
|
+
tracker.init()
|
|
71
|
+
|
|
72
|
+
const mockChanges: IntersectionObserverEntry[] = [
|
|
73
|
+
{
|
|
74
|
+
target: mockComponent,
|
|
75
|
+
isIntersecting: true,
|
|
76
|
+
intersectionRatio: 1.0,
|
|
77
|
+
boundingClientRect: {} as DOMRectReadOnly,
|
|
78
|
+
intersectionRect: {} as DOMRectReadOnly,
|
|
79
|
+
rootBounds: null,
|
|
80
|
+
time: 1,
|
|
81
|
+
},
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
tracker['onChange'](mockChanges)
|
|
85
|
+
|
|
86
|
+
const viewEvent = dispatchEventSpy.mock.calls[1][0] as CustomEvent
|
|
87
|
+
expect(viewEvent.type).toBe('oTracking.event')
|
|
88
|
+
expect(viewEvent.detail).toEqual({
|
|
89
|
+
category: 'component',
|
|
90
|
+
action: 'view',
|
|
91
|
+
component: {
|
|
92
|
+
name: 'flourish-topper',
|
|
93
|
+
id: 'test-id',
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const mockChangesAfter: IntersectionObserverEntry[] = [
|
|
98
|
+
{
|
|
99
|
+
target: mockComponent,
|
|
100
|
+
isIntersecting: false,
|
|
101
|
+
intersectionRatio: 0.0,
|
|
102
|
+
boundingClientRect: {} as DOMRectReadOnly,
|
|
103
|
+
intersectionRect: {} as DOMRectReadOnly,
|
|
104
|
+
rootBounds: null,
|
|
105
|
+
time: 12.3444,
|
|
106
|
+
},
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
tracker['onChange'](mockChangesAfter)
|
|
110
|
+
|
|
111
|
+
const stopViewEvent = dispatchEventSpy.mock.calls[2][0] as CustomEvent
|
|
112
|
+
expect(stopViewEvent.type).toBe('oTracking.event')
|
|
113
|
+
expect(stopViewEvent.detail).toEqual({
|
|
114
|
+
category: 'component',
|
|
115
|
+
action: 'stop-view',
|
|
116
|
+
component: {
|
|
117
|
+
name: 'flourish-topper',
|
|
118
|
+
id: 'test-id',
|
|
119
|
+
timeElapsedSeconds: 12.34,
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('should disconnect the observer', () => {
|
|
125
|
+
const tracker = new TopperTracker()
|
|
126
|
+
tracker.init()
|
|
127
|
+
tracker.disconnect()
|
|
128
|
+
|
|
129
|
+
expect(mockUnobserve).toHaveBeenCalledWith(mockComponent)
|
|
130
|
+
expect(mockDisconnect).toHaveBeenCalled()
|
|
131
|
+
})
|
|
132
|
+
})
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
interface Component {
|
|
2
|
+
name: string
|
|
3
|
+
id: string | null
|
|
4
|
+
timeElapsedSeconds?: number
|
|
5
|
+
}
|
|
6
|
+
class TopperTracker {
|
|
7
|
+
private startTime: number
|
|
8
|
+
private totalVisibleTime: number
|
|
9
|
+
private timeElapsedSeconds: number | null
|
|
10
|
+
private type: string
|
|
11
|
+
private component: Element | null
|
|
12
|
+
private id: string | null
|
|
13
|
+
private observer: IntersectionObserver | null
|
|
14
|
+
|
|
15
|
+
constructor({ type = 'flourish-topper' } = {}) {
|
|
16
|
+
this.startTime = 0
|
|
17
|
+
this.totalVisibleTime = 0
|
|
18
|
+
this.timeElapsedSeconds = null
|
|
19
|
+
this.type = type
|
|
20
|
+
this.component = document.querySelector(
|
|
21
|
+
`[data-component-type="${this.type}"]`
|
|
22
|
+
)
|
|
23
|
+
this.id = this.component?.getAttribute('data-component-id') || null
|
|
24
|
+
this.observer = null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
init(): void {
|
|
28
|
+
if (!window.IntersectionObserver || !this.component) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.dispatchEvent('mount')
|
|
33
|
+
|
|
34
|
+
this.observer = new IntersectionObserver(this.onChange.bind(this), {
|
|
35
|
+
threshold: [0.75],
|
|
36
|
+
})
|
|
37
|
+
this.observer.observe(this.component)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private dispatchEvent(action: string): void {
|
|
41
|
+
const component: Component = {
|
|
42
|
+
name: this.type,
|
|
43
|
+
id: this.id,
|
|
44
|
+
}
|
|
45
|
+
if (this.timeElapsedSeconds) {
|
|
46
|
+
component.timeElapsedSeconds = this.timeElapsedSeconds
|
|
47
|
+
}
|
|
48
|
+
const event = new CustomEvent('oTracking.event', {
|
|
49
|
+
detail: {
|
|
50
|
+
category: 'component',
|
|
51
|
+
action: action,
|
|
52
|
+
component,
|
|
53
|
+
},
|
|
54
|
+
bubbles: true,
|
|
55
|
+
})
|
|
56
|
+
document.body.dispatchEvent(event)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private onChange(changes: IntersectionObserverEntry[]): void {
|
|
60
|
+
changes.forEach((change) => {
|
|
61
|
+
if (change.target !== this.component) {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
if (change.isIntersecting || change.intersectionRatio >= 1) {
|
|
65
|
+
this.dispatchEvent('view')
|
|
66
|
+
this.startTime = performance.now()
|
|
67
|
+
}
|
|
68
|
+
if (!change.isIntersecting || change.intersectionRatio === 0) {
|
|
69
|
+
this.totalVisibleTime = performance.now() - this.startTime
|
|
70
|
+
this.timeElapsedSeconds = parseFloat(
|
|
71
|
+
(this.totalVisibleTime / 1000).toFixed(2)
|
|
72
|
+
)
|
|
73
|
+
this.dispatchEvent('stop-view')
|
|
74
|
+
this.totalVisibleTime = 0
|
|
75
|
+
this.timeElapsedSeconds = null
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
disconnect(): void {
|
|
81
|
+
if (this.observer && this.component) {
|
|
82
|
+
this.observer.unobserve(this.component)
|
|
83
|
+
this.observer.disconnect()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { TopperTracker }
|