@orchestrator-ui/orchestrator-ui-components 0.10.0 → 0.11.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.
Files changed (33) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/.turbo/turbo-test.log +11 -10
  4. package/CHANGELOG.md +12 -0
  5. package/dist/index.d.ts +49 -9
  6. package/dist/index.js +4397 -4140
  7. package/package.json +1 -1
  8. package/src/components/WfoBadges/WfoFailedTasksBadge/WfoFailedTasksBadge.tsx +1 -3
  9. package/src/components/WfoBadges/WfoHeaderBadge/WfoHeaderBadge.tsx +1 -1
  10. package/src/components/WfoBadges/WfoSubscriptionSyncStatusBadge/WfoSubscriptionSyncStatusBadge.stories.tsx +27 -0
  11. package/src/components/WfoBadges/WfoSubscriptionSyncStatusBadge/WfoSubscriptionSyncStatusBadge.tsx +45 -0
  12. package/src/components/WfoBadges/WfoSubscriptionSyncStatusBadge/index.ts +1 -0
  13. package/src/components/WfoBadges/WfoWebsocketStatusBadge/WfoWebsocketStatusBadge.stories.tsx +13 -0
  14. package/src/components/WfoBadges/WfoWebsocketStatusBadge/WfoWebsocketStatusBadge.tsx +41 -0
  15. package/src/components/WfoBadges/WfoWebsocketStatusBadge/index.ts +1 -0
  16. package/src/components/WfoForms/formFields/CustomerField.tsx +4 -1
  17. package/src/components/WfoPageTemplate/WfoPageHeader/WfoPageHeader.tsx +47 -2
  18. package/src/components/WfoPageTemplate/WfoPageTemplate/WfoPageTemplate.tsx +4 -1
  19. package/src/components/WfoSubscription/WfoSubscription.tsx +25 -2
  20. package/src/contexts/OrchestratorConfigContext.tsx +1 -0
  21. package/src/hooks/useOrchestratorTheme.ts +3 -1
  22. package/src/hooks/useShowToastMessage.ts +4 -26
  23. package/src/icons/WfoSideMenu.stories.tsx +1 -1
  24. package/src/messages/en-GB.json +8 -2
  25. package/src/messages/nl-NL.json +8 -2
  26. package/src/rtk/api.ts +7 -1
  27. package/src/rtk/endpoints/index.ts +1 -0
  28. package/src/rtk/endpoints/settings.ts +3 -3
  29. package/src/rtk/endpoints/streamMessages.ts +128 -0
  30. package/src/theme/defaultOrchestratorTheme.ts +28 -1
  31. package/src/types/types.ts +6 -0
  32. package/src/utils/getToastMessage.spec.ts +28 -0
  33. package/src/utils/getToastMessage.ts +35 -0
@@ -0,0 +1,128 @@
1
+ import { debounce } from 'lodash';
2
+
3
+ import { addToastMessage } from '@/rtk/slices/toastMessages';
4
+ import type { RootState } from '@/rtk/store';
5
+ import { ToastTypes } from '@/types';
6
+ import { getToastMessage } from '@/utils/getToastMessage';
7
+
8
+ import { CacheTags, orchestratorApi } from '../api';
9
+
10
+ const getWebSocket = (url: string) => {
11
+ // TODO: Implement authentication taking this into account: https://stackoverflow.com/questions/4361173/http-headers-in-websockets-client-api/77060459#77060459
12
+ // https://github.com/workfloworchestrator/orchestrator-core/issues/502 - https://github.com/workfloworchestrator/orchestrator-ui-library/issues/823
13
+ return new WebSocket(url);
14
+ };
15
+
16
+ const PING_INTERVAL_MS = 5000;
17
+ const DEBOUNCE_CLOSE_INTERVAL_MS = 10000;
18
+
19
+ /*
20
+ * Websocket handling as recommended by RTK QUery see: https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#streaming-data-with-no-initial-request
21
+ * The websocket is opened after the cacheDataLoaded promise is resolved, and closed after the cacheEntryRemoved promise is resolved maintaining
22
+ * the connection in between
23
+ * - It sends a ping message every PING_INTERVAL ms to keep the connection alive
24
+ * - It debounces the close event to avoid closing the connection every time a 'pong' message is received
25
+ * - It closes the connection if any websocket error or close event is received
26
+ * - It invalidates the cache entry with the tag received in the message event
27
+ */
28
+ const streamMessagesApi = orchestratorApi.injectEndpoints({
29
+ endpoints: (build) => ({
30
+ streamMessages: build.query<boolean, void>({
31
+ queryFn: () => {
32
+ return { data: false };
33
+ },
34
+ async onCacheEntryAdded(
35
+ _,
36
+ {
37
+ cacheDataLoaded,
38
+ cacheEntryRemoved,
39
+ dispatch,
40
+ getState,
41
+ updateCachedData,
42
+ },
43
+ ) {
44
+ const cleanUp = () => {
45
+ const message = getToastMessage(
46
+ ToastTypes.ERROR,
47
+ 'Connection to the server was lost. Please refresh the page to reconnect.',
48
+ 'WebSocket closed',
49
+ );
50
+ dispatch(addToastMessage(message));
51
+ clearInterval(pingInterval);
52
+ updateCachedData(() => false);
53
+ };
54
+
55
+ await cacheDataLoaded;
56
+
57
+ const state = getState() as RootState;
58
+ const { orchestratorWebsocketUrl } = state.orchestratorConfig;
59
+
60
+ // Starts the websocket
61
+ const webSocket = getWebSocket(orchestratorWebsocketUrl);
62
+
63
+ // Lets the WfoWebsocketStatusBadge know the websocket is connected
64
+ webSocket.onopen = () => {
65
+ updateCachedData(() => true);
66
+ };
67
+
68
+ // Send a ping message every to the websocket server to keep the connection alive
69
+ const pingInterval = setInterval(() => {
70
+ webSocket.send('ping');
71
+ }, PING_INTERVAL_MS);
72
+
73
+ const debounceCloseWebSocket = debounce(() => {
74
+ webSocket.close();
75
+ }, DEBOUNCE_CLOSE_INTERVAL_MS);
76
+ // Start the debounced function to close the websocket when no 'pong' message is received after DEBOUNCE_CLOSE_INTERVAL
77
+ debounceCloseWebSocket();
78
+
79
+ webSocket.addEventListener(
80
+ 'message',
81
+ (message: MessageEvent<string>) => {
82
+ const tagOrPong = message.data.trim();
83
+
84
+ if (tagOrPong === 'pong') {
85
+ // Reset the debounced every time a 'pong' message is received
86
+ debounceCloseWebSocket();
87
+ return;
88
+ }
89
+
90
+ const tagToInvalidate = tagOrPong as CacheTags;
91
+ const validCacheTags = Object.values(CacheTags);
92
+
93
+ if (
94
+ tagToInvalidate &&
95
+ validCacheTags.includes(tagToInvalidate)
96
+ ) {
97
+ const cacheInvalidationAction =
98
+ orchestratorApi.util.invalidateTags([
99
+ tagToInvalidate,
100
+ ]);
101
+ dispatch(cacheInvalidationAction);
102
+ } else {
103
+ console.error(
104
+ `Trying to invalidate a cache entry with an unknown tag: ${tagToInvalidate}`,
105
+ );
106
+ }
107
+ },
108
+ );
109
+
110
+ webSocket.onerror = (event) => {
111
+ console.error('WebSocket error', event);
112
+ cleanUp();
113
+ };
114
+
115
+ webSocket.onclose = () => {
116
+ console.error('WebSocket closed');
117
+ cleanUp();
118
+ };
119
+
120
+ await cacheEntryRemoved;
121
+
122
+ webSocket.close();
123
+ },
124
+ }),
125
+ }),
126
+ });
127
+
128
+ export const { useStreamMessagesQuery } = streamMessagesApi;
@@ -23,7 +23,34 @@ export const defaultOrchestratorTheme: EuiThemeModifications = {
23
23
  xxxxl: '64px',
24
24
  },
25
25
  colors: {
26
- DARK: {},
26
+ DARK: {
27
+ primary: '#265391',
28
+ accent: '#F68FBE',
29
+ success: '#7DDED8',
30
+ warning: '#F3D371',
31
+ danger: '#F86B63',
32
+ primaryText: '#3EA6fA',
33
+ accentText: '#F68FBE',
34
+ successText: '#7DDEd8',
35
+ warningText: '#F3D371',
36
+ dangerText: '#F86B63',
37
+ emptyShade: '#1D1E24',
38
+ lightestShade: '#25262E',
39
+ lightShade: '#343741',
40
+ mediumShade: '#535966',
41
+ darkShade: '#98A2B3',
42
+ darkestShade: '#D4DAE5',
43
+ fullShade: '#FFFFFF',
44
+ text: '#DFE5EF',
45
+ title: '#DFE5EF',
46
+ subduedText: '#81858f',
47
+ link: '#3EA6fA',
48
+ body: '#141519',
49
+ highlight: '#262830',
50
+ disabled: '#515761',
51
+ disabledText: '#515761',
52
+ shadow: '#000000',
53
+ },
27
54
  LIGHT: {
28
55
  primary: '#0077C8',
29
56
  accent: '#E67300',
@@ -462,6 +462,7 @@ export enum Environment {
462
462
 
463
463
  export type OrchestratorConfig = {
464
464
  environmentName: Environment | string;
465
+ orchestratorWebsocketUrl: string;
465
466
  orchestratorApiBaseUrl: string;
466
467
  graphqlEndpointCore: string;
467
468
  engineStatusEndpoint: string;
@@ -471,3 +472,8 @@ export type OrchestratorConfig = {
471
472
  subscriptionProcessesEndpoint: string;
472
473
  authActive: boolean;
473
474
  };
475
+
476
+ export enum ColorModes {
477
+ LIGHT = 'LIGHT',
478
+ DARK = 'DARK',
479
+ }
@@ -0,0 +1,28 @@
1
+ import { ToastTypes } from '../types';
2
+ import { getToastMessage } from './getToastMessage';
3
+
4
+ describe('getToastMessage()', () => {
5
+ it('returns message object with title, text and passed in type', () => {
6
+ const result = getToastMessage(ToastTypes.ERROR, 'text', 'title');
7
+ expect(result.title).toBe('title');
8
+ expect(result.text).toBe('text');
9
+ });
10
+
11
+ it('returns message object with random integer id', () => {
12
+ const result = getToastMessage(ToastTypes.ERROR, 'text', 'title');
13
+ const result2 = getToastMessage(ToastTypes.ERROR, 'text', 'title');
14
+ expect(result.id).not.toBe(result2.id);
15
+ });
16
+
17
+ it('returns message object with ToastTypes.Error to color danger and iconType warning', () => {
18
+ const result = getToastMessage(ToastTypes.ERROR, 'text', 'title');
19
+ expect(result.color).toBe('danger');
20
+ expect(result.iconType).toBe('warning');
21
+ });
22
+
23
+ it('returns message object with ToastTypes.Error to color success and iconType check', () => {
24
+ const result = getToastMessage(ToastTypes.SUCCESS, 'text', 'title');
25
+ expect(result.color).toBe('success');
26
+ expect(result.iconType).toBe('check');
27
+ });
28
+ });
@@ -0,0 +1,35 @@
1
+ import { ToastTypes } from '../types';
2
+ import type { Toast } from '../types';
3
+
4
+ export const getToastMessage = (
5
+ type: ToastTypes,
6
+ text: string, // We use string here instead of Toast['text'] because we want to prevent passing in react component because they trigger an "unsynchronizable values in payload detected" error',
7
+ title: string, // same as above for string instead of Toast['title']
8
+ ) => {
9
+ const getTypeProps = (type: ToastTypes): Partial<Toast> => {
10
+ switch (type) {
11
+ case ToastTypes.ERROR:
12
+ return {
13
+ color: 'danger',
14
+ iconType: 'warning',
15
+ };
16
+ case ToastTypes.SUCCESS:
17
+ return {
18
+ color: 'success',
19
+ iconType: 'check',
20
+ };
21
+ }
22
+ };
23
+
24
+ const id = Math.floor(Math.random() * 10000000).toString();
25
+ const typeProps = getTypeProps(type);
26
+
27
+ const toast: Toast = {
28
+ id,
29
+ text,
30
+ title,
31
+ ...typeProps,
32
+ };
33
+
34
+ return toast;
35
+ };