@finos/legend-application-studio 28.15.4 → 28.15.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. package/lib/components/editor/editor-group/EditorGroup.d.ts.map +1 -1
  2. package/lib/components/editor/editor-group/EditorGroup.js +5 -0
  3. package/lib/components/editor/editor-group/EditorGroup.js.map +1 -1
  4. package/lib/components/editor/editor-group/UnsupportedElementEditor.d.ts +2 -0
  5. package/lib/components/editor/editor-group/UnsupportedElementEditor.d.ts.map +1 -1
  6. package/lib/components/editor/editor-group/UnsupportedElementEditor.js +12 -3
  7. package/lib/components/editor/editor-group/UnsupportedElementEditor.js.map +1 -1
  8. package/lib/components/editor/editor-group/function-activator/FunctionEditor.d.ts +1 -1
  9. package/lib/components/editor/editor-group/function-activator/FunctionEditor.d.ts.map +1 -1
  10. package/lib/components/editor/editor-group/function-activator/FunctionEditor.js +6 -4
  11. package/lib/components/editor/editor-group/function-activator/FunctionEditor.js.map +1 -1
  12. package/lib/components/editor/editor-group/function-activator/HostedServiceFunctionActivatorEditor.d.ts +19 -0
  13. package/lib/components/editor/editor-group/function-activator/HostedServiceFunctionActivatorEditor.d.ts.map +1 -0
  14. package/lib/components/editor/editor-group/function-activator/HostedServiceFunctionActivatorEditor.js +175 -0
  15. package/lib/components/editor/editor-group/function-activator/HostedServiceFunctionActivatorEditor.js.map +1 -0
  16. package/lib/components/editor/editor-group/function-activator/SnowflakeAppFunctionActivatorEditor.d.ts.map +1 -1
  17. package/lib/components/editor/editor-group/function-activator/SnowflakeAppFunctionActivatorEditor.js +18 -5
  18. package/lib/components/editor/editor-group/function-activator/SnowflakeAppFunctionActivatorEditor.js.map +1 -1
  19. package/lib/components/project-reviewer/ProjectReviewSideBar.js +1 -1
  20. package/lib/components/project-reviewer/ProjectReviewSideBar.js.map +1 -1
  21. package/lib/components/project-reviewer/ProjectReviewer.js +1 -1
  22. package/lib/components/project-reviewer/ProjectReviewer.js.map +1 -1
  23. package/lib/index.css +2 -2
  24. package/lib/index.css.map +1 -1
  25. package/lib/package.json +1 -1
  26. package/lib/stores/editor/EditorGraphState.d.ts.map +1 -1
  27. package/lib/stores/editor/EditorGraphState.js +4 -1
  28. package/lib/stores/editor/EditorGraphState.js.map +1 -1
  29. package/lib/stores/editor/EditorTabManagerState.d.ts.map +1 -1
  30. package/lib/stores/editor/EditorTabManagerState.js +5 -1
  31. package/lib/stores/editor/EditorTabManagerState.js.map +1 -1
  32. package/lib/stores/editor/editor-state/element-editor-state/ElementEditorState.d.ts.map +1 -1
  33. package/lib/stores/editor/editor-state/element-editor-state/ElementEditorState.js +10 -3
  34. package/lib/stores/editor/editor-state/element-editor-state/ElementEditorState.js.map +1 -1
  35. package/lib/stores/editor/editor-state/element-editor-state/FunctionActivatorState.d.ts.map +1 -1
  36. package/lib/stores/editor/editor-state/element-editor-state/FunctionActivatorState.js +17 -2
  37. package/lib/stores/editor/editor-state/element-editor-state/FunctionActivatorState.js.map +1 -1
  38. package/lib/stores/editor/editor-state/element-editor-state/function-activator/HostedServiceFunctionActivatorEditorState.d.ts +53 -0
  39. package/lib/stores/editor/editor-state/element-editor-state/function-activator/HostedServiceFunctionActivatorEditorState.d.ts.map +1 -0
  40. package/lib/stores/editor/editor-state/element-editor-state/function-activator/HostedServiceFunctionActivatorEditorState.js +150 -0
  41. package/lib/stores/editor/editor-state/element-editor-state/function-activator/HostedServiceFunctionActivatorEditorState.js.map +1 -0
  42. package/lib/stores/editor/utils/ModelClassifierUtils.d.ts +1 -0
  43. package/lib/stores/editor/utils/ModelClassifierUtils.d.ts.map +1 -1
  44. package/lib/stores/editor/utils/ModelClassifierUtils.js +1 -0
  45. package/lib/stores/editor/utils/ModelClassifierUtils.js.map +1 -1
  46. package/lib/stores/graph-modifier/DSL_FunctionActivator_GraphModifierHelper.d.ts +28 -0
  47. package/lib/stores/graph-modifier/DSL_FunctionActivator_GraphModifierHelper.d.ts.map +1 -0
  48. package/lib/stores/graph-modifier/DSL_FunctionActivator_GraphModifierHelper.js +55 -0
  49. package/lib/stores/graph-modifier/DSL_FunctionActivator_GraphModifierHelper.js.map +1 -0
  50. package/lib/stores/project-reviewer/ProjectReviewerStore.d.ts +9 -7
  51. package/lib/stores/project-reviewer/ProjectReviewerStore.d.ts.map +1 -1
  52. package/lib/stores/project-reviewer/ProjectReviewerStore.js +58 -36
  53. package/lib/stores/project-reviewer/ProjectReviewerStore.js.map +1 -1
  54. package/package.json +5 -5
  55. package/src/components/editor/editor-group/EditorGroup.tsx +10 -0
  56. package/src/components/editor/editor-group/UnsupportedElementEditor.tsx +38 -9
  57. package/src/components/editor/editor-group/function-activator/FunctionEditor.tsx +10 -6
  58. package/src/components/editor/editor-group/function-activator/HostedServiceFunctionActivatorEditor.tsx +631 -0
  59. package/src/components/editor/editor-group/function-activator/SnowflakeAppFunctionActivatorEditor.tsx +51 -3
  60. package/src/components/project-reviewer/ProjectReviewSideBar.tsx +1 -1
  61. package/src/components/project-reviewer/ProjectReviewer.tsx +1 -1
  62. package/src/stores/editor/EditorGraphState.ts +3 -0
  63. package/src/stores/editor/EditorTabManagerState.ts +7 -0
  64. package/src/stores/editor/editor-state/element-editor-state/ElementEditorState.ts +22 -5
  65. package/src/stores/editor/editor-state/element-editor-state/FunctionActivatorState.ts +17 -1
  66. package/src/stores/editor/editor-state/element-editor-state/function-activator/HostedServiceFunctionActivatorEditorState.ts +212 -0
  67. package/src/stores/editor/utils/ModelClassifierUtils.ts +1 -0
  68. package/src/stores/graph-modifier/DSL_FunctionActivator_GraphModifierHelper.ts +96 -0
  69. package/src/stores/project-reviewer/ProjectReviewerStore.ts +85 -60
  70. package/tsconfig.json +3 -0
@@ -0,0 +1,631 @@
1
+ /**
2
+ * Copyright (c) 2020-present, Goldman Sachs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import {
18
+ Panel,
19
+ PanelHeader,
20
+ PanelContent,
21
+ PanelForm,
22
+ PURE_FunctionIcon,
23
+ LongArrowRightIcon,
24
+ PanelLoadingIndicator,
25
+ PanelFormBooleanField,
26
+ PanelFormValidatedTextField,
27
+ TimesIcon,
28
+ CustomSelectorInput,
29
+ PencilIcon,
30
+ ErrorIcon,
31
+ } from '@finos/legend-art';
32
+ import {
33
+ DeploymentOwner,
34
+ UserList,
35
+ generateFunctionPrettyName,
36
+ validate_ServicePattern,
37
+ } from '@finos/legend-graph';
38
+ import { observer } from 'mobx-react-lite';
39
+ import { useApplicationStore } from '@finos/legend-application';
40
+ import { useEditorStore } from '../../EditorStoreProvider.js';
41
+ import {
42
+ HostedServiceFunctionActivatorEditorState,
43
+ OWNERSHIP_OPTIONS,
44
+ type HostedServiceOwnerOption,
45
+ MINIMUM_HOSTED_SERVICE_OWNERS,
46
+ } from '../../../../stores/editor/editor-state/element-editor-state/function-activator/HostedServiceFunctionActivatorEditorState.js';
47
+ import {
48
+ hostedService_setAutoActivateUpdates,
49
+ hostedService_setDocumentation,
50
+ hostedService_setPattern,
51
+ hostedService_removePatternParameter,
52
+ hostedService_setStoreModel,
53
+ hostedService_setGenerateLineage,
54
+ activator_setDeploymentOwner,
55
+ activator_updateUserOwnership,
56
+ activator_deleteValueFromUserOwnership,
57
+ activator_addUserOwner,
58
+ } from '../../../../stores/graph-modifier/DSL_FunctionActivator_GraphModifierHelper.js';
59
+ import { useEffect, useMemo, useRef, useState } from 'react';
60
+ import { LEGEND_STUDIO_TEST_ID } from '../../../../__lib__/LegendStudioTesting.js';
61
+ import { debounce } from '@finos/legend-shared';
62
+ import { flowResult } from 'mobx';
63
+
64
+ type UserOption = { label: string; value: string };
65
+
66
+ export const HostedServiceFunctionActivatorEditor = observer(() => {
67
+ const editorStore = useEditorStore();
68
+ const applicationStore = useApplicationStore();
69
+ const editorState = editorStore.tabManagerState.getCurrentEditorState(
70
+ HostedServiceFunctionActivatorEditorState,
71
+ );
72
+ const isReadOnly = editorState.isReadOnly;
73
+ const activator = editorState.activator;
74
+ const ownership = activator.ownership;
75
+ const visitFunction = (): void =>
76
+ editorState.editorStore.graphEditorMode.openElement(
77
+ activator.function.value,
78
+ );
79
+ const validate = (): void => {
80
+ flowResult(editorState.validate()).catch(
81
+ applicationStore.alertUnhandledError,
82
+ );
83
+ };
84
+ const deploy = (): void => {
85
+ flowResult(editorState.deployToSandbox()).catch(
86
+ applicationStore.alertUnhandledError,
87
+ );
88
+ };
89
+
90
+ const changeDocumentation: React.ChangeEventHandler<HTMLTextAreaElement> = (
91
+ event,
92
+ ) => {
93
+ if (!isReadOnly) {
94
+ hostedService_setDocumentation(activator, event.target.value);
95
+ }
96
+ };
97
+
98
+ const toggleUseStoreModel = (): void => {
99
+ hostedService_setStoreModel(activator, !activator.storeModel);
100
+ };
101
+
102
+ const toggleGenerateLineage = (): void => {
103
+ hostedService_setGenerateLineage(activator, !activator.generateLineage);
104
+ };
105
+
106
+ const toggleAutoActivateUpdates = (): void => {
107
+ hostedService_setAutoActivateUpdates(
108
+ activator,
109
+ !activator.autoActivateUpdates,
110
+ );
111
+ };
112
+
113
+ const getValidationMessage = (inputPattern: string): string | undefined => {
114
+ const patternValidationResult = validate_ServicePattern(inputPattern);
115
+ return patternValidationResult
116
+ ? patternValidationResult.messages[0]
117
+ : undefined;
118
+ };
119
+
120
+ //Ownership
121
+ const [showOwnerEditInput, setShowOwnerEditInput] = useState<
122
+ boolean | number
123
+ >(false);
124
+ const [ownerInputValue, setOwnerInputValue] = useState<string>('');
125
+ const [searchText, setSearchText] = useState('');
126
+ const [userOptions, setUserOptions] = useState<UserOption[]>([]);
127
+ const [isLoadingUsers, setIsLoadingUsers] = useState<boolean>(false);
128
+ const [ownerInputs, setOwnerInputs] = useState<string[]>([]);
129
+ const showAddOwnerInput = (): void => setShowOwnerEditInput(true);
130
+
131
+ const onOwnershipChange = (
132
+ val: HostedServiceOwnerOption | undefined,
133
+ ): void => {
134
+ if (val) {
135
+ editorState.setSelectedOwnership(val);
136
+ }
137
+ };
138
+
139
+ const updateDeploymentIdentifier: React.ChangeEventHandler<
140
+ HTMLInputElement
141
+ > = (event) => {
142
+ if (!isReadOnly && ownership instanceof DeploymentOwner) {
143
+ activator_setDeploymentOwner(ownership, event.target.value);
144
+ }
145
+ };
146
+
147
+ const changeUserOwnerInputValue: React.ChangeEventHandler<
148
+ HTMLInputElement
149
+ > = (event) => setOwnerInputValue(event.target.value);
150
+
151
+ const updateUser =
152
+ (idx: number): (() => void) =>
153
+ (): void => {
154
+ if (
155
+ ownerInputValue &&
156
+ ownership instanceof UserList &&
157
+ !ownership.users.includes(ownerInputValue)
158
+ ) {
159
+ activator_updateUserOwnership(ownership, ownerInputValue, idx);
160
+ }
161
+ };
162
+
163
+ const showEditOwnerInput =
164
+ (value: string, idx: number): (() => void) =>
165
+ (): void => {
166
+ setOwnerInputValue(value);
167
+ setShowOwnerEditInput(idx);
168
+ };
169
+
170
+ const hideAddOrEditOwnerInput = (): void => {
171
+ setShowOwnerEditInput(false);
172
+ setOwnerInputValue('');
173
+ };
174
+
175
+ const deleteUser =
176
+ (idx: number): (() => void) =>
177
+ (): void => {
178
+ if (!isReadOnly && ownership instanceof UserList) {
179
+ activator_deleteValueFromUserOwnership(ownership, idx);
180
+ if (
181
+ typeof showOwnerEditInput === 'number' &&
182
+ showOwnerEditInput > idx
183
+ ) {
184
+ setShowOwnerEditInput(showOwnerEditInput - 1);
185
+ }
186
+ }
187
+ };
188
+
189
+ const debouncedSearchUsers = useMemo(
190
+ () =>
191
+ debounce((input: string): void => {
192
+ setIsLoadingUsers(true);
193
+ flowResult(editorState.searchUsers(input))
194
+ .then((users) =>
195
+ setUserOptions(
196
+ users.map((u) => ({
197
+ value: u.userId,
198
+ label: u.userId,
199
+ })),
200
+ ),
201
+ )
202
+ .then(() => setIsLoadingUsers(false))
203
+ .catch(editorState.editorStore.applicationStore.alertUnhandledError);
204
+ }, 500),
205
+ [editorState],
206
+ );
207
+
208
+ const onSearchTextChange = (value: string): void => {
209
+ if (value !== searchText) {
210
+ setSearchText(value);
211
+ debouncedSearchUsers.cancel();
212
+ if (value.length >= 3) {
213
+ debouncedSearchUsers(value);
214
+ } else if (value.length === 0) {
215
+ setUserOptions([]);
216
+ setIsLoadingUsers(false);
217
+ }
218
+ }
219
+ };
220
+
221
+ const onUserOptionChange = (options: UserOption[]): void => {
222
+ setOwnerInputs(options.map((op) => op.label));
223
+ setUserOptions([]);
224
+ debouncedSearchUsers.cancel();
225
+ setIsLoadingUsers(false);
226
+ };
227
+
228
+ const addUser = (): void => {
229
+ ownerInputs.forEach((value) => {
230
+ if (
231
+ value &&
232
+ ownership instanceof UserList &&
233
+ !ownership.users.includes(value)
234
+ ) {
235
+ activator_addUserOwner(ownership, value);
236
+ }
237
+ });
238
+ hideAddOrEditOwnerInput();
239
+ };
240
+
241
+ //Pattern
242
+ const patternRef = useRef<HTMLInputElement>(null);
243
+ const [pattern, setPattern] = useState(activator.pattern);
244
+
245
+ const updatePattern = (newPattern: string): void => {
246
+ if (!isReadOnly) {
247
+ hostedService_setPattern(activator, newPattern);
248
+ }
249
+ };
250
+
251
+ const removePatternParameter =
252
+ (val: string): (() => void) =>
253
+ (): void => {
254
+ hostedService_removePatternParameter(activator, val);
255
+ setPattern(activator.pattern);
256
+ };
257
+
258
+ useEffect(() => {
259
+ patternRef.current?.focus();
260
+ }, [editorState]);
261
+
262
+ return (
263
+ <div className="hosted-service-function-activator-editor">
264
+ <Panel>
265
+ <PanelHeader title="Rest Service Application" />
266
+ <PanelLoadingIndicator
267
+ isLoading={Boolean(
268
+ editorState.validateState.isInProgress ||
269
+ editorState.deployState.isInProgress,
270
+ )}
271
+ />
272
+ <PanelContent>
273
+ <div className="hosted-service-function-activator-editor__header">
274
+ <div className="hosted-service-function-activator-editor__header__label">
275
+ Rest Service Activator
276
+ </div>
277
+ <div className="hosted-service-function-activator-editor__header__actions">
278
+ <button
279
+ className="hosted-service-function-activator-editor__header__actions__action hosted-service-function-activator-editor__header__actions__action--primary"
280
+ onClick={validate}
281
+ disabled={editorState.validateState.isInProgress}
282
+ tabIndex={-1}
283
+ title="Click Validate to verify your activator before deployment"
284
+ >
285
+ Validate
286
+ </button>
287
+ <button
288
+ className="hosted-service-function-activator-editor__header__actions__action hosted-service-function-activator-editor__header__actions__action--primary"
289
+ onClick={deploy}
290
+ disabled={editorState.deployState.isInProgress}
291
+ title="Deploy to sandbox"
292
+ tabIndex={-1}
293
+ >
294
+ Deploy to Sandbox
295
+ </button>
296
+ </div>
297
+ </div>
298
+ <PanelForm>
299
+ <PanelFormValidatedTextField
300
+ ref={patternRef}
301
+ name="URL Pattern"
302
+ isReadOnly={isReadOnly}
303
+ className="service-editor__pattern__input"
304
+ errorMessageClassName="service-editor__pattern__input"
305
+ prompt={
306
+ <>
307
+ Specifies the URL pattern of the service (e.g. /myService/
308
+ <span className="service-editor__pattern__example__param">{`{param}`}</span>
309
+ )
310
+ </>
311
+ }
312
+ update={(value: string | undefined): void => {
313
+ updatePattern(value ?? '');
314
+ }}
315
+ validate={getValidationMessage}
316
+ value={pattern}
317
+ />
318
+ </PanelForm>
319
+ <PanelForm>
320
+ <div className="panel__content__form__section service-editor__parameters">
321
+ <div className="panel__content__form__section__header__label">
322
+ Parameters
323
+ </div>
324
+ <div className="panel__content__form__section__header__prompt">
325
+ URL parameters (each must be surrounded by curly braces) will be
326
+ passed as arguments for the execution query. Note that if the
327
+ service is configured to use multi-execution, one of the URL
328
+ parameters must be chosen as the execution key.
329
+ </div>
330
+ <div className="service-editor__parameters__list">
331
+ {!activator.patternParameters.length && (
332
+ <div className="service-editor__parameters__list__empty">
333
+ No parameter
334
+ </div>
335
+ )}
336
+ {Boolean(activator.patternParameters.length) &&
337
+ activator.patternParameters.map((parameter) => (
338
+ <div key={parameter} className="service-editor__parameter">
339
+ <div className="service-editor__parameter__text">
340
+ {parameter}
341
+ </div>
342
+ <div className="service-editor__parameter__actions">
343
+ <button
344
+ className="service-editor__parameter__action"
345
+ disabled={isReadOnly}
346
+ onClick={removePatternParameter(parameter)}
347
+ title="Remove parameter"
348
+ tabIndex={-1}
349
+ >
350
+ <TimesIcon />
351
+ </button>
352
+ </div>
353
+ </div>
354
+ ))}
355
+ </div>
356
+ </div>
357
+ </PanelForm>
358
+ <PanelForm>
359
+ <div className="panel__content__form__section">
360
+ <div className="panel__content__form__section__header__label">
361
+ Function
362
+ </div>
363
+ </div>
364
+ <div className="hosted-service-function-activator-editor__configuration__items">
365
+ <div className="hosted-service-function-activator-editor__configuration__item">
366
+ <div className="btn--sm hosted-service-function-activator-editor__configuration__item__label">
367
+ <PURE_FunctionIcon />
368
+ </div>
369
+ <input
370
+ className="panel__content__form__section__input"
371
+ spellCheck={false}
372
+ disabled={true}
373
+ value={generateFunctionPrettyName(activator.function.value, {
374
+ fullPath: true,
375
+ spacing: false,
376
+ })}
377
+ />
378
+ <button
379
+ className="btn--dark btn--sm hosted-service-function-activator-editor__configuration__item__btn"
380
+ onClick={visitFunction}
381
+ tabIndex={-1}
382
+ title="See Function"
383
+ >
384
+ <LongArrowRightIcon />
385
+ </button>
386
+ </div>
387
+ </div>
388
+ </PanelForm>
389
+ <PanelForm>
390
+ <div className="panel__content__form__section">
391
+ <div className="panel__content__form__section__header__label">
392
+ Documentation
393
+ </div>
394
+ <div className="panel__content__form__section__header__prompt">{`Provide a brief description of the service's functionalities and usage`}</div>
395
+ <textarea
396
+ className="panel__content__form__section__textarea service-editor__documentation__input"
397
+ spellCheck={false}
398
+ disabled={isReadOnly}
399
+ value={activator.documentation}
400
+ onChange={changeDocumentation}
401
+ />
402
+ </div>
403
+ </PanelForm>
404
+ <PanelForm>
405
+ <PanelFormBooleanField
406
+ isReadOnly={isReadOnly}
407
+ value={activator.autoActivateUpdates}
408
+ name="Auto Activate Updates"
409
+ prompt="Specifies if the new generation should be automatically activated;
410
+ only valid when latest revision is selected upon service
411
+ registration"
412
+ update={toggleAutoActivateUpdates}
413
+ />
414
+ </PanelForm>
415
+ <PanelForm>
416
+ <PanelFormBooleanField
417
+ isReadOnly={isReadOnly}
418
+ value={activator.storeModel}
419
+ name="Store Model"
420
+ prompt="Use Store Model (slower)"
421
+ update={toggleUseStoreModel}
422
+ />
423
+ </PanelForm>
424
+ <PanelForm>
425
+ <PanelFormBooleanField
426
+ isReadOnly={isReadOnly}
427
+ value={activator.generateLineage}
428
+ name="Generate Lineage"
429
+ prompt="Generate Lineage (slower)"
430
+ update={toggleGenerateLineage}
431
+ />
432
+ </PanelForm>
433
+ <PanelForm>
434
+ {
435
+ <div>
436
+ <div className="panel__content__form__section">
437
+ <div className="panel__content__form__section__header__label">
438
+ Ownership
439
+ </div>
440
+ <div className="panel__content__form__section__header__prompt">
441
+ The ownership model you want to use to control your service.
442
+ </div>
443
+ <CustomSelectorInput
444
+ options={OWNERSHIP_OPTIONS}
445
+ onChange={onOwnershipChange}
446
+ value={editorState.selectedOwnership}
447
+ darkMode={true}
448
+ />
449
+ </div>
450
+ {ownership instanceof DeploymentOwner && (
451
+ <div className="panel__content__form__section">
452
+ <div>
453
+ <div className="panel__content__form__section__header__label">
454
+ Deployment Identifier :
455
+ </div>
456
+ <input
457
+ className="panel__content__form__section__input"
458
+ spellCheck={false}
459
+ disabled={isReadOnly}
460
+ value={ownership.id}
461
+ onChange={updateDeploymentIdentifier}
462
+ />
463
+ </div>
464
+ </div>
465
+ )}
466
+ {ownership instanceof UserList && (
467
+ <div className="panel__content__form__section">
468
+ <div>
469
+ <div className="panel__content__form__section__header__label">
470
+ Users :
471
+ </div>
472
+ <div className="panel__content__form__section__list">
473
+ <div
474
+ className="panel__content__form__section__list__items"
475
+ data-testid={
476
+ LEGEND_STUDIO_TEST_ID.PANEL_CONTENT_FORM_SECTION_LIST_ITEMS
477
+ }
478
+ >
479
+ {ownership.users.map((value, idx) => (
480
+ <div
481
+ key={value}
482
+ className={
483
+ showOwnerEditInput === idx
484
+ ? 'panel__content__form__section__list__new-item'
485
+ : 'panel__content__form__section__list__item'
486
+ }
487
+ >
488
+ {showOwnerEditInput === idx ? (
489
+ <>
490
+ <input
491
+ className="panel__content__form__section__input panel__content__form__section__list__new-item__input"
492
+ spellCheck={false}
493
+ disabled={isReadOnly}
494
+ value={ownerInputValue}
495
+ onChange={changeUserOwnerInputValue}
496
+ />
497
+ <div className="panel__content__form__section__list__new-item__actions">
498
+ <button
499
+ className="panel__content__form__section__list__new-item__add-btn btn btn--dark"
500
+ disabled={
501
+ isReadOnly ||
502
+ ownership.users.includes(
503
+ ownerInputValue,
504
+ )
505
+ }
506
+ onClick={updateUser(idx)}
507
+ tabIndex={-1}
508
+ >
509
+ Save
510
+ </button>
511
+ <button
512
+ className="panel__content__form__section__list__new-item__cancel-btn btn btn--dark"
513
+ disabled={isReadOnly}
514
+ onClick={hideAddOrEditOwnerInput}
515
+ tabIndex={-1}
516
+ >
517
+ Cancel
518
+ </button>
519
+ </div>
520
+ </>
521
+ ) : (
522
+ <>
523
+ <div className="panel__content__form__section__list__item__value">
524
+ {value}
525
+ </div>
526
+ <div className="panel__content__form__section__list__item__actions">
527
+ <button
528
+ className="panel__content__form__section__list__item__edit-btn"
529
+ disabled={isReadOnly}
530
+ onClick={showEditOwnerInput(value, idx)}
531
+ tabIndex={-1}
532
+ >
533
+ <PencilIcon />
534
+ </button>
535
+ <button
536
+ className="panel__content__form__section__list__item__remove-btn"
537
+ disabled={isReadOnly}
538
+ onClick={deleteUser(idx)}
539
+ tabIndex={-1}
540
+ >
541
+ <TimesIcon />
542
+ </button>
543
+ </div>
544
+ </>
545
+ )}
546
+ </div>
547
+ ))}
548
+ {showOwnerEditInput === true && (
549
+ <div className="panel__content__form__section__list__new-item">
550
+ <CustomSelectorInput
551
+ className="service-editor__owner__selector"
552
+ placeholder="Enter an owner..."
553
+ spellCheck={false}
554
+ inputValue={searchText}
555
+ options={userOptions}
556
+ allowCreating={true}
557
+ isLoading={isLoadingUsers}
558
+ disabled={isReadOnly}
559
+ darkMode={
560
+ !applicationStore.layoutService
561
+ .TEMPORARY__isLightColorThemeEnabled
562
+ }
563
+ onInputChange={onSearchTextChange}
564
+ onChange={onUserOptionChange}
565
+ isMulti={true}
566
+ />
567
+ <div className="panel__content__form__section__list__new-item__actions">
568
+ <button
569
+ className="panel__content__form__section__list__new-item__add-btn btn btn--dark service-editor__owner__action"
570
+ disabled={
571
+ isReadOnly ||
572
+ ownerInputs.some((i) =>
573
+ ownership.users.includes(i),
574
+ )
575
+ }
576
+ onClick={addUser}
577
+ tabIndex={-1}
578
+ >
579
+ Save
580
+ </button>
581
+ <button
582
+ className="panel__content__form__section__list__new-item__cancel-btn btn btn--dark service-editor__owner__action"
583
+ disabled={isReadOnly}
584
+ onClick={hideAddOrEditOwnerInput}
585
+ tabIndex={-1}
586
+ >
587
+ Cancel
588
+ </button>
589
+ </div>
590
+ </div>
591
+ )}
592
+ </div>
593
+ {ownership.users.length <
594
+ MINIMUM_HOSTED_SERVICE_OWNERS &&
595
+ showOwnerEditInput !== true && (
596
+ <div
597
+ className="service-editor__owner__validation"
598
+ title={`${MINIMUM_HOSTED_SERVICE_OWNERS} owners required`}
599
+ >
600
+ <ErrorIcon />
601
+ <div className="service-editor__owner__validation-label">
602
+ Service requires at least{' '}
603
+ {MINIMUM_HOSTED_SERVICE_OWNERS} owners
604
+ </div>
605
+ </div>
606
+ )}
607
+ {showOwnerEditInput !== true && (
608
+ <div className="panel__content__form__section__list__new-item__add">
609
+ <button
610
+ className="panel__content__form__section__list__new-item__add-btn btn btn--dark"
611
+ disabled={isReadOnly}
612
+ onClick={showAddOwnerInput}
613
+ tabIndex={-1}
614
+ title="Add owner"
615
+ >
616
+ Add Value
617
+ </button>
618
+ </div>
619
+ )}
620
+ </div>
621
+ </div>
622
+ </div>
623
+ )}
624
+ </div>
625
+ }
626
+ </PanelForm>
627
+ </PanelContent>
628
+ </Panel>
629
+ </div>
630
+ );
631
+ });