@wise/dynamic-flow-client-internal 5.28.2-experimental-a8a898e → 5.28.2
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/build/dynamicFlow/DynamicFlow.js +11 -0
- package/build/dynamicFlow/DynamicFlowModal.js +21 -0
- package/build/dynamicFlow/DynamicFlowWiseProps.js +1 -0
- package/build/dynamicFlow/DynamicFlowWithRef.js +19 -0
- package/build/dynamicFlow/context-menu/useContextMenu.js +33 -0
- package/build/dynamicFlow/context-menu/useDFContextMenu.js +135 -0
- package/build/dynamicFlow/context-menu/useDFContextMenu.test.js +101 -0
- package/build/dynamicFlow/getMergedRenderers.js +36 -0
- package/build/dynamicFlow/messages.js +13 -0
- package/build/dynamicFlow/renderers.js +10 -0
- package/build/dynamicFlow/telemetry/app-version.js +4 -0
- package/build/dynamicFlow/telemetry/dispatchAnalyticsEvent.js +8 -0
- package/build/dynamicFlow/telemetry/getLogEvent.js +22 -0
- package/build/dynamicFlow/telemetry/getTrackEvent.js +39 -0
- package/build/dynamicFlow/telemetry/index.js +2 -0
- package/build/dynamicFlow/useCreateSnackBar.js +9 -0
- package/build/dynamicFlow/useOnCopy.js +16 -0
- package/build/dynamicFlow/useWiseHttpClient.js +24 -0
- package/build/dynamicFlow/useWiseToCoreProps.js +42 -0
- package/build/i18n/fr.json +1 -1
- package/build/i18n/index.js +43 -0
- package/build/index.js +6 -0
- package/build/main.js +2 -2
- package/build/main.mjs +2 -2
- package/build/stories/ButtonLoader.story.js +109 -0
- package/build/stories/ContainerQueries.story.js +74 -0
- package/build/stories/DynamicFlow.story.js +37 -0
- package/build/stories/DynamicFlowWithRef.story.js +72 -0
- package/build/stories/Editable.story.js +58 -0
- package/build/stories/Initialisation.story.js +33 -0
- package/build/stories/PinnedButtons.story.js +94 -0
- package/build/stories/SCAIntegration.story.js +67 -0
- package/build/stories/utils/fixtureHttpClient.js +59 -0
- package/build/test-utils/NeptuneProviders.js +11 -0
- package/build/test-utils/index.js +4 -0
- package/build/test-utils/respond-with.js +6 -0
- package/build/test-utils/rtl-utils.js +7 -0
- package/build/test-utils/wait.js +3 -0
- package/build/tests/DynamicFlow.test.js +177 -0
- package/build/tests/HideStepTitle.test.js +58 -0
- package/build/tests/RequestCache.test.js +34 -0
- package/build/tests/SCARejectionHandling.test.js +71 -0
- package/build/tests/Subflow.Wise.test.js +650 -0
- package/build/tests/events.test.js +124 -0
- package/build/tests/logging.test.js +12 -0
- package/build/tests/onNotification.test.js +96 -0
- package/build/tests/renderers/ControlAndTags.test.js +225 -0
- package/build/tests/renderers/ImageRenderer.test.js +129 -0
- package/build/tests/renderers/RadioInputRendererComponent.test.js +89 -0
- package/build/tests/renderers/ReviewRenderer.test.js +41 -0
- package/build/tests/renderers/TextInputRenderer.test.js +44 -0
- package/build/tests/renderers/TimeZoneHeader.test.js +19 -0
- package/build/tests/renderers/UploadInputRenderer.maxSize.test.js +22 -0
- package/build/tests/renderers/value-utils.test.js +24 -0
- package/build/tsconfig.types.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useDynamicFlow } from '@wise/dynamic-flow-client';
|
|
3
|
+
import { useDFContextMenu } from './context-menu/useDFContextMenu';
|
|
4
|
+
import { useWiseToCoreProps } from './useWiseToCoreProps';
|
|
5
|
+
export function DynamicFlow(props) {
|
|
6
|
+
const { className = '' } = props;
|
|
7
|
+
const dfProps = useWiseToCoreProps(props);
|
|
8
|
+
const df = useDynamicFlow(dfProps);
|
|
9
|
+
const { onContextMenu, contextMenu } = useDFContextMenu(df.controller);
|
|
10
|
+
return (_jsxs("div", { className: className, onContextMenu: onContextMenu, children: [df.view, contextMenu] }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import { useDynamicFlowModal } from '@wise/dynamic-flow-client';
|
|
14
|
+
import { useWiseToCoreProps } from './useWiseToCoreProps';
|
|
15
|
+
import { Modal } from '@transferwise/components';
|
|
16
|
+
export function DynamicFlowModal(props) {
|
|
17
|
+
const { className = '' } = props, rest = __rest(props, ["className"]);
|
|
18
|
+
const dfProps = useWiseToCoreProps(rest);
|
|
19
|
+
const df = useDynamicFlowModal(dfProps);
|
|
20
|
+
return (_jsx(Modal, Object.assign({ className: `dynamic-flow-modal ${className}`, disableDimmerClickToClose: true }, df.modal, { body: _jsx("div", { className: "dynamic-flow-modal", children: df.modal.body }) })));
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useImperativeHandle, } from 'react';
|
|
3
|
+
import { useWiseToCoreProps } from './useWiseToCoreProps';
|
|
4
|
+
import { useDynamicFlow } from '@wise/dynamic-flow-client';
|
|
5
|
+
/**
|
|
6
|
+
* DynamicFlowWithRef is like DynamicFlow, but it also accepts a ForwardedRef.
|
|
7
|
+
* This ref conforms to the DynamicFlowRef type, containing two functions: `getValue` and `validate`.
|
|
8
|
+
* @experimental This component may be changed in the future without notice.
|
|
9
|
+
*/
|
|
10
|
+
export const DynamicFlowWithRef = forwardRef(function DynamicFlowWithRef(props, ref) {
|
|
11
|
+
const { className = '' } = props;
|
|
12
|
+
const dfProps = useWiseToCoreProps(props);
|
|
13
|
+
const df = useDynamicFlow(dfProps);
|
|
14
|
+
useImperativeHandle(ref, () => ({
|
|
15
|
+
getValue: () => { var _a; return (_a = df.controller.getValue()) !== null && _a !== void 0 ? _a : null; },
|
|
16
|
+
validate: () => df.controller.validate(),
|
|
17
|
+
}), [df]);
|
|
18
|
+
return _jsx("div", { className: className, children: df.view });
|
|
19
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
export function useContextMenu({ title, subtitle, items, }) {
|
|
4
|
+
const { menuPosition, openMenuAt, closeMenu } = useMenuPosition();
|
|
5
|
+
const onContextMenu = (event) => {
|
|
6
|
+
if (items.length > 0 && !event.shiftKey) {
|
|
7
|
+
event.preventDefault();
|
|
8
|
+
openMenuAt({ x: event.clientX, y: event.clientY });
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
const contextMenu = menuPosition ? (_jsxs("div", { className: "df-context-menu", style: { top: menuPosition.y, left: menuPosition.x }, children: [_jsx("div", { className: "menu-header", children: title }), _jsx("div", { className: "menu-subtitle", children: subtitle }), items.map(({ label, onClick }) => (_jsx("button", { type: "button", className: "menu-button", onClick: () => {
|
|
12
|
+
closeMenu();
|
|
13
|
+
onClick();
|
|
14
|
+
}, children: label }, label)))] })) : null;
|
|
15
|
+
return {
|
|
16
|
+
onContextMenu,
|
|
17
|
+
contextMenu,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const useMenuPosition = () => {
|
|
21
|
+
const [menuPosition, setMenuPosition] = useState(null);
|
|
22
|
+
const clearMenu = useCallback(() => setMenuPosition(null), []);
|
|
23
|
+
const openMenuAt = useCallback((position) => {
|
|
24
|
+
setMenuPosition(position);
|
|
25
|
+
window.addEventListener('click', clearMenu);
|
|
26
|
+
}, [clearMenu]);
|
|
27
|
+
const closeMenu = useCallback(() => {
|
|
28
|
+
setMenuPosition(null);
|
|
29
|
+
window.removeEventListener('click', clearMenu);
|
|
30
|
+
}, [clearMenu]);
|
|
31
|
+
useEffect(() => clearMenu, [clearMenu]);
|
|
32
|
+
return { menuPosition, openMenuAt, closeMenu };
|
|
33
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { appVersion } from '../telemetry/app-version';
|
|
2
|
+
import { useContextMenu } from './useContextMenu';
|
|
3
|
+
export const useDFContextMenu = (controller) => {
|
|
4
|
+
const getCurrentStep = () => {
|
|
5
|
+
const step = controller.getCurrentStep();
|
|
6
|
+
return step ? recursivelyRemoveNullish(step) : null;
|
|
7
|
+
};
|
|
8
|
+
const getCurrentStepWithModel = async () => {
|
|
9
|
+
const step = controller.getCurrentStep();
|
|
10
|
+
if (!step) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const model = controller.getValue();
|
|
14
|
+
return recursivelyRemoveNullish(Object.assign(Object.assign({}, step), { model }));
|
|
15
|
+
};
|
|
16
|
+
const getEncodedCurrentStep = () => {
|
|
17
|
+
const step = getCurrentStep();
|
|
18
|
+
return step ? toBase64(JSON.stringify(step, null, 2)) : null;
|
|
19
|
+
};
|
|
20
|
+
const getEncodedCurrentStepWithModel = async () => {
|
|
21
|
+
const step = await getCurrentStepWithModel();
|
|
22
|
+
return step ? toBase64(JSON.stringify(step, null, 2)) : null;
|
|
23
|
+
};
|
|
24
|
+
return useContextMenu({
|
|
25
|
+
title: 'DynamicFlow Menu (Dev/Staging only)',
|
|
26
|
+
subtitle: 'Shift + right-click for browser menu',
|
|
27
|
+
items: isDevOrStaging()
|
|
28
|
+
? [
|
|
29
|
+
{
|
|
30
|
+
label: `DF client version: ${appVersion}`,
|
|
31
|
+
onClick: () => {
|
|
32
|
+
openVersionPage(appVersion);
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
label: 'Copy step JSON',
|
|
37
|
+
onClick: () => {
|
|
38
|
+
copyToClipboard(getCurrentStep());
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
label: 'Open in Studio (experimental)',
|
|
43
|
+
onClick: () => {
|
|
44
|
+
openIn('studio', getEncodedCurrentStep());
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
label: 'Open in Sandbox',
|
|
49
|
+
onClick: () => {
|
|
50
|
+
openIn('sandbox', getEncodedCurrentStep());
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
label: 'Open in Sandbox (with model)',
|
|
55
|
+
onClick: () => {
|
|
56
|
+
void getEncodedCurrentStepWithModel().then((base64Step) => openIn('sandbox', base64Step));
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
]
|
|
60
|
+
: [],
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
const toBase64 = (str) => {
|
|
64
|
+
const bytes = new TextEncoder().encode(str);
|
|
65
|
+
const binString = Array.from(bytes, (b) => String.fromCharCode(b)).join('');
|
|
66
|
+
return btoa(binString);
|
|
67
|
+
};
|
|
68
|
+
const openIn = (editor, base64Step) => {
|
|
69
|
+
if (base64Step) {
|
|
70
|
+
window.open(`https://df.wise.com/${editor}#${base64Step}`, '_blank', 'noopener,noreferrer');
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const openVersionPage = (version) => {
|
|
74
|
+
const changelogUrl = getGitHubChangelogUrl(version);
|
|
75
|
+
if (changelogUrl) {
|
|
76
|
+
window.open(changelogUrl, '_blank', 'noopener,noreferrer');
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
window.open(getNpmPackageUrl(version), '_blank', 'noopener,noreferrer');
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const getGitHubChangelogUrl = (version) => {
|
|
83
|
+
const semverRegex = /^\d+\.\d+\.\d+$/;
|
|
84
|
+
if (semverRegex.test(version)) {
|
|
85
|
+
const hash = version.replace(/\./g, ''); // convert version like "1.2.3" to "123" for the GitHub changelog anchor
|
|
86
|
+
return `https://github.com/transferwise/dynamic-flow/blob/main/web/wise/CHANGELOG.md#${hash}`;
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
};
|
|
90
|
+
const getNpmPackageUrl = (version) => {
|
|
91
|
+
return `https://www.npmjs.com/package/@wise/dynamic-flow-client-internal/v/${version}`;
|
|
92
|
+
};
|
|
93
|
+
const copyToClipboard = (value) => {
|
|
94
|
+
if (typeof value === 'string') {
|
|
95
|
+
void navigator.clipboard.writeText(value);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (value) {
|
|
99
|
+
void navigator.clipboard.writeText(JSON.stringify(value, null, 2));
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
// We only want to show the context menu in dev and staging environments.
|
|
103
|
+
const isDevOrStaging = () => {
|
|
104
|
+
const validHostNames = ['localhost', 'dev-wi.se'];
|
|
105
|
+
try {
|
|
106
|
+
if (typeof window === 'undefined' || !window.location) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const currentHostname = window.location.hostname;
|
|
110
|
+
if (!currentHostname) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
return validHostNames.some((hostname) => currentHostname === hostname || currentHostname.endsWith(`.${hostname}`));
|
|
114
|
+
}
|
|
115
|
+
catch (_a) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const recursivelyRemoveNullish = (element) => {
|
|
120
|
+
if (Array.isArray(element)) {
|
|
121
|
+
return element.map(recursivelyRemoveNullish);
|
|
122
|
+
}
|
|
123
|
+
if (element !== null && typeof element === 'object') {
|
|
124
|
+
return Object.entries(element).reduce((acc, [key, value]) => {
|
|
125
|
+
if (value !== null && value !== undefined) {
|
|
126
|
+
// Use mutation instead of object spread to avoid O(n²) complexity.
|
|
127
|
+
// Object spread would rebuild the accumulator on every property, causing performance issues for large steps.
|
|
128
|
+
// eslint-disable-next-line functional/immutable-data, no-param-reassign
|
|
129
|
+
acc[key] = recursivelyRemoveNullish(value);
|
|
130
|
+
}
|
|
131
|
+
return acc;
|
|
132
|
+
}, {});
|
|
133
|
+
}
|
|
134
|
+
return element;
|
|
135
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import { vi } from 'vitest';
|
|
4
|
+
import { renderWithProviders } from '../../test-utils/rtl-utils';
|
|
5
|
+
import { useDFContextMenu } from './useDFContextMenu';
|
|
6
|
+
const mockedAppVersion = vi.hoisted(() => ({ current: '0.0.0' }));
|
|
7
|
+
vi.mock('../telemetry/app-version', () => ({
|
|
8
|
+
get appVersion() {
|
|
9
|
+
return mockedAppVersion.current;
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
const targetText = 'Right-click here to see the context menu';
|
|
13
|
+
const step = {
|
|
14
|
+
id: 'step-id',
|
|
15
|
+
title: 'Test Step',
|
|
16
|
+
description: null,
|
|
17
|
+
layout: [],
|
|
18
|
+
schemas: [],
|
|
19
|
+
};
|
|
20
|
+
const base64EncodedStep = 'ewogICJpZCI6ICJzdGVwLWlkIiwKICAidGl0bGUiOiAiVGVzdCBTdGVwIiwKICAibGF5b3V0IjogW10sCiAgInNjaGVtYXMiOiBbXQp9';
|
|
21
|
+
const base64EncodedStepWithModel = 'ewogICJpZCI6ICJzdGVwLWlkIiwKICAidGl0bGUiOiAiVGVzdCBTdGVwIiwKICAibGF5b3V0IjogW10sCiAgInNjaGVtYXMiOiBbXSwKICAibW9kZWwiOiB7fQp9';
|
|
22
|
+
const controller = {
|
|
23
|
+
getCurrentStep: vi.fn(() => step),
|
|
24
|
+
getValue: vi.fn(() => ({})),
|
|
25
|
+
};
|
|
26
|
+
const TestComponent = () => {
|
|
27
|
+
const { onContextMenu, contextMenu } = useDFContextMenu(controller);
|
|
28
|
+
return (_jsxs("div", { onContextMenu: onContextMenu, children: [targetText, contextMenu] }));
|
|
29
|
+
};
|
|
30
|
+
describe('useDFContextMenu', () => {
|
|
31
|
+
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null);
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
vi.clearAllMocks();
|
|
34
|
+
Object.defineProperty(navigator, 'clipboard', {
|
|
35
|
+
writable: true,
|
|
36
|
+
value: { writeText: vi.fn().mockResolvedValue(undefined) },
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe('when right-clicking on localhost', () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
Object.defineProperty(window, 'location', {
|
|
42
|
+
writable: true,
|
|
43
|
+
value: { hostname: 'localhost' },
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe('when the version is a release version', () => {
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
mockedAppVersion.current = '1.2.3';
|
|
49
|
+
});
|
|
50
|
+
it('shows the context menu with the version item, and on click opens the GitHub changelog', () => {
|
|
51
|
+
renderWithProviders(_jsx(TestComponent, {}));
|
|
52
|
+
fireEvent.contextMenu(screen.getByText(targetText));
|
|
53
|
+
expect(screen.getByText('DF client version: 1.2.3')).toBeInTheDocument();
|
|
54
|
+
fireEvent.click(screen.getByText('DF client version: 1.2.3'));
|
|
55
|
+
expect(openSpy).toHaveBeenCalledWith('https://github.com/transferwise/dynamic-flow/blob/main/web/wise/CHANGELOG.md#123', '_blank', 'noopener,noreferrer');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe('when the version is a non-release version', () => {
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
mockedAppVersion.current = '1.2.3-beta';
|
|
61
|
+
});
|
|
62
|
+
it('shows the context menu with the version item, and on click opens the npm page for the current version', () => {
|
|
63
|
+
renderWithProviders(_jsx(TestComponent, {}));
|
|
64
|
+
fireEvent.contextMenu(screen.getByText(targetText));
|
|
65
|
+
expect(screen.getByText('DF client version: 1.2.3-beta')).toBeInTheDocument();
|
|
66
|
+
fireEvent.click(screen.getByText('DF client version: 1.2.3-beta'));
|
|
67
|
+
expect(openSpy).toHaveBeenCalledWith('https://www.npmjs.com/package/@wise/dynamic-flow-client-internal/v/1.2.3-beta', '_blank', 'noopener,noreferrer');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
it('shows a menu item to open the current step in the sandbox', () => {
|
|
71
|
+
renderWithProviders(_jsx(TestComponent, {}));
|
|
72
|
+
fireEvent.contextMenu(screen.getByText(targetText));
|
|
73
|
+
expect(screen.getByText('Open in Sandbox')).toBeInTheDocument();
|
|
74
|
+
fireEvent.click(screen.getByText('Open in Sandbox'));
|
|
75
|
+
expect(openSpy).toHaveBeenCalledWith(expect.stringContaining(`https://df.wise.com/sandbox#${base64EncodedStep}`), '_blank', 'noopener,noreferrer');
|
|
76
|
+
});
|
|
77
|
+
it('shows a menu item to open the current step in Studio', () => {
|
|
78
|
+
renderWithProviders(_jsx(TestComponent, {}));
|
|
79
|
+
fireEvent.contextMenu(screen.getByText(targetText));
|
|
80
|
+
expect(screen.getByText('Open in Studio (experimental)')).toBeInTheDocument();
|
|
81
|
+
fireEvent.click(screen.getByText('Open in Studio (experimental)'));
|
|
82
|
+
expect(openSpy).toHaveBeenCalledWith(expect.stringContaining(`https://df.wise.com/studio#${base64EncodedStep}`), '_blank', 'noopener,noreferrer');
|
|
83
|
+
});
|
|
84
|
+
it('shows a menu item to open the current step in the sandbox with model', async () => {
|
|
85
|
+
renderWithProviders(_jsx(TestComponent, {}));
|
|
86
|
+
fireEvent.contextMenu(screen.getByText(targetText));
|
|
87
|
+
expect(screen.getByText('Open in Sandbox (with model)')).toBeInTheDocument();
|
|
88
|
+
fireEvent.click(screen.getByText('Open in Sandbox (with model)'));
|
|
89
|
+
await waitFor(() => {
|
|
90
|
+
expect(openSpy).toHaveBeenCalledWith(expect.stringContaining(`https://df.wise.com/sandbox#${base64EncodedStepWithModel}`), '_blank', 'noopener,noreferrer');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
it('shows a menu item to copy the current step JSON', () => {
|
|
94
|
+
renderWithProviders(_jsx(TestComponent, {}));
|
|
95
|
+
fireEvent.contextMenu(screen.getByText(targetText));
|
|
96
|
+
expect(screen.getByText('Copy step JSON')).toBeInTheDocument();
|
|
97
|
+
fireEvent.click(screen.getByText('Copy step JSON'));
|
|
98
|
+
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(JSON.stringify({ id: 'step-id', title: 'Test Step', layout: [], schemas: [] }, null, 2));
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import { getDynamicSubflowRenderer } from '@wise/dynamic-flow-client';
|
|
14
|
+
import { getWiseRenderers, InitialLoadingStateRenderer } from '@wise/dynamic-flow-renderers';
|
|
15
|
+
import { DynamicFlow } from './DynamicFlow';
|
|
16
|
+
import { DynamicFlowModal } from './DynamicFlowModal';
|
|
17
|
+
const wiseRenderers = getWiseRenderers();
|
|
18
|
+
export const getMergedRenderers = (props) => {
|
|
19
|
+
var _a;
|
|
20
|
+
var _b;
|
|
21
|
+
const { initialAction, initialStep } = props, restProps = __rest(props, ["initialAction", "initialStep"]);
|
|
22
|
+
const subflowFeatures = Object.assign(Object.assign({}, props.features), { nativeBack: true });
|
|
23
|
+
const subflowRenderer = getDynamicSubflowRenderer({
|
|
24
|
+
Component: (_a) => {
|
|
25
|
+
var { presentation, initialRequest } = _a, rest = __rest(_a, ["presentation", "initialRequest"]);
|
|
26
|
+
const action = {
|
|
27
|
+
url: initialRequest.url,
|
|
28
|
+
method: initialRequest.method,
|
|
29
|
+
data: initialRequest.body,
|
|
30
|
+
};
|
|
31
|
+
return presentation.type === 'push' ? (_jsx(DynamicFlow, Object.assign({}, restProps, rest, { features: subflowFeatures, initialAction: action }))) : (_jsx(DynamicFlowModal, Object.assign({}, restProps, rest, { features: subflowFeatures, initialAction: action })));
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
const loadingStateRenderer = ((_a = props.features) === null || _a === void 0 ? void 0 : _a.initialLoader) ? [InitialLoadingStateRenderer] : [];
|
|
35
|
+
return [...((_b = props.renderers) !== null && _b !== void 0 ? _b : []), subflowRenderer, ...loadingStateRenderer, ...wiseRenderers];
|
|
36
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineMessages } from 'react-intl';
|
|
2
|
+
export default defineMessages({
|
|
3
|
+
copied: {
|
|
4
|
+
id: 'df.wise.CopyFeedback.copy',
|
|
5
|
+
defaultMessage: 'Copied to clipboard',
|
|
6
|
+
description: 'Appears in a snackbar when the copy operation succeeds.',
|
|
7
|
+
},
|
|
8
|
+
copyFailed: {
|
|
9
|
+
id: 'df.wise.CopyFeedback.copyFailed',
|
|
10
|
+
defaultMessage: 'Failed to copy to clipboard',
|
|
11
|
+
description: 'Appears in a snackbar when the copy operation fails.',
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Header as RendererHeader, Media as RendererMedia, getMargin as rendererGetMargin, createCollectionRenderer as rendererCreateCollectionRenderer, } from '@wise/dynamic-flow-renderers';
|
|
2
|
+
/**
|
|
3
|
+
* We need to redefine these because @wise/dynamic-flow-renderers is a private package that
|
|
4
|
+
* cannot be installed. There is likely a better solution, but until we find it we can
|
|
5
|
+
* redefine and re-export.
|
|
6
|
+
*/
|
|
7
|
+
export const Header = RendererHeader;
|
|
8
|
+
export const Media = RendererMedia;
|
|
9
|
+
export const getMargin = rendererGetMargin;
|
|
10
|
+
export const createCollectionRenderer = rendererCreateCollectionRenderer;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const dispatchAnalyticsEvent = (eventName, properties) => {
|
|
2
|
+
const props = Object.assign({ dfFallbackAnalytics: true }, properties);
|
|
3
|
+
if (typeof window !== 'undefined' &&
|
|
4
|
+
window.mixpanel &&
|
|
5
|
+
typeof window.mixpanel.track === 'function') {
|
|
6
|
+
window.mixpanel.track(eventName, props);
|
|
7
|
+
}
|
|
8
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { appVersion } from './app-version';
|
|
2
|
+
export const getLogEvent = (onLog) => (level, message, extra) => {
|
|
3
|
+
const extraWithVersion = Object.assign(Object.assign({}, extra), { dfWiseVersion: appVersion });
|
|
4
|
+
if (level !== 'info' && onLog) {
|
|
5
|
+
if (onLog) {
|
|
6
|
+
onLog(level, message, extraWithVersion);
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
logToRollbar(level, message, extraWithVersion);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const logToRollbar = (level, message, extra) => {
|
|
14
|
+
try {
|
|
15
|
+
const rollbar = typeof window.Rollbar !== 'undefined' ? window.Rollbar : undefined;
|
|
16
|
+
rollbar === null || rollbar === void 0 ? void 0 : rollbar[level](message, extra);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
// eslint-disable-next-line no-console
|
|
20
|
+
console.error('Failed to log to Rollbar', error);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { eventNames } from '@wise/dynamic-flow-client';
|
|
2
|
+
import { dispatchAnalyticsEvent } from './dispatchAnalyticsEvent';
|
|
3
|
+
const prefix = 'Dynamic Flow - ';
|
|
4
|
+
export const getTrackEvent = (onEvent, onAnalytics) => {
|
|
5
|
+
return (name, properties) => {
|
|
6
|
+
/*
|
|
7
|
+
* By adding an `$insert_id`, mixpanel can deduplicate events
|
|
8
|
+
* https://docs.mixpanel.com/reference/event-deduplication
|
|
9
|
+
*
|
|
10
|
+
* This means we do not need to rely on onEvent/onAnalytics to track events
|
|
11
|
+
* correctly, and can safely send our own. For integrations that are
|
|
12
|
+
* tracking correctly, they will be deduplicated at query time immediately,
|
|
13
|
+
* or at compaction time (for data exports) after a few hours and again
|
|
14
|
+
* after ~20 days.
|
|
15
|
+
*/
|
|
16
|
+
const propsWithUuid = Object.assign(Object.assign({}, properties), { $insert_id: crypto.randomUUID() });
|
|
17
|
+
onEvent === null || onEvent === void 0 ? void 0 : onEvent(name, propsWithUuid);
|
|
18
|
+
if (includeInAnalytics(name)) {
|
|
19
|
+
dispatchAnalyticsEvent(name, propsWithUuid);
|
|
20
|
+
onAnalytics === null || onAnalytics === void 0 ? void 0 : onAnalytics(name, propsWithUuid);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
const includeInAnalytics = (name) => {
|
|
25
|
+
const eventName = name.startsWith(prefix) ? name.slice(prefix.length) : name;
|
|
26
|
+
if (isCoreEventName(eventName)) {
|
|
27
|
+
return true; // for now, all core events are sent to analytics
|
|
28
|
+
}
|
|
29
|
+
if (eventName in customEventsToAnalytics) {
|
|
30
|
+
return customEventsToAnalytics[eventName];
|
|
31
|
+
}
|
|
32
|
+
return true; // unknown custom events are sent to analytics by default
|
|
33
|
+
};
|
|
34
|
+
const isCoreEventName = (eventName) => {
|
|
35
|
+
return eventNames.includes(eventName);
|
|
36
|
+
};
|
|
37
|
+
const customEventsToAnalytics = {
|
|
38
|
+
'Theme Required': false, // Excluded from analytics
|
|
39
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { useSnackBarIfAvailable } from '@wise/dynamic-flow-renderers';
|
|
2
|
+
/**
|
|
3
|
+
* Wrapper around `useSnackBarIfAvailable` so tests can mock this module without
|
|
4
|
+
* mocking `@wise/dynamic-flow-renderers` directly.
|
|
5
|
+
*/
|
|
6
|
+
export const useCreateSnackBar = () => {
|
|
7
|
+
const createSnackBar = useSnackBarIfAvailable();
|
|
8
|
+
return createSnackBar;
|
|
9
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useSnackBarIfAvailable } from '@wise/dynamic-flow-renderers';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
import { useIntl } from 'react-intl';
|
|
4
|
+
import messages from './messages';
|
|
5
|
+
export const useOnCopy = () => {
|
|
6
|
+
const { formatMessage } = useIntl();
|
|
7
|
+
const createSnackBar = useSnackBarIfAvailable();
|
|
8
|
+
return useCallback((copiedContent) => {
|
|
9
|
+
if (copiedContent) {
|
|
10
|
+
createSnackBar({ text: formatMessage(messages.copied) });
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
createSnackBar({ text: formatMessage(messages.copyFailed) });
|
|
14
|
+
}
|
|
15
|
+
}, [createSnackBar, formatMessage]);
|
|
16
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { useIntl } from 'react-intl';
|
|
3
|
+
export const useWiseHttpClient = (httpClient) => {
|
|
4
|
+
const { locale } = useIntl();
|
|
5
|
+
return useCallback(async (input, init = {}) => {
|
|
6
|
+
const headers = new Headers(init.headers);
|
|
7
|
+
headers.set('accept-language', locale);
|
|
8
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || undefined;
|
|
9
|
+
if (timeZone) {
|
|
10
|
+
headers.set('time-zone', timeZone);
|
|
11
|
+
}
|
|
12
|
+
const requestInit = Object.assign(Object.assign({}, init), { credentials: 'include', headers });
|
|
13
|
+
return httpClient(input, requestInit).catch(handleRejection);
|
|
14
|
+
}, [httpClient, locale]);
|
|
15
|
+
};
|
|
16
|
+
const handleRejection = (error) => {
|
|
17
|
+
if (error instanceof Error && error.message === 'SCA_MODAL_CLOSED') {
|
|
18
|
+
// This is a special case for the SCA modal.
|
|
19
|
+
// By responding with an empty `validation` object, and an error status code,
|
|
20
|
+
// we can avoid showing any error messages.
|
|
21
|
+
return new Response(JSON.stringify({ validation: {} }), { status: 400 });
|
|
22
|
+
}
|
|
23
|
+
throw error;
|
|
24
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { getMergedRenderers } from './getMergedRenderers';
|
|
3
|
+
import { getLogEvent, getTrackEvent } from './telemetry';
|
|
4
|
+
import { useOnCopy } from './useOnCopy';
|
|
5
|
+
import { useWiseHttpClient } from './useWiseHttpClient';
|
|
6
|
+
import { useCreateSnackBar } from './useCreateSnackBar';
|
|
7
|
+
export const useWiseToCoreProps = (props) => {
|
|
8
|
+
const { displayStepTitle = true, features = {}, customFetch = globalThis.fetch, renderers, onAnalytics, onEvent, onLink = openLinkInNewTab, onNotification, onLog, } = props;
|
|
9
|
+
const httpClient = useWiseHttpClient(customFetch);
|
|
10
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
11
|
+
const mergedRenderers = useMemo(() => getMergedRenderers(props), [renderers]);
|
|
12
|
+
const createSnackBar = useCreateSnackBar();
|
|
13
|
+
const logEvent = useMemo(() => getLogEvent(onLog), [onLog]);
|
|
14
|
+
const trackEvent = useMemo(() => getTrackEvent(onEvent, onAnalytics), [onEvent, onAnalytics]);
|
|
15
|
+
const onCopy = useOnCopy();
|
|
16
|
+
return Object.assign(Object.assign({}, props), { httpClient, features: Object.assign({ hideStepTitle: !displayStepTitle }, features), renderers: mergedRenderers, onEvent: trackEvent, onLog: logEvent, onCopy, onNotification: onNotification !== null && onNotification !== void 0 ? onNotification : createSnackBar, onLink });
|
|
17
|
+
};
|
|
18
|
+
const openLinkInNewTab = (url) => {
|
|
19
|
+
if (typeof window === 'undefined' || typeof window.open !== 'function') {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
// eslint-disable-next-line no-script-url
|
|
23
|
+
if (url.startsWith('javascript:')) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const w = window.open(url, '_blank');
|
|
28
|
+
if (w) {
|
|
29
|
+
try {
|
|
30
|
+
// eslint-disable-next-line functional/immutable-data
|
|
31
|
+
w.opener = null;
|
|
32
|
+
}
|
|
33
|
+
catch (_a) {
|
|
34
|
+
// ignore
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return Boolean(w);
|
|
38
|
+
}
|
|
39
|
+
catch (_b) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
};
|
package/build/i18n/fr.json
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"df.wise.ControlFeedback.type": "Type incorrect",
|
|
21
21
|
"df.wise.CopyFeedback.copy": "Copié dans le presse-papier",
|
|
22
22
|
"df.wise.CopyFeedback.copyFailed": "Impossible de copier dans le presse-papier",
|
|
23
|
-
"df.wise.DateInput.unavailableDates": "
|
|
23
|
+
"df.wise.DateInput.unavailableDates": "Certaines dates ne sont pas disponibles.",
|
|
24
24
|
"df.wise.DynamicParagraph.copied": "Copié dans le presse-papier",
|
|
25
25
|
"df.wise.DynamicParagraph.copy": "Copier",
|
|
26
26
|
"df.wise.ErrorBoundary.errorAlert": "Une erreur s'est produite.",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { translations as coreTranslations } from '@wise/dynamic-flow-client';
|
|
2
|
+
import cs from './cs.json';
|
|
3
|
+
import de from './de.json';
|
|
4
|
+
import en from './en.json';
|
|
5
|
+
import es from './es.json';
|
|
6
|
+
import fr from './fr.json';
|
|
7
|
+
import hu from './hu.json';
|
|
8
|
+
import id from './id.json';
|
|
9
|
+
import it from './it.json';
|
|
10
|
+
import ja from './ja.json';
|
|
11
|
+
import nl from './nl.json';
|
|
12
|
+
import pl from './pl.json';
|
|
13
|
+
import pt from './pt.json';
|
|
14
|
+
import ro from './ro.json';
|
|
15
|
+
import ru from './ru.json';
|
|
16
|
+
import th from './th.json';
|
|
17
|
+
import tr from './tr.json';
|
|
18
|
+
import zhCN from './zh_CN.json';
|
|
19
|
+
import zhHK from './zh_HK.json';
|
|
20
|
+
const wiseTranslations = {
|
|
21
|
+
cs,
|
|
22
|
+
en,
|
|
23
|
+
de,
|
|
24
|
+
es,
|
|
25
|
+
fr,
|
|
26
|
+
hu,
|
|
27
|
+
id,
|
|
28
|
+
it,
|
|
29
|
+
ja,
|
|
30
|
+
nl,
|
|
31
|
+
pl,
|
|
32
|
+
pt,
|
|
33
|
+
ro,
|
|
34
|
+
ru,
|
|
35
|
+
th,
|
|
36
|
+
tr,
|
|
37
|
+
zh: zhCN,
|
|
38
|
+
'zh-CN': zhCN,
|
|
39
|
+
'zh-HK': zhHK,
|
|
40
|
+
};
|
|
41
|
+
const languages = Array.from(new Set([...Object.keys(coreTranslations), ...Object.keys(wiseTranslations)]));
|
|
42
|
+
const translations = languages.reduce((acc, lang) => (Object.assign(Object.assign({}, acc), { [lang]: Object.assign(Object.assign({}, coreTranslations[lang]), wiseTranslations[lang]) })), {});
|
|
43
|
+
export default translations;
|
package/build/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { makeHttpClient as makeCustomFetch } from '@wise/dynamic-flow-client';
|
|
2
|
+
export { DynamicFlow } from './dynamicFlow/DynamicFlow';
|
|
3
|
+
export { DynamicFlowWithRef } from './dynamicFlow/DynamicFlowWithRef';
|
|
4
|
+
export { findRendererPropsByType } from '@wise/dynamic-flow-client';
|
|
5
|
+
export { createCollectionRenderer, getMargin, Header, Media } from './dynamicFlow/renderers';
|
|
6
|
+
export { default as translations } from './i18n';
|
package/build/main.js
CHANGED
|
@@ -146,7 +146,7 @@ var import_dynamic_flow_client4 = require("@wise/dynamic-flow-client");
|
|
|
146
146
|
// src/dynamicFlow/telemetry/app-version.ts
|
|
147
147
|
var appVersion = (
|
|
148
148
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
149
|
-
typeof process !== "undefined" ? "5.28.
|
|
149
|
+
typeof process !== "undefined" ? "5.28.2" : "0.0.0"
|
|
150
150
|
);
|
|
151
151
|
|
|
152
152
|
// src/dynamicFlow/context-menu/useContextMenu.tsx
|
|
@@ -7503,7 +7503,7 @@ var fr_default = {
|
|
|
7503
7503
|
"df.wise.ControlFeedback.type": "Type incorrect",
|
|
7504
7504
|
"df.wise.CopyFeedback.copy": "Copi\xE9 dans le presse-papier",
|
|
7505
7505
|
"df.wise.CopyFeedback.copyFailed": "Impossible de copier dans le presse-papier",
|
|
7506
|
-
"df.wise.DateInput.unavailableDates": "
|
|
7506
|
+
"df.wise.DateInput.unavailableDates": "Certaines dates ne sont pas disponibles.",
|
|
7507
7507
|
"df.wise.DynamicParagraph.copied": "Copi\xE9 dans le presse-papier",
|
|
7508
7508
|
"df.wise.DynamicParagraph.copy": "Copier",
|
|
7509
7509
|
"df.wise.ErrorBoundary.errorAlert": "Une erreur s'est produite.",
|