@atlaskit/analytics-cross-product 1.0.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 +1 -0
- package/LICENSE.md +11 -0
- package/README.md +111 -0
- package/dist/cjs/generateUrlWithParams.js +32 -0
- package/dist/cjs/globalInteractionSessionTracking.js +31 -0
- package/dist/cjs/index.js +27 -0
- package/dist/cjs/useCrossProductUrlWrapper.js +81 -0
- package/dist/es2019/generateUrlWithParams.js +26 -0
- package/dist/es2019/globalInteractionSessionTracking.js +16 -0
- package/dist/es2019/index.js +3 -0
- package/dist/es2019/useCrossProductUrlWrapper.js +67 -0
- package/dist/esm/generateUrlWithParams.js +26 -0
- package/dist/esm/globalInteractionSessionTracking.js +25 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/useCrossProductUrlWrapper.js +73 -0
- package/dist/types/generateUrlWithParams.d.ts +2 -0
- package/dist/types/globalInteractionSessionTracking.d.ts +7 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/useCrossProductUrlWrapper.d.ts +20 -0
- package/dist/types-ts4.5/generateUrlWithParams.d.ts +2 -0
- package/dist/types-ts4.5/globalInteractionSessionTracking.d.ts +7 -0
- package/dist/types-ts4.5/index.d.ts +3 -0
- package/dist/types-ts4.5/useCrossProductUrlWrapper.d.ts +20 -0
- package/package.json +90 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# @atlaskit/analytics-cross-product
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Copyright 2023 Atlassian Pty Ltd
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
|
|
4
|
+
compliance with the License. You may obtain a copy of the License at
|
|
5
|
+
|
|
6
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
|
|
8
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is
|
|
9
|
+
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
10
|
+
implied. See the License for the specific language governing permissions and limitations under the
|
|
11
|
+
License.
|
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# AnalyticsCrossProduct
|
|
2
|
+
|
|
3
|
+
Utilities to enable cross-product interaction session tracking
|
|
4
|
+
|
|
5
|
+
This repository is the public facing version of
|
|
6
|
+
`@atlassiansox/analytics-cross-product-interaction-client`. Functionality has been duplicated from
|
|
7
|
+
the private package to enable `@atlaskit` components to use our library, with the eventual goal of
|
|
8
|
+
deprecating these functions in the existing package to avoid code duplication.
|
|
9
|
+
|
|
10
|
+
The functions in this library are designed to work in conjunction with `analytics-web-client` and
|
|
11
|
+
`analytics-cross-product-interaction-client` on Atlassian products, and using these functions on
|
|
12
|
+
their own will return a no-op.
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
The Analytics Web Client generates interaction session data and stores it in Session Storage. The
|
|
17
|
+
data is in the following shape:
|
|
18
|
+
|
|
19
|
+
```Json
|
|
20
|
+
"interactionSession": {
|
|
21
|
+
"id": "123456789",
|
|
22
|
+
"prevId": "912345678",
|
|
23
|
+
"bridge": "atlassianSwitcher",
|
|
24
|
+
"source": "jira"
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This package exposes: `useCrossProductUrlWrapper`, a React hook that can be used to retrieve
|
|
29
|
+
interaction session data and append them as URL parameters onto cross-product navigation URLs
|
|
30
|
+
|
|
31
|
+
## Usage within an Atlassian bridge
|
|
32
|
+
|
|
33
|
+
A bridge is a component that provides a link/navigation to a different Atlassian page, for example:
|
|
34
|
+
|
|
35
|
+
```TypeScript
|
|
36
|
+
return <Link href="https://hello.atlassian.net/wiki"> Confluence </Link>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
To embed the interaction session properties into the link as a UTM parameter, call our hook
|
|
40
|
+
`useCrossProductUrlWrapper` which generates a function to wrap the link with.
|
|
41
|
+
|
|
42
|
+
Then replace any instances of URLs with the URL wrapped in the hook
|
|
43
|
+
|
|
44
|
+
```TypeScript
|
|
45
|
+
import { useCrossProductUrlWrapper } from '@atlaskit/analytics-cross-product';
|
|
46
|
+
|
|
47
|
+
const withInteractionSession = useCrossProductUrlWrapper({
|
|
48
|
+
bridge: 'Bridge Name', // The name of your bridge component e.g. atlassianSwitcher
|
|
49
|
+
product: 'Product Name', // The product you are hosted on e.g. jira
|
|
50
|
+
subProduct: 'SubProduct Name', // Optional - include if required
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Link href={withInteractionSession("https://hello.atlassian.net/wiki")}> Confluence </Link>
|
|
55
|
+
)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The `useCrossProductUrlWrapper` hook takes in options of type `CrossProductUrlOptions` which can be
|
|
59
|
+
imported from our library if required
|
|
60
|
+
|
|
61
|
+
The returned wrapper function (`withInteractionSession` in the above example) takes a URL as a
|
|
62
|
+
string, and returns a string as well. The return value of the above call would be something like:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
https://hello.atlassian.net/wiki?xpis=e2JyaWRnZToiQnJpZGdlIE5hbWUiLGlkOiIxMjM0NTY3ODkiLHNvdXJjZToiUHJvZHVjdCBOYW1lLVN1YlByb2R1Y3QgTmFtZSJ9
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
with the interaction session data encoded in the `?xpis` query parameter (cross-product interaction
|
|
69
|
+
session)
|
|
70
|
+
|
|
71
|
+
See our [example](./examples/basic.tsx). To run the example locally:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
yarn start @atlaskit/analytics-cross-product
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Parameter generation and re-generation
|
|
78
|
+
|
|
79
|
+
Interaction session data will only be appended to the URL if it has already been generated by the
|
|
80
|
+
Analytics Web Client and exists in Session Storage. If interaction session data cannot be found, the
|
|
81
|
+
hook will not modify the URL. The hook is subscribed to a DOM event that will trigger a re-render if
|
|
82
|
+
a new interaciton session is generated, and will dynamically update the URL to include the correct
|
|
83
|
+
parameters
|
|
84
|
+
|
|
85
|
+
### Relative URLs
|
|
86
|
+
|
|
87
|
+
The wrapper supports relative URLs, for example:
|
|
88
|
+
|
|
89
|
+
```TypeScript
|
|
90
|
+
withInteractionSession("/wiki")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
will return
|
|
94
|
+
|
|
95
|
+
```TypeScript
|
|
96
|
+
"/wiki?xpis=e2Jya..."
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
We use the `URL` class to perform operations. Note:
|
|
100
|
+
|
|
101
|
+
- The URL must not have a trailing slash (i.e. use `/wiki` instead of `/wiki/`)
|
|
102
|
+
- A leading slash will be added to the return output (i.e. `wiki` will become `/wiki?xpis=...`)
|
|
103
|
+
- Existing query parameters will be preserved (`/wiki?a=b` becomes `/wiki?a=b&xpis=e2Jya...`)
|
|
104
|
+
|
|
105
|
+
### Usage with Identity
|
|
106
|
+
|
|
107
|
+
When a link is wrapped in Identity (passed as the `continue` paramater in the redirect), Identity
|
|
108
|
+
encodes query parameters using percent encoding (i.e. `&key=value` becomes `%26key%3Dvalue`).
|
|
109
|
+
|
|
110
|
+
Ensure that our wrapper is called on the `href` _before_ passing into identity. This will ensure our
|
|
111
|
+
query parameters are also correctly percent encoded, and will persist after Identity redirect
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.generateUrlWithParams = exports.URL_QUERY_PARAM_INTERACTION_SESSION = void 0;
|
|
7
|
+
var URL_QUERY_PARAM_INTERACTION_SESSION = exports.URL_QUERY_PARAM_INTERACTION_SESSION = 'xpis';
|
|
8
|
+
var generateUrlWithParams = exports.generateUrlWithParams = function generateUrlWithParams(url, bridge, interactionSessionId, product, subProduct) {
|
|
9
|
+
var interactionSource = subProduct ? "".concat(product, "-").concat(subProduct) : product;
|
|
10
|
+
var interactionSession = {
|
|
11
|
+
bridge: bridge,
|
|
12
|
+
id: interactionSessionId,
|
|
13
|
+
source: interactionSource
|
|
14
|
+
};
|
|
15
|
+
var paramValueEncoded = btoa(JSON.stringify(interactionSession));
|
|
16
|
+
var partialUrl = false;
|
|
17
|
+
var urlObj;
|
|
18
|
+
|
|
19
|
+
// If relative URL provided, add localhost as placeholder base, to be stripped later
|
|
20
|
+
try {
|
|
21
|
+
urlObj = new URL(url);
|
|
22
|
+
} catch (_unused) {
|
|
23
|
+
urlObj = new URL(url, 'http://localhost/');
|
|
24
|
+
partialUrl = true;
|
|
25
|
+
}
|
|
26
|
+
urlObj.searchParams.set(URL_QUERY_PARAM_INTERACTION_SESSION, paramValueEncoded);
|
|
27
|
+
if (partialUrl) {
|
|
28
|
+
return urlObj.pathname + urlObj.search;
|
|
29
|
+
} else {
|
|
30
|
+
return urlObj.toString();
|
|
31
|
+
}
|
|
32
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.default = exports.INTERNAL_CLIENT_WINDOW_KEY = void 0;
|
|
8
|
+
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
9
|
+
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
10
|
+
var INTERNAL_CLIENT_WINDOW_KEY = exports.INTERNAL_CLIENT_WINDOW_KEY = 'analyticsInteractionSesssionTrackingClient';
|
|
11
|
+
var GlobalInteractionSessionTracking = exports.default = /*#__PURE__*/function () {
|
|
12
|
+
function GlobalInteractionSessionTracking() {
|
|
13
|
+
(0, _classCallCheck2.default)(this, GlobalInteractionSessionTracking);
|
|
14
|
+
}
|
|
15
|
+
return (0, _createClass2.default)(GlobalInteractionSessionTracking, null, [{
|
|
16
|
+
key: "getInstance",
|
|
17
|
+
value: function getInstance() {
|
|
18
|
+
var interactionSessionTracking;
|
|
19
|
+
try {
|
|
20
|
+
// Check if InteractionSessionTracking in global window object
|
|
21
|
+
if (INTERNAL_CLIENT_WINDOW_KEY in window) {
|
|
22
|
+
interactionSessionTracking = window[INTERNAL_CLIENT_WINDOW_KEY];
|
|
23
|
+
}
|
|
24
|
+
} catch (error) {
|
|
25
|
+
// eslint-disable-next-line no-console
|
|
26
|
+
console.error("Error fetching InteractionSessionTracking from window - ".concat(error instanceof Error ? error.message : String(error)));
|
|
27
|
+
}
|
|
28
|
+
return interactionSessionTracking;
|
|
29
|
+
}
|
|
30
|
+
}]);
|
|
31
|
+
}();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _typeof = require("@babel/runtime/helpers/typeof");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
Object.defineProperty(exports, "INTERACTION_SESSION_ID_UPDATED_EVENT", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
get: function get() {
|
|
10
|
+
return _useCrossProductUrlWrapper.INTERACTION_SESSION_ID_UPDATED_EVENT;
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
Object.defineProperty(exports, "INTERNAL_CLIENT_WINDOW_KEY", {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: function get() {
|
|
16
|
+
return _globalInteractionSessionTracking.INTERNAL_CLIENT_WINDOW_KEY;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports, "useCrossProductUrlWrapper", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function get() {
|
|
22
|
+
return _useCrossProductUrlWrapper.default;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
var _globalInteractionSessionTracking = require("./globalInteractionSessionTracking");
|
|
26
|
+
var _useCrossProductUrlWrapper = _interopRequireWildcard(require("./useCrossProductUrlWrapper"));
|
|
27
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.INTERACTION_SESSION_ID_UPDATED_EVENT = void 0;
|
|
8
|
+
exports.default = useCrossProductUrlWrapper;
|
|
9
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
10
|
+
var _react = require("react");
|
|
11
|
+
var _bindEventListener = require("bind-event-listener");
|
|
12
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
13
|
+
var _generateUrlWithParams = require("./generateUrlWithParams");
|
|
14
|
+
var _globalInteractionSessionTracking = _interopRequireDefault(require("./globalInteractionSessionTracking"));
|
|
15
|
+
var INTERACTION_SESSION_ID_UPDATED_EVENT = exports.INTERACTION_SESSION_ID_UPDATED_EVENT = 'interactionSessionIdUpdated';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param bridge - The name of your navigation component e.g. atlassianSwitcher
|
|
19
|
+
* @param product - The product you are hosted on e.g. jira
|
|
20
|
+
* @param subProduct - If specified, will be appended to the end of product as: product-subProduct
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* This React hook is called with the correct bridge, product and sub-product parameters.
|
|
25
|
+
* It returns a function that can be used to generate URLs with cross-product interaction parameters.
|
|
26
|
+
*
|
|
27
|
+
* @param options - Properties object that contains the following parameters:
|
|
28
|
+
*
|
|
29
|
+
* @returns A function that appends interaction session ID and other cross-product interaction parameters to a given URL.
|
|
30
|
+
*/
|
|
31
|
+
function useCrossProductUrlWrapper(options) {
|
|
32
|
+
var _useState = (0, _react.useState)(''),
|
|
33
|
+
_useState2 = (0, _slicedToArray2.default)(_useState, 2),
|
|
34
|
+
interactionSessionId = _useState2[0],
|
|
35
|
+
setInteractionSessionId = _useState2[1];
|
|
36
|
+
var bridge = options.bridge,
|
|
37
|
+
product = options.product,
|
|
38
|
+
subProduct = options.subProduct;
|
|
39
|
+
var isEnabled = (0, _platformFeatureFlags.fg)('atlaskit-analytics-cross-product');
|
|
40
|
+
var interactionSessionClient;
|
|
41
|
+
(0, _react.useEffect)(function () {
|
|
42
|
+
if (isEnabled) {
|
|
43
|
+
// Fetch interaction session client from window object
|
|
44
|
+
interactionSessionClient = _globalInteractionSessionTracking.default.getInstance();
|
|
45
|
+
|
|
46
|
+
// Fetch any initial interaction session ID
|
|
47
|
+
if (interactionSessionClient) {
|
|
48
|
+
var currentSessionId = interactionSessionClient.getCurrentInteractionSessionId();
|
|
49
|
+
currentSessionId && setInteractionSessionId(currentSessionId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Add event listener that subscribes to any future interaction session ID updates
|
|
53
|
+
var unbind = (0, _bindEventListener.bind)(document, {
|
|
54
|
+
type: INTERACTION_SESSION_ID_UPDATED_EVENT,
|
|
55
|
+
listener: function listener() {
|
|
56
|
+
// Re-attempt fetch Global interactionSessionClient if not present already
|
|
57
|
+
if (!interactionSessionClient) {
|
|
58
|
+
interactionSessionClient = _globalInteractionSessionTracking.default.getInstance();
|
|
59
|
+
}
|
|
60
|
+
if (interactionSessionClient) {
|
|
61
|
+
var _currentSessionId = interactionSessionClient.getCurrentInteractionSessionId();
|
|
62
|
+
_currentSessionId && setInteractionSessionId(_currentSessionId);
|
|
63
|
+
} else {
|
|
64
|
+
// eslint-disable-next-line no-console
|
|
65
|
+
console.error("INTERACTION_SESSION_ID_UPDATED_EVENT received but interactionSessionClient not present on window");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return unbind;
|
|
70
|
+
}
|
|
71
|
+
return function () {};
|
|
72
|
+
}, [interactionSessionClient, isEnabled]);
|
|
73
|
+
if (!isEnabled || !interactionSessionId) {
|
|
74
|
+
return function (url) {
|
|
75
|
+
return url;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return function (url) {
|
|
79
|
+
return (0, _generateUrlWithParams.generateUrlWithParams)(url, bridge, interactionSessionId, product, subProduct);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const URL_QUERY_PARAM_INTERACTION_SESSION = 'xpis';
|
|
2
|
+
export const generateUrlWithParams = (url, bridge, interactionSessionId, product, subProduct) => {
|
|
3
|
+
const interactionSource = subProduct ? `${product}-${subProduct}` : product;
|
|
4
|
+
const interactionSession = {
|
|
5
|
+
bridge: bridge,
|
|
6
|
+
id: interactionSessionId,
|
|
7
|
+
source: interactionSource
|
|
8
|
+
};
|
|
9
|
+
const paramValueEncoded = btoa(JSON.stringify(interactionSession));
|
|
10
|
+
let partialUrl = false;
|
|
11
|
+
let urlObj;
|
|
12
|
+
|
|
13
|
+
// If relative URL provided, add localhost as placeholder base, to be stripped later
|
|
14
|
+
try {
|
|
15
|
+
urlObj = new URL(url);
|
|
16
|
+
} catch {
|
|
17
|
+
urlObj = new URL(url, 'http://localhost/');
|
|
18
|
+
partialUrl = true;
|
|
19
|
+
}
|
|
20
|
+
urlObj.searchParams.set(URL_QUERY_PARAM_INTERACTION_SESSION, paramValueEncoded);
|
|
21
|
+
if (partialUrl) {
|
|
22
|
+
return urlObj.pathname + urlObj.search;
|
|
23
|
+
} else {
|
|
24
|
+
return urlObj.toString();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const INTERNAL_CLIENT_WINDOW_KEY = 'analyticsInteractionSesssionTrackingClient';
|
|
2
|
+
export default class GlobalInteractionSessionTracking {
|
|
3
|
+
static getInstance() {
|
|
4
|
+
let interactionSessionTracking;
|
|
5
|
+
try {
|
|
6
|
+
// Check if InteractionSessionTracking in global window object
|
|
7
|
+
if (INTERNAL_CLIENT_WINDOW_KEY in window) {
|
|
8
|
+
interactionSessionTracking = window[INTERNAL_CLIENT_WINDOW_KEY];
|
|
9
|
+
}
|
|
10
|
+
} catch (error) {
|
|
11
|
+
// eslint-disable-next-line no-console
|
|
12
|
+
console.error(`Error fetching InteractionSessionTracking from window - ${error instanceof Error ? error.message : String(error)}`);
|
|
13
|
+
}
|
|
14
|
+
return interactionSessionTracking;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { INTERNAL_CLIENT_WINDOW_KEY } from './globalInteractionSessionTracking';
|
|
2
|
+
import useCrossProductUrlWrapper, { INTERACTION_SESSION_ID_UPDATED_EVENT } from './useCrossProductUrlWrapper';
|
|
3
|
+
export { useCrossProductUrlWrapper, INTERNAL_CLIENT_WINDOW_KEY, INTERACTION_SESSION_ID_UPDATED_EVENT };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { bind } from 'bind-event-listener';
|
|
3
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
4
|
+
import { generateUrlWithParams } from './generateUrlWithParams';
|
|
5
|
+
import GlobalInteractionSessionTracking from './globalInteractionSessionTracking';
|
|
6
|
+
export const INTERACTION_SESSION_ID_UPDATED_EVENT = 'interactionSessionIdUpdated';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param bridge - The name of your navigation component e.g. atlassianSwitcher
|
|
10
|
+
* @param product - The product you are hosted on e.g. jira
|
|
11
|
+
* @param subProduct - If specified, will be appended to the end of product as: product-subProduct
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* This React hook is called with the correct bridge, product and sub-product parameters.
|
|
16
|
+
* It returns a function that can be used to generate URLs with cross-product interaction parameters.
|
|
17
|
+
*
|
|
18
|
+
* @param options - Properties object that contains the following parameters:
|
|
19
|
+
*
|
|
20
|
+
* @returns A function that appends interaction session ID and other cross-product interaction parameters to a given URL.
|
|
21
|
+
*/
|
|
22
|
+
export default function useCrossProductUrlWrapper(options) {
|
|
23
|
+
const [interactionSessionId, setInteractionSessionId] = useState('');
|
|
24
|
+
const {
|
|
25
|
+
bridge,
|
|
26
|
+
product,
|
|
27
|
+
subProduct
|
|
28
|
+
} = options;
|
|
29
|
+
const isEnabled = fg('atlaskit-analytics-cross-product');
|
|
30
|
+
let interactionSessionClient;
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (isEnabled) {
|
|
33
|
+
// Fetch interaction session client from window object
|
|
34
|
+
interactionSessionClient = GlobalInteractionSessionTracking.getInstance();
|
|
35
|
+
|
|
36
|
+
// Fetch any initial interaction session ID
|
|
37
|
+
if (interactionSessionClient) {
|
|
38
|
+
const currentSessionId = interactionSessionClient.getCurrentInteractionSessionId();
|
|
39
|
+
currentSessionId && setInteractionSessionId(currentSessionId);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Add event listener that subscribes to any future interaction session ID updates
|
|
43
|
+
const unbind = bind(document, {
|
|
44
|
+
type: INTERACTION_SESSION_ID_UPDATED_EVENT,
|
|
45
|
+
listener: () => {
|
|
46
|
+
// Re-attempt fetch Global interactionSessionClient if not present already
|
|
47
|
+
if (!interactionSessionClient) {
|
|
48
|
+
interactionSessionClient = GlobalInteractionSessionTracking.getInstance();
|
|
49
|
+
}
|
|
50
|
+
if (interactionSessionClient) {
|
|
51
|
+
const currentSessionId = interactionSessionClient.getCurrentInteractionSessionId();
|
|
52
|
+
currentSessionId && setInteractionSessionId(currentSessionId);
|
|
53
|
+
} else {
|
|
54
|
+
// eslint-disable-next-line no-console
|
|
55
|
+
console.error(`INTERACTION_SESSION_ID_UPDATED_EVENT received but interactionSessionClient not present on window`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return unbind;
|
|
60
|
+
}
|
|
61
|
+
return () => {};
|
|
62
|
+
}, [interactionSessionClient, isEnabled]);
|
|
63
|
+
if (!isEnabled || !interactionSessionId) {
|
|
64
|
+
return url => url;
|
|
65
|
+
}
|
|
66
|
+
return url => generateUrlWithParams(url, bridge, interactionSessionId, product, subProduct);
|
|
67
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export var URL_QUERY_PARAM_INTERACTION_SESSION = 'xpis';
|
|
2
|
+
export var generateUrlWithParams = function generateUrlWithParams(url, bridge, interactionSessionId, product, subProduct) {
|
|
3
|
+
var interactionSource = subProduct ? "".concat(product, "-").concat(subProduct) : product;
|
|
4
|
+
var interactionSession = {
|
|
5
|
+
bridge: bridge,
|
|
6
|
+
id: interactionSessionId,
|
|
7
|
+
source: interactionSource
|
|
8
|
+
};
|
|
9
|
+
var paramValueEncoded = btoa(JSON.stringify(interactionSession));
|
|
10
|
+
var partialUrl = false;
|
|
11
|
+
var urlObj;
|
|
12
|
+
|
|
13
|
+
// If relative URL provided, add localhost as placeholder base, to be stripped later
|
|
14
|
+
try {
|
|
15
|
+
urlObj = new URL(url);
|
|
16
|
+
} catch (_unused) {
|
|
17
|
+
urlObj = new URL(url, 'http://localhost/');
|
|
18
|
+
partialUrl = true;
|
|
19
|
+
}
|
|
20
|
+
urlObj.searchParams.set(URL_QUERY_PARAM_INTERACTION_SESSION, paramValueEncoded);
|
|
21
|
+
if (partialUrl) {
|
|
22
|
+
return urlObj.pathname + urlObj.search;
|
|
23
|
+
} else {
|
|
24
|
+
return urlObj.toString();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
|
|
2
|
+
import _createClass from "@babel/runtime/helpers/createClass";
|
|
3
|
+
export var INTERNAL_CLIENT_WINDOW_KEY = 'analyticsInteractionSesssionTrackingClient';
|
|
4
|
+
var GlobalInteractionSessionTracking = /*#__PURE__*/function () {
|
|
5
|
+
function GlobalInteractionSessionTracking() {
|
|
6
|
+
_classCallCheck(this, GlobalInteractionSessionTracking);
|
|
7
|
+
}
|
|
8
|
+
return _createClass(GlobalInteractionSessionTracking, null, [{
|
|
9
|
+
key: "getInstance",
|
|
10
|
+
value: function getInstance() {
|
|
11
|
+
var interactionSessionTracking;
|
|
12
|
+
try {
|
|
13
|
+
// Check if InteractionSessionTracking in global window object
|
|
14
|
+
if (INTERNAL_CLIENT_WINDOW_KEY in window) {
|
|
15
|
+
interactionSessionTracking = window[INTERNAL_CLIENT_WINDOW_KEY];
|
|
16
|
+
}
|
|
17
|
+
} catch (error) {
|
|
18
|
+
// eslint-disable-next-line no-console
|
|
19
|
+
console.error("Error fetching InteractionSessionTracking from window - ".concat(error instanceof Error ? error.message : String(error)));
|
|
20
|
+
}
|
|
21
|
+
return interactionSessionTracking;
|
|
22
|
+
}
|
|
23
|
+
}]);
|
|
24
|
+
}();
|
|
25
|
+
export { GlobalInteractionSessionTracking as default };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { INTERNAL_CLIENT_WINDOW_KEY } from './globalInteractionSessionTracking';
|
|
2
|
+
import useCrossProductUrlWrapper, { INTERACTION_SESSION_ID_UPDATED_EVENT } from './useCrossProductUrlWrapper';
|
|
3
|
+
export { useCrossProductUrlWrapper, INTERNAL_CLIENT_WINDOW_KEY, INTERACTION_SESSION_ID_UPDATED_EVENT };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { bind } from 'bind-event-listener';
|
|
4
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
5
|
+
import { generateUrlWithParams } from './generateUrlWithParams';
|
|
6
|
+
import GlobalInteractionSessionTracking from './globalInteractionSessionTracking';
|
|
7
|
+
export var INTERACTION_SESSION_ID_UPDATED_EVENT = 'interactionSessionIdUpdated';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param bridge - The name of your navigation component e.g. atlassianSwitcher
|
|
11
|
+
* @param product - The product you are hosted on e.g. jira
|
|
12
|
+
* @param subProduct - If specified, will be appended to the end of product as: product-subProduct
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* This React hook is called with the correct bridge, product and sub-product parameters.
|
|
17
|
+
* It returns a function that can be used to generate URLs with cross-product interaction parameters.
|
|
18
|
+
*
|
|
19
|
+
* @param options - Properties object that contains the following parameters:
|
|
20
|
+
*
|
|
21
|
+
* @returns A function that appends interaction session ID and other cross-product interaction parameters to a given URL.
|
|
22
|
+
*/
|
|
23
|
+
export default function useCrossProductUrlWrapper(options) {
|
|
24
|
+
var _useState = useState(''),
|
|
25
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
26
|
+
interactionSessionId = _useState2[0],
|
|
27
|
+
setInteractionSessionId = _useState2[1];
|
|
28
|
+
var bridge = options.bridge,
|
|
29
|
+
product = options.product,
|
|
30
|
+
subProduct = options.subProduct;
|
|
31
|
+
var isEnabled = fg('atlaskit-analytics-cross-product');
|
|
32
|
+
var interactionSessionClient;
|
|
33
|
+
useEffect(function () {
|
|
34
|
+
if (isEnabled) {
|
|
35
|
+
// Fetch interaction session client from window object
|
|
36
|
+
interactionSessionClient = GlobalInteractionSessionTracking.getInstance();
|
|
37
|
+
|
|
38
|
+
// Fetch any initial interaction session ID
|
|
39
|
+
if (interactionSessionClient) {
|
|
40
|
+
var currentSessionId = interactionSessionClient.getCurrentInteractionSessionId();
|
|
41
|
+
currentSessionId && setInteractionSessionId(currentSessionId);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Add event listener that subscribes to any future interaction session ID updates
|
|
45
|
+
var unbind = bind(document, {
|
|
46
|
+
type: INTERACTION_SESSION_ID_UPDATED_EVENT,
|
|
47
|
+
listener: function listener() {
|
|
48
|
+
// Re-attempt fetch Global interactionSessionClient if not present already
|
|
49
|
+
if (!interactionSessionClient) {
|
|
50
|
+
interactionSessionClient = GlobalInteractionSessionTracking.getInstance();
|
|
51
|
+
}
|
|
52
|
+
if (interactionSessionClient) {
|
|
53
|
+
var _currentSessionId = interactionSessionClient.getCurrentInteractionSessionId();
|
|
54
|
+
_currentSessionId && setInteractionSessionId(_currentSessionId);
|
|
55
|
+
} else {
|
|
56
|
+
// eslint-disable-next-line no-console
|
|
57
|
+
console.error("INTERACTION_SESSION_ID_UPDATED_EVENT received but interactionSessionClient not present on window");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
return unbind;
|
|
62
|
+
}
|
|
63
|
+
return function () {};
|
|
64
|
+
}, [interactionSessionClient, isEnabled]);
|
|
65
|
+
if (!isEnabled || !interactionSessionId) {
|
|
66
|
+
return function (url) {
|
|
67
|
+
return url;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return function (url) {
|
|
71
|
+
return generateUrlWithParams(url, bridge, interactionSessionId, product, subProduct);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const INTERNAL_CLIENT_WINDOW_KEY = "analyticsInteractionSesssionTrackingClient";
|
|
2
|
+
export interface InteractionSessionTracking {
|
|
3
|
+
getCurrentInteractionSessionId(): string | null;
|
|
4
|
+
}
|
|
5
|
+
export default class GlobalInteractionSessionTracking {
|
|
6
|
+
static getInstance(): InteractionSessionTracking | undefined;
|
|
7
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { INTERNAL_CLIENT_WINDOW_KEY } from './globalInteractionSessionTracking';
|
|
2
|
+
import useCrossProductUrlWrapper, { type CrossProductUrlOptions, INTERACTION_SESSION_ID_UPDATED_EVENT } from './useCrossProductUrlWrapper';
|
|
3
|
+
export { useCrossProductUrlWrapper, type CrossProductUrlOptions, INTERNAL_CLIENT_WINDOW_KEY, INTERACTION_SESSION_ID_UPDATED_EVENT, };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const INTERACTION_SESSION_ID_UPDATED_EVENT = "interactionSessionIdUpdated";
|
|
2
|
+
/**
|
|
3
|
+
* @param bridge - The name of your navigation component e.g. atlassianSwitcher
|
|
4
|
+
* @param product - The product you are hosted on e.g. jira
|
|
5
|
+
* @param subProduct - If specified, will be appended to the end of product as: product-subProduct
|
|
6
|
+
*/
|
|
7
|
+
export type CrossProductUrlOptions = {
|
|
8
|
+
bridge: string;
|
|
9
|
+
product: string;
|
|
10
|
+
subProduct?: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* This React hook is called with the correct bridge, product and sub-product parameters.
|
|
14
|
+
* It returns a function that can be used to generate URLs with cross-product interaction parameters.
|
|
15
|
+
*
|
|
16
|
+
* @param options - Properties object that contains the following parameters:
|
|
17
|
+
*
|
|
18
|
+
* @returns A function that appends interaction session ID and other cross-product interaction parameters to a given URL.
|
|
19
|
+
*/
|
|
20
|
+
export default function useCrossProductUrlWrapper(options: CrossProductUrlOptions): (url: string) => string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const INTERNAL_CLIENT_WINDOW_KEY = "analyticsInteractionSesssionTrackingClient";
|
|
2
|
+
export interface InteractionSessionTracking {
|
|
3
|
+
getCurrentInteractionSessionId(): string | null;
|
|
4
|
+
}
|
|
5
|
+
export default class GlobalInteractionSessionTracking {
|
|
6
|
+
static getInstance(): InteractionSessionTracking | undefined;
|
|
7
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { INTERNAL_CLIENT_WINDOW_KEY } from './globalInteractionSessionTracking';
|
|
2
|
+
import useCrossProductUrlWrapper, { type CrossProductUrlOptions, INTERACTION_SESSION_ID_UPDATED_EVENT } from './useCrossProductUrlWrapper';
|
|
3
|
+
export { useCrossProductUrlWrapper, type CrossProductUrlOptions, INTERNAL_CLIENT_WINDOW_KEY, INTERACTION_SESSION_ID_UPDATED_EVENT, };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const INTERACTION_SESSION_ID_UPDATED_EVENT = "interactionSessionIdUpdated";
|
|
2
|
+
/**
|
|
3
|
+
* @param bridge - The name of your navigation component e.g. atlassianSwitcher
|
|
4
|
+
* @param product - The product you are hosted on e.g. jira
|
|
5
|
+
* @param subProduct - If specified, will be appended to the end of product as: product-subProduct
|
|
6
|
+
*/
|
|
7
|
+
export type CrossProductUrlOptions = {
|
|
8
|
+
bridge: string;
|
|
9
|
+
product: string;
|
|
10
|
+
subProduct?: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* This React hook is called with the correct bridge, product and sub-product parameters.
|
|
14
|
+
* It returns a function that can be used to generate URLs with cross-product interaction parameters.
|
|
15
|
+
*
|
|
16
|
+
* @param options - Properties object that contains the following parameters:
|
|
17
|
+
*
|
|
18
|
+
* @returns A function that appends interaction session ID and other cross-product interaction parameters to a given URL.
|
|
19
|
+
*/
|
|
20
|
+
export default function useCrossProductUrlWrapper(options: CrossProductUrlOptions): (url: string) => string;
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"atlassian": {
|
|
3
|
+
"team": "Atlassian Analytics Pipeline",
|
|
4
|
+
"website": {
|
|
5
|
+
"name": "AnalyticsCrossProduct",
|
|
6
|
+
"category": "Layout and structure"
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"repository": "https://bitbucket.org/atlassian/atlassian-frontend-monorepo",
|
|
10
|
+
"main": "dist/cjs/index.js",
|
|
11
|
+
"module": "dist/esm/index.js",
|
|
12
|
+
"module:es2019": "dist/es2019/index.js",
|
|
13
|
+
"types": "dist/types/index.d.ts",
|
|
14
|
+
"typesVersions": {
|
|
15
|
+
">=4.5 <5.9": {
|
|
16
|
+
"*": [
|
|
17
|
+
"dist/types-ts4.5/*",
|
|
18
|
+
"dist/types-ts4.5/index.d.ts"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"sideEffects": [
|
|
23
|
+
"*.compiled.css"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@atlaskit/platform-feature-flags": "^1.1.0",
|
|
27
|
+
"@babel/runtime": "^7.0.0",
|
|
28
|
+
"@compiled/react": "^0.20.0",
|
|
29
|
+
"bind-event-listener": "^3.0.0"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"react": "^18.2.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@atlassian/feature-flags-test-utils": "^1.1.0",
|
|
36
|
+
"@testing-library/react": "^16.3.0",
|
|
37
|
+
"react-dom": "^18.2.0"
|
|
38
|
+
},
|
|
39
|
+
"techstack": {
|
|
40
|
+
"@atlassian/frontend": {
|
|
41
|
+
"code-structure": [
|
|
42
|
+
"tangerine-next"
|
|
43
|
+
],
|
|
44
|
+
"import-structure": [
|
|
45
|
+
"atlassian-conventions"
|
|
46
|
+
],
|
|
47
|
+
"circular-dependencies": [
|
|
48
|
+
"file-and-folder-level"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"@repo/internal": {
|
|
52
|
+
"dom-events": "use-bind-event-listener",
|
|
53
|
+
"analytics": [
|
|
54
|
+
"analytics-next"
|
|
55
|
+
],
|
|
56
|
+
"design-tokens": [
|
|
57
|
+
"color"
|
|
58
|
+
],
|
|
59
|
+
"theming": [
|
|
60
|
+
"react-context"
|
|
61
|
+
],
|
|
62
|
+
"ui-components": [
|
|
63
|
+
"lite-mode"
|
|
64
|
+
],
|
|
65
|
+
"deprecation": [
|
|
66
|
+
"no-deprecated-imports"
|
|
67
|
+
],
|
|
68
|
+
"styling": [
|
|
69
|
+
"static",
|
|
70
|
+
"compiled"
|
|
71
|
+
],
|
|
72
|
+
"imports": [
|
|
73
|
+
"import-no-extraneous-disable-for-examples-and-docs"
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"name": "@atlaskit/analytics-cross-product",
|
|
78
|
+
"version": "1.0.0",
|
|
79
|
+
"description": "Utilities to enable cross-product interaction session tracking",
|
|
80
|
+
"author": "Atlassian Pty Ltd",
|
|
81
|
+
"license": "Apache-2.0",
|
|
82
|
+
"publishConfig": {
|
|
83
|
+
"registry": "https://registry.npmjs.org/"
|
|
84
|
+
},
|
|
85
|
+
"platform-feature-flags": {
|
|
86
|
+
"atlaskit-analytics-cross-product": {
|
|
87
|
+
"type": "boolean"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|