@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 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,2 @@
1
+ export declare const URL_QUERY_PARAM_INTERACTION_SESSION = "xpis";
2
+ export declare const generateUrlWithParams: (url: string, bridge: string, interactionSessionId: string, product: string, subProduct?: 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;
@@ -0,0 +1,2 @@
1
+ export declare const URL_QUERY_PARAM_INTERACTION_SESSION = "xpis";
2
+ export declare const generateUrlWithParams: (url: string, bridge: string, interactionSessionId: string, product: string, subProduct?: 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
+ }