@odigos/ui-kit 0.0.17 → 0.0.18
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 +7 -0
- package/lib/components.js +13 -44
- package/lib/constants.js +5 -9
- package/lib/containers.js +12 -49
- package/lib/functions.js +9 -15
- package/lib/hooks.js +10 -10
- package/lib/icons.js +3 -4
- package/lib/{index-Hz7AAE0t.js → index-7-KCQK-x.js} +1 -1
- package/lib/{index-BiNX-Cge.js → index-Bb7VdYPG.js} +78 -47
- package/lib/index-Bdimyacn.js +687 -0
- package/lib/{index-C3nz3TIx.js → index-BlJU2fGe.js} +5 -3
- package/lib/{index-BQW5EUgp.js → index-CT0qszYw.js} +6 -4
- package/lib/{index-BxQTUOME.js → index-CVs2NNg9.js} +5 -3
- package/lib/{index-CIXQeSHu.js → index-DGel4E-Z.js} +8 -1
- package/lib/{index-G4WmxXds.js → index-jPxFCX-5.js} +21 -4
- package/lib/store.js +3 -6
- package/lib/theme.js +3 -86
- package/lib/types.js +215 -6
- package/lib/useSourceSelectionFormData-LmLZzJyk.js +563 -0
- package/lib/{useTimeAgo-weEj7br6.js → useTransition-WRhgkuG2.js} +113 -544
- package/package.json +1 -2
- package/lib/index-B72aw6tI.js +0 -23
- package/lib/index-BQs4sULy.js +0 -32
- package/lib/index-BVVVevuY.js +0 -100
- package/lib/index-BWqrekK4.js +0 -11
- package/lib/index-C1PCuZgw.js +0 -18
- package/lib/index-CIgHU72d.js +0 -52
- package/lib/index-DbfrGXPH.js +0 -8
- package/lib/index-RBS1MqCQ.js +0 -37
- package/lib/react-CjImwkhV.js +0 -44
- package/lib/useDarkMode-DxhIuVNi.js +0 -201
- package/lib/useSelectedStore-93bIo1kE.js +0 -97
- package/lib/useSetupStore-CoYx1UQw.js +0 -211
- package/lib/useTransition-D0wUpPGk.js +0 -128
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import { ActionType, StatusType, AddNodeTypes, EntityTypes, FieldTypes } from './types.js';
|
|
2
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
3
|
+
import { f as useNotificationStore, e as useModalStore, a as useDrawerStore, b as useEntityStore, i as useSetupStore } from './index-Bdimyacn.js';
|
|
4
|
+
import 'styled-components';
|
|
5
|
+
import { i as isEmpty, s as safeJsonParse } from './index-BZS1ijMm.js';
|
|
6
|
+
import './index-jPxFCX-5.js';
|
|
7
|
+
import { F as FORM_ALERTS } from './index-C_0J5P9M.js';
|
|
8
|
+
import { b as useGenericForm } from './useTransition-WRhgkuG2.js';
|
|
9
|
+
import { g as getIdFromSseTarget } from './index-7-KCQK-x.js';
|
|
10
|
+
|
|
11
|
+
const INITIAL$2 = {
|
|
12
|
+
// @ts-ignore (TS complains about empty string because we expect an "ActionsType", but it's fine)
|
|
13
|
+
type: '',
|
|
14
|
+
name: '',
|
|
15
|
+
notes: '',
|
|
16
|
+
signals: [],
|
|
17
|
+
disabled: false,
|
|
18
|
+
clusterAttributes: null,
|
|
19
|
+
renames: null,
|
|
20
|
+
attributeNamesToDelete: null,
|
|
21
|
+
piiCategories: null,
|
|
22
|
+
fallbackSamplingRatio: null,
|
|
23
|
+
samplingPercentage: null,
|
|
24
|
+
endpointsFilters: null,
|
|
25
|
+
};
|
|
26
|
+
const useActionFormData = () => {
|
|
27
|
+
const { addNotification } = useNotificationStore();
|
|
28
|
+
const { formData, formErrors, handleFormChange, handleErrorChange, resetFormData } = useGenericForm(INITIAL$2);
|
|
29
|
+
const validateForm = (params) => {
|
|
30
|
+
const errors = {};
|
|
31
|
+
let ok = true;
|
|
32
|
+
Object.entries(formData).forEach(([k, v]) => {
|
|
33
|
+
switch (k) {
|
|
34
|
+
case 'type':
|
|
35
|
+
case 'signals':
|
|
36
|
+
if (isEmpty(v))
|
|
37
|
+
errors[k] = FORM_ALERTS.FIELD_IS_REQUIRED;
|
|
38
|
+
break;
|
|
39
|
+
case 'clusterAttributes':
|
|
40
|
+
if (formData.type === ActionType.AddClusterInfo && isEmpty(v))
|
|
41
|
+
errors[k] = FORM_ALERTS.FIELD_IS_REQUIRED;
|
|
42
|
+
break;
|
|
43
|
+
case 'renames':
|
|
44
|
+
if (formData.type === ActionType.RenameAttributes && isEmpty(v))
|
|
45
|
+
errors[k] = FORM_ALERTS.FIELD_IS_REQUIRED;
|
|
46
|
+
break;
|
|
47
|
+
case 'attributeNamesToDelete':
|
|
48
|
+
if (formData.type === ActionType.DeleteAttributes && isEmpty(v))
|
|
49
|
+
errors[k] = FORM_ALERTS.FIELD_IS_REQUIRED;
|
|
50
|
+
break;
|
|
51
|
+
case 'piiCategories':
|
|
52
|
+
if (formData.type === ActionType.PiiMasking && isEmpty(v))
|
|
53
|
+
errors[k] = FORM_ALERTS.FIELD_IS_REQUIRED;
|
|
54
|
+
break;
|
|
55
|
+
case 'fallbackSamplingRatio':
|
|
56
|
+
if (formData.type === ActionType.ErrorSampler && isEmpty(v))
|
|
57
|
+
errors[k] = FORM_ALERTS.FIELD_IS_REQUIRED;
|
|
58
|
+
break;
|
|
59
|
+
case 'samplingPercentage':
|
|
60
|
+
if (formData.type === ActionType.ProbabilisticSampler && isEmpty(v))
|
|
61
|
+
errors[k] = FORM_ALERTS.FIELD_IS_REQUIRED;
|
|
62
|
+
break;
|
|
63
|
+
case 'endpointsFilters':
|
|
64
|
+
if (formData.type === ActionType.LatencySampler) {
|
|
65
|
+
if (isEmpty(v))
|
|
66
|
+
errors[k] = FORM_ALERTS.FIELD_IS_REQUIRED;
|
|
67
|
+
v?.forEach((endpoint) => {
|
|
68
|
+
if (endpoint.httpRoute.charAt(0) !== '/')
|
|
69
|
+
errors[k] = FORM_ALERTS.LATENCY_HTTP_ROUTE;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
ok = !Object.values(errors).length;
|
|
76
|
+
if (!ok && params?.withAlert) {
|
|
77
|
+
addNotification({
|
|
78
|
+
type: StatusType.Warning,
|
|
79
|
+
title: params.alertTitle,
|
|
80
|
+
message: FORM_ALERTS.REQUIRED_FIELDS,
|
|
81
|
+
hideFromHistory: true,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
handleErrorChange(undefined, undefined, errors);
|
|
85
|
+
return ok;
|
|
86
|
+
};
|
|
87
|
+
const loadFormWithDrawerItem = ({ type, spec }) => {
|
|
88
|
+
const updatedData = {
|
|
89
|
+
...INITIAL$2,
|
|
90
|
+
type,
|
|
91
|
+
};
|
|
92
|
+
Object.entries(spec).forEach(([k, v]) => {
|
|
93
|
+
if (!!v) {
|
|
94
|
+
switch (k) {
|
|
95
|
+
case 'actionName': {
|
|
96
|
+
updatedData['name'] = v;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case 'type':
|
|
100
|
+
case 'notes':
|
|
101
|
+
case 'signals':
|
|
102
|
+
case 'disabled':
|
|
103
|
+
case 'collectContainerAttributes':
|
|
104
|
+
case 'collectWorkloadId':
|
|
105
|
+
case 'collectClusterId':
|
|
106
|
+
case 'labelsAttributes':
|
|
107
|
+
case 'annotationsAttributes':
|
|
108
|
+
case 'clusterAttributes':
|
|
109
|
+
case 'attributeNamesToDelete':
|
|
110
|
+
case 'renames':
|
|
111
|
+
case 'piiCategories':
|
|
112
|
+
case 'fallbackSamplingRatio':
|
|
113
|
+
case 'samplingPercentage':
|
|
114
|
+
case 'endpointsFilters': {
|
|
115
|
+
// @ts-ignore
|
|
116
|
+
updatedData[k] = v;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
handleFormChange(undefined, undefined, updatedData);
|
|
123
|
+
};
|
|
124
|
+
return {
|
|
125
|
+
formData,
|
|
126
|
+
formErrors,
|
|
127
|
+
handleFormChange,
|
|
128
|
+
resetFormData,
|
|
129
|
+
validateForm,
|
|
130
|
+
loadFormWithDrawerItem,
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const useClickNode = () => {
|
|
135
|
+
const { setCurrentModal } = useModalStore();
|
|
136
|
+
const { setDrawerType, setDrawerEntityId } = useDrawerStore();
|
|
137
|
+
const onClickNode = (_, object) => {
|
|
138
|
+
const { data: { id, type }, } = object;
|
|
139
|
+
switch (type) {
|
|
140
|
+
case EntityTypes.Source:
|
|
141
|
+
case EntityTypes.Destination:
|
|
142
|
+
case EntityTypes.Action:
|
|
143
|
+
case EntityTypes.InstrumentationRule:
|
|
144
|
+
setDrawerType(type);
|
|
145
|
+
setDrawerEntityId(id);
|
|
146
|
+
break;
|
|
147
|
+
case AddNodeTypes.AddSource:
|
|
148
|
+
setCurrentModal(EntityTypes.Source);
|
|
149
|
+
break;
|
|
150
|
+
case AddNodeTypes.AddDestination:
|
|
151
|
+
setCurrentModal(EntityTypes.Destination);
|
|
152
|
+
break;
|
|
153
|
+
case AddNodeTypes.AddAction:
|
|
154
|
+
setCurrentModal(EntityTypes.Action);
|
|
155
|
+
break;
|
|
156
|
+
case AddNodeTypes.AddRule:
|
|
157
|
+
setCurrentModal(EntityTypes.InstrumentationRule);
|
|
158
|
+
break;
|
|
159
|
+
default:
|
|
160
|
+
console.warn('Unhandled node click', object);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
return { onClickNode };
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const useClickNotification = () => {
|
|
168
|
+
const { setDrawerType, setDrawerEntityId } = useDrawerStore();
|
|
169
|
+
const { markAsDismissed, markAsSeen } = useNotificationStore();
|
|
170
|
+
const onClickNotification = (notif, options) => {
|
|
171
|
+
const { id, crdType, target } = notif;
|
|
172
|
+
const { dismissToast } = options || {};
|
|
173
|
+
if (crdType && target) {
|
|
174
|
+
switch (crdType) {
|
|
175
|
+
case EntityTypes.InstrumentationRule:
|
|
176
|
+
setDrawerType(EntityTypes.InstrumentationRule);
|
|
177
|
+
setDrawerEntityId(getIdFromSseTarget(target, EntityTypes.InstrumentationRule));
|
|
178
|
+
break;
|
|
179
|
+
case EntityTypes.Source:
|
|
180
|
+
case 'InstrumentationConfig':
|
|
181
|
+
case 'InstrumentationInstance':
|
|
182
|
+
setDrawerType(EntityTypes.Source);
|
|
183
|
+
setDrawerEntityId(getIdFromSseTarget(target, EntityTypes.Source));
|
|
184
|
+
break;
|
|
185
|
+
case EntityTypes.Action:
|
|
186
|
+
setDrawerType(EntityTypes.Action);
|
|
187
|
+
setDrawerEntityId(getIdFromSseTarget(target, EntityTypes.Action));
|
|
188
|
+
break;
|
|
189
|
+
case EntityTypes.Destination:
|
|
190
|
+
case 'Destination':
|
|
191
|
+
setDrawerType(EntityTypes.Destination);
|
|
192
|
+
setDrawerEntityId(getIdFromSseTarget(target, EntityTypes.Destination));
|
|
193
|
+
break;
|
|
194
|
+
default:
|
|
195
|
+
console.warn('notif click not handled for:', { crdType, target });
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
markAsSeen(id);
|
|
200
|
+
if (dismissToast)
|
|
201
|
+
markAsDismissed(id);
|
|
202
|
+
};
|
|
203
|
+
return { onClickNotification };
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const INITIAL$1 = {
|
|
207
|
+
// @ts-ignore form should be initialized with empty values
|
|
208
|
+
type: '',
|
|
209
|
+
name: '',
|
|
210
|
+
exportedSignals: {
|
|
211
|
+
logs: false,
|
|
212
|
+
metrics: false,
|
|
213
|
+
traces: false,
|
|
214
|
+
},
|
|
215
|
+
fields: [],
|
|
216
|
+
};
|
|
217
|
+
const buildFormDynamicFields = (fields) => {
|
|
218
|
+
return fields
|
|
219
|
+
.filter((f) => !!f)
|
|
220
|
+
.map((f) => {
|
|
221
|
+
const { name, componentType, componentProperties, displayName, initialValue, renderCondition } = f;
|
|
222
|
+
switch (componentType) {
|
|
223
|
+
case FieldTypes.Dropdown: {
|
|
224
|
+
const componentPropertiesJson = safeJsonParse(componentProperties, {});
|
|
225
|
+
const options = Array.isArray(componentPropertiesJson.values)
|
|
226
|
+
? componentPropertiesJson.values.map((value) => ({
|
|
227
|
+
id: value,
|
|
228
|
+
value,
|
|
229
|
+
}))
|
|
230
|
+
: Object.entries(componentPropertiesJson.values).map(([key, value]) => ({
|
|
231
|
+
id: key,
|
|
232
|
+
value,
|
|
233
|
+
}));
|
|
234
|
+
return {
|
|
235
|
+
name,
|
|
236
|
+
componentType: componentType,
|
|
237
|
+
title: displayName,
|
|
238
|
+
value: initialValue,
|
|
239
|
+
placeholder: componentPropertiesJson.placeholder || 'Select an option',
|
|
240
|
+
options,
|
|
241
|
+
renderCondition,
|
|
242
|
+
...componentPropertiesJson,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
default: {
|
|
246
|
+
const componentPropertiesJson = safeJsonParse(componentProperties, {});
|
|
247
|
+
return {
|
|
248
|
+
name,
|
|
249
|
+
componentType,
|
|
250
|
+
title: displayName,
|
|
251
|
+
value: initialValue,
|
|
252
|
+
renderCondition,
|
|
253
|
+
...componentPropertiesJson,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
const useDestinationFormData = (params) => {
|
|
260
|
+
const { supportedSignals, preLoadedFields } = params || {};
|
|
261
|
+
const { addNotification } = useNotificationStore();
|
|
262
|
+
const { formData, formErrors, handleFormChange, handleErrorChange, resetFormData } = useGenericForm(INITIAL$1);
|
|
263
|
+
const [yamlFields, setYamlFields] = useState([]);
|
|
264
|
+
const [dynamicFields, setDynamicFields] = useState([]);
|
|
265
|
+
useEffect(() => {
|
|
266
|
+
if (yamlFields) {
|
|
267
|
+
setDynamicFields(buildFormDynamicFields(yamlFields).map((field) => {
|
|
268
|
+
// if we have preloaded fields, we need to set the value of the field
|
|
269
|
+
// (this can be from an odigos-detected-destination during create, or from an existing destination during edit/update)
|
|
270
|
+
if (!!preLoadedFields) {
|
|
271
|
+
const parsedFields = typeof preLoadedFields === 'string' ? safeJsonParse(preLoadedFields, {}) : preLoadedFields;
|
|
272
|
+
if (field.name in parsedFields) {
|
|
273
|
+
return {
|
|
274
|
+
...field,
|
|
275
|
+
// @ts-ignore
|
|
276
|
+
value: parsedFields[field.name],
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return field;
|
|
281
|
+
}));
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
setDynamicFields([]);
|
|
285
|
+
}
|
|
286
|
+
}, [yamlFields, preLoadedFields]);
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
handleFormChange('fields', dynamicFields.map((field) => ({
|
|
289
|
+
key: field.name,
|
|
290
|
+
value: field.value,
|
|
291
|
+
})));
|
|
292
|
+
}, [dynamicFields]);
|
|
293
|
+
useEffect(() => {
|
|
294
|
+
const { logs, metrics, traces } = supportedSignals || {};
|
|
295
|
+
handleFormChange('exportedSignals', {
|
|
296
|
+
logs: logs?.supported || false,
|
|
297
|
+
metrics: metrics?.supported || false,
|
|
298
|
+
traces: traces?.supported || false,
|
|
299
|
+
});
|
|
300
|
+
}, [supportedSignals]);
|
|
301
|
+
const validateForm = (params) => {
|
|
302
|
+
const errors = {};
|
|
303
|
+
let ok = true;
|
|
304
|
+
dynamicFields.forEach(({ name, value, required }) => {
|
|
305
|
+
if (required && !value) {
|
|
306
|
+
ok = false;
|
|
307
|
+
errors[name] = FORM_ALERTS.FIELD_IS_REQUIRED;
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
if (!ok && params?.withAlert) {
|
|
311
|
+
addNotification({
|
|
312
|
+
type: StatusType.Warning,
|
|
313
|
+
title: params.alertTitle,
|
|
314
|
+
message: FORM_ALERTS.REQUIRED_FIELDS,
|
|
315
|
+
hideFromHistory: true,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
handleErrorChange(undefined, undefined, errors);
|
|
319
|
+
return ok;
|
|
320
|
+
};
|
|
321
|
+
const loadFormWithDrawerItem = ({ destinationType: { type }, name, exportedSignals, fields }) => {
|
|
322
|
+
const updatedData = {
|
|
323
|
+
...INITIAL$1,
|
|
324
|
+
type,
|
|
325
|
+
name,
|
|
326
|
+
exportedSignals,
|
|
327
|
+
fields: Object.entries(safeJsonParse(fields, {})).map(([key, value]) => ({ key, value })),
|
|
328
|
+
};
|
|
329
|
+
handleFormChange(undefined, undefined, updatedData);
|
|
330
|
+
};
|
|
331
|
+
return {
|
|
332
|
+
formData,
|
|
333
|
+
formErrors,
|
|
334
|
+
handleFormChange,
|
|
335
|
+
resetFormData,
|
|
336
|
+
validateForm,
|
|
337
|
+
loadFormWithDrawerItem,
|
|
338
|
+
yamlFields,
|
|
339
|
+
setYamlFields,
|
|
340
|
+
dynamicFields,
|
|
341
|
+
setDynamicFields,
|
|
342
|
+
};
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const INITIAL = {
|
|
346
|
+
otelServiceName: '',
|
|
347
|
+
};
|
|
348
|
+
const useSourceFormData = () => {
|
|
349
|
+
const { addNotification } = useNotificationStore();
|
|
350
|
+
const { formData, formErrors, handleFormChange, handleErrorChange, resetFormData } = useGenericForm(INITIAL);
|
|
351
|
+
const validateForm = (params) => {
|
|
352
|
+
const errors = {};
|
|
353
|
+
let ok = true;
|
|
354
|
+
handleErrorChange(undefined, undefined, errors);
|
|
355
|
+
return ok;
|
|
356
|
+
};
|
|
357
|
+
const loadFormWithDrawerItem = ({ otelServiceName, name }) => {
|
|
358
|
+
const updatedData = {
|
|
359
|
+
...INITIAL,
|
|
360
|
+
otelServiceName: otelServiceName || name || '',
|
|
361
|
+
};
|
|
362
|
+
handleFormChange(undefined, undefined, updatedData);
|
|
363
|
+
};
|
|
364
|
+
return {
|
|
365
|
+
formData,
|
|
366
|
+
formErrors,
|
|
367
|
+
handleFormChange,
|
|
368
|
+
resetFormData,
|
|
369
|
+
validateForm,
|
|
370
|
+
loadFormWithDrawerItem,
|
|
371
|
+
};
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const useSourceSelectionFormData = (params) => {
|
|
375
|
+
const { namespaces } = useEntityStore();
|
|
376
|
+
const { selectedNamespace, onSelectNamespace, namespace } = params || {};
|
|
377
|
+
// only for "onboarding" - get unsaved values and set to state
|
|
378
|
+
// (this is to persist the values when user navigates back to this page)
|
|
379
|
+
const { configuredSources, configuredFutureApps, availableSources } = useSetupStore();
|
|
380
|
+
// Keeps intial values fetched from API, so we can later filter the user-specific-selections, therebey minimizing the amount of data sent to the API on "persist sources".
|
|
381
|
+
const [recordedInitialSources, setRecordedInitialSources] = useState(availableSources);
|
|
382
|
+
const [selectAllForNamespace, setSelectAllForNamespace] = useState('');
|
|
383
|
+
const [selectedSources, setSelectedSources] = useState(configuredSources);
|
|
384
|
+
const [selectedFutureApps, setSelectedFutureApps] = useState(configuredFutureApps);
|
|
385
|
+
useEffect(() => {
|
|
386
|
+
if (!!namespaces?.length) {
|
|
387
|
+
// initialize all states (to avoid undefined errors)
|
|
388
|
+
setRecordedInitialSources((prev) => {
|
|
389
|
+
const payload = { ...prev };
|
|
390
|
+
namespaces.forEach(({ name }) => (payload[name] = payload[name] || []));
|
|
391
|
+
return payload;
|
|
392
|
+
});
|
|
393
|
+
setSelectedSources((prev) => {
|
|
394
|
+
const payload = { ...prev };
|
|
395
|
+
namespaces.forEach(({ name }) => (payload[name] = payload[name] || []));
|
|
396
|
+
return payload;
|
|
397
|
+
});
|
|
398
|
+
setSelectedFutureApps((prev) => {
|
|
399
|
+
const payload = { ...prev };
|
|
400
|
+
namespaces.forEach(({ name, selected }) => (payload[name] = payload[name] || selected || false));
|
|
401
|
+
return payload;
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}, [namespaces]);
|
|
405
|
+
useEffect(() => {
|
|
406
|
+
if (!!namespace) {
|
|
407
|
+
// initialize sources for this namespace
|
|
408
|
+
const { name, sources = [] } = namespace;
|
|
409
|
+
setRecordedInitialSources((prev) => ({
|
|
410
|
+
...prev,
|
|
411
|
+
[name]: sources.map(({ name, kind, selected, numberOfInstances }) => ({
|
|
412
|
+
name,
|
|
413
|
+
kind,
|
|
414
|
+
selected,
|
|
415
|
+
numberOfInstances,
|
|
416
|
+
})),
|
|
417
|
+
}));
|
|
418
|
+
setSelectedSources((prev) => ({
|
|
419
|
+
...prev,
|
|
420
|
+
[name]: !!prev[name].length
|
|
421
|
+
? prev[name]
|
|
422
|
+
: sources.map(({ name, kind, selected, numberOfInstances }) => ({
|
|
423
|
+
name,
|
|
424
|
+
kind,
|
|
425
|
+
selected,
|
|
426
|
+
numberOfInstances,
|
|
427
|
+
})),
|
|
428
|
+
}));
|
|
429
|
+
}
|
|
430
|
+
}, [namespace]);
|
|
431
|
+
// form filters
|
|
432
|
+
const [searchText, setSearchText] = useState('');
|
|
433
|
+
const [showSelectedOnly, setShowSelectedOnly] = useState(false);
|
|
434
|
+
const onSelectAll = useCallback((selected, ns, selectionsByNamespace) => {
|
|
435
|
+
// When clicking "select all" on a single namespace
|
|
436
|
+
if (!!ns) {
|
|
437
|
+
if (!selectionsByNamespace) {
|
|
438
|
+
// If the sources are not loaded yet, call the onSelectNamespace to load the sources
|
|
439
|
+
onSelectNamespace?.(selected ? ns : '');
|
|
440
|
+
// Set the state, so the interval would be able to use the namespace
|
|
441
|
+
setSelectAllForNamespace(selected ? ns : '');
|
|
442
|
+
}
|
|
443
|
+
else if (!!selectionsByNamespace?.[ns]?.length) {
|
|
444
|
+
// Clear the state, so the interval would stop
|
|
445
|
+
setSelectAllForNamespace('');
|
|
446
|
+
}
|
|
447
|
+
// Set the selected sources
|
|
448
|
+
setSelectedSources((prev) => ({
|
|
449
|
+
...prev,
|
|
450
|
+
[ns]: selectionsByNamespace?.[ns]?.map((source) => ({
|
|
451
|
+
...source,
|
|
452
|
+
selected,
|
|
453
|
+
})) || [],
|
|
454
|
+
}));
|
|
455
|
+
// setSelectedFutureApps((prev) => ({
|
|
456
|
+
// ...prev,
|
|
457
|
+
// [ns]: !!selectionsByNamespace?.[ns]?.length ? selected : false,
|
|
458
|
+
// }))
|
|
459
|
+
}
|
|
460
|
+
// When clicking "select all" on all namespaces
|
|
461
|
+
else {
|
|
462
|
+
setSelectedSources((prev) => {
|
|
463
|
+
const payload = { ...prev };
|
|
464
|
+
Object.entries(payload).forEach(([key, sources]) => {
|
|
465
|
+
payload[key] = sources.map((source) => ({ ...source, selected }));
|
|
466
|
+
});
|
|
467
|
+
return payload;
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}, [selectedSources]);
|
|
471
|
+
// This is to keep trying "select all" per namespace, until the sources are loaded (allows for 1-click, better UX).
|
|
472
|
+
useEffect(() => {
|
|
473
|
+
if (!!selectAllForNamespace) {
|
|
474
|
+
const interval = setInterval(() => onSelectAll(true, selectAllForNamespace, selectedSources), 100);
|
|
475
|
+
return () => clearInterval(interval);
|
|
476
|
+
}
|
|
477
|
+
}, [selectAllForNamespace, onSelectAll]);
|
|
478
|
+
const onSelectSource = (source, namespace) => {
|
|
479
|
+
const id = namespace || selectedNamespace;
|
|
480
|
+
if (!id)
|
|
481
|
+
return;
|
|
482
|
+
const arr = [...(selectedSources[id] || [])];
|
|
483
|
+
const foundIdx = arr.findIndex(({ name, kind }) => name === source.name && kind === source.kind);
|
|
484
|
+
if (foundIdx !== -1) {
|
|
485
|
+
// Replace the item with a new object to avoid mutating a possibly read-only object
|
|
486
|
+
const updatedItem = { ...arr[foundIdx], selected: !arr[foundIdx].selected };
|
|
487
|
+
arr[foundIdx] = updatedItem;
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
arr.push({ ...source, selected: true });
|
|
491
|
+
}
|
|
492
|
+
setSelectedSources((prev) => ({ ...prev, [id]: arr }));
|
|
493
|
+
};
|
|
494
|
+
const onSelectFutureApps = (bool, namespace) => {
|
|
495
|
+
const id = namespace || selectedNamespace;
|
|
496
|
+
if (!id)
|
|
497
|
+
return;
|
|
498
|
+
setSelectedFutureApps((prev) => ({ ...prev, [id]: bool }));
|
|
499
|
+
};
|
|
500
|
+
const filterNamespaces = (options) => {
|
|
501
|
+
const { cancelSearch } = options || {};
|
|
502
|
+
const namespaces = Object.entries(selectedSources);
|
|
503
|
+
const isSearchOk = (targetText) => cancelSearch || !searchText || targetText.toLowerCase().includes(searchText);
|
|
504
|
+
return namespaces.filter(([namespace]) => isSearchOk(namespace));
|
|
505
|
+
};
|
|
506
|
+
const filterSources = (namespace, options) => {
|
|
507
|
+
const { cancelSearch, cancelSelected } = options || {};
|
|
508
|
+
const id = namespace || selectedNamespace;
|
|
509
|
+
if (!id)
|
|
510
|
+
return [];
|
|
511
|
+
const isSearchOk = (targetText) => cancelSearch || !searchText || targetText.toLowerCase().includes(searchText);
|
|
512
|
+
const isOnlySelectedOk = (sources, compareKey, target) => cancelSelected || !showSelectedOnly || !!sources.find((item) => item[compareKey] === target && item.selected);
|
|
513
|
+
return selectedSources[id].filter((source) => isSearchOk(source.name) && isOnlySelectedOk(selectedSources[id], 'name', source.name));
|
|
514
|
+
};
|
|
515
|
+
// This is to filter the user-specific-selections, therebey minimizing the amount of data sent to the API on "persist sources".
|
|
516
|
+
const getApiSourcesPayload = () => {
|
|
517
|
+
const payload = {};
|
|
518
|
+
Object.entries(selectedSources).forEach(([namespace, sources]) => {
|
|
519
|
+
sources.forEach((source) => {
|
|
520
|
+
const foundInitial = recordedInitialSources[namespace]?.find((initialSource) => initialSource.name === source.name && initialSource.kind === source.kind);
|
|
521
|
+
if (foundInitial?.selected !== source.selected) {
|
|
522
|
+
if (!payload[namespace])
|
|
523
|
+
payload[namespace] = [];
|
|
524
|
+
payload[namespace].push({
|
|
525
|
+
name: source.name,
|
|
526
|
+
kind: source.kind,
|
|
527
|
+
selected: source.selected,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
return payload;
|
|
533
|
+
};
|
|
534
|
+
// This is to filter the user-specific-selections, therebey minimizing the amount of data sent to the API on "persist namespaces".
|
|
535
|
+
const getApiFutureAppsPayload = () => {
|
|
536
|
+
const payload = {};
|
|
537
|
+
Object.entries(selectedFutureApps).forEach(([namespace, selected]) => {
|
|
538
|
+
const foundInitial = namespaces?.find((ns) => ns.name === namespace);
|
|
539
|
+
if (foundInitial?.selected !== selected) {
|
|
540
|
+
payload[namespace] = selected;
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
return payload;
|
|
544
|
+
};
|
|
545
|
+
return {
|
|
546
|
+
recordedInitialSources,
|
|
547
|
+
filterNamespaces,
|
|
548
|
+
filterSources,
|
|
549
|
+
getApiSourcesPayload,
|
|
550
|
+
getApiFutureAppsPayload,
|
|
551
|
+
selectedSources,
|
|
552
|
+
onSelectSource,
|
|
553
|
+
selectedFutureApps,
|
|
554
|
+
onSelectFutureApps,
|
|
555
|
+
onSelectAll,
|
|
556
|
+
searchText,
|
|
557
|
+
setSearchText,
|
|
558
|
+
showSelectedOnly,
|
|
559
|
+
setShowSelectedOnly,
|
|
560
|
+
};
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
export { useClickNode as a, useClickNotification as b, useDestinationFormData as c, useSourceFormData as d, useSourceSelectionFormData as e, useActionFormData as u };
|