@finos/legend-extension-dsl-service 0.0.1

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 (68) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +3 -0
  3. package/lib/components/studio/DSL_Service_LegendStudioApplicationPlugin.d.ts +22 -0
  4. package/lib/components/studio/DSL_Service_LegendStudioApplicationPlugin.d.ts.map +1 -0
  5. package/lib/components/studio/DSL_Service_LegendStudioApplicationPlugin.js +59 -0
  6. package/lib/components/studio/DSL_Service_LegendStudioApplicationPlugin.js.map +1 -0
  7. package/lib/components/studio/ServiceQueryEditor.d.ts +25 -0
  8. package/lib/components/studio/ServiceQueryEditor.d.ts.map +1 -0
  9. package/lib/components/studio/ServiceQueryEditor.js +190 -0
  10. package/lib/components/studio/ServiceQueryEditor.js.map +1 -0
  11. package/lib/components/studio/ServiceQueryEditorReviewAction.d.ts +19 -0
  12. package/lib/components/studio/ServiceQueryEditorReviewAction.d.ts.map +1 -0
  13. package/lib/components/studio/ServiceQueryEditorReviewAction.js +71 -0
  14. package/lib/components/studio/ServiceQueryEditorReviewAction.js.map +1 -0
  15. package/lib/components/studio/ServiceQueryEditorStoreProvider.d.ts +31 -0
  16. package/lib/components/studio/ServiceQueryEditorStoreProvider.d.ts.map +1 -0
  17. package/lib/components/studio/ServiceQueryEditorStoreProvider.js +40 -0
  18. package/lib/components/studio/ServiceQueryEditorStoreProvider.js.map +1 -0
  19. package/lib/components/studio/ServiceQueryEditorWorkspaceStatus.d.ts +19 -0
  20. package/lib/components/studio/ServiceQueryEditorWorkspaceStatus.d.ts.map +1 -0
  21. package/lib/components/studio/ServiceQueryEditorWorkspaceStatus.js +69 -0
  22. package/lib/components/studio/ServiceQueryEditorWorkspaceStatus.js.map +1 -0
  23. package/lib/components/studio/UpdateProjectServiceQuerySetup.d.ts +18 -0
  24. package/lib/components/studio/UpdateProjectServiceQuerySetup.d.ts.map +1 -0
  25. package/lib/components/studio/UpdateProjectServiceQuerySetup.js +182 -0
  26. package/lib/components/studio/UpdateProjectServiceQuerySetup.js.map +1 -0
  27. package/lib/components/studio/UpdateServiceQuerySetup.d.ts +18 -0
  28. package/lib/components/studio/UpdateServiceQuerySetup.d.ts.map +1 -0
  29. package/lib/components/studio/UpdateServiceQuerySetup.js +172 -0
  30. package/lib/components/studio/UpdateServiceQuerySetup.js.map +1 -0
  31. package/lib/index.css +17 -0
  32. package/lib/index.css.map +1 -0
  33. package/lib/index.d.ts +17 -0
  34. package/lib/index.d.ts.map +1 -0
  35. package/lib/index.js +17 -0
  36. package/lib/index.js.map +1 -0
  37. package/lib/package.json +84 -0
  38. package/lib/stores/studio/DSL_Service_LegendStudioRouter.d.ts +53 -0
  39. package/lib/stores/studio/DSL_Service_LegendStudioRouter.d.ts.map +1 -0
  40. package/lib/stores/studio/DSL_Service_LegendStudioRouter.js +62 -0
  41. package/lib/stores/studio/DSL_Service_LegendStudioRouter.js.map +1 -0
  42. package/lib/stores/studio/ServiceQueryEditorStore.d.ts +64 -0
  43. package/lib/stores/studio/ServiceQueryEditorStore.d.ts.map +1 -0
  44. package/lib/stores/studio/ServiceQueryEditorStore.js +260 -0
  45. package/lib/stores/studio/ServiceQueryEditorStore.js.map +1 -0
  46. package/lib/stores/studio/UpdateProjectServiceQuerySetupStore.d.ts +46 -0
  47. package/lib/stores/studio/UpdateProjectServiceQuerySetupStore.d.ts.map +1 -0
  48. package/lib/stores/studio/UpdateProjectServiceQuerySetupStore.js +184 -0
  49. package/lib/stores/studio/UpdateProjectServiceQuerySetupStore.js.map +1 -0
  50. package/lib/stores/studio/UpdateServiceQuerySetupStore.d.ts +48 -0
  51. package/lib/stores/studio/UpdateServiceQuerySetupStore.d.ts.map +1 -0
  52. package/lib/stores/studio/UpdateServiceQuerySetupStore.js +184 -0
  53. package/lib/stores/studio/UpdateServiceQuerySetupStore.js.map +1 -0
  54. package/package.json +84 -0
  55. package/src/components/studio/DSL_Service_LegendStudioApplicationPlugin.tsx +71 -0
  56. package/src/components/studio/ServiceQueryEditor.tsx +551 -0
  57. package/src/components/studio/ServiceQueryEditorReviewAction.tsx +172 -0
  58. package/src/components/studio/ServiceQueryEditorStoreProvider.tsx +89 -0
  59. package/src/components/studio/ServiceQueryEditorWorkspaceStatus.tsx +121 -0
  60. package/src/components/studio/UpdateProjectServiceQuerySetup.tsx +479 -0
  61. package/src/components/studio/UpdateServiceQuerySetup.tsx +476 -0
  62. package/src/index.ts +17 -0
  63. package/src/stores/studio/DSL_Service_LegendStudioRouter.ts +159 -0
  64. package/src/stores/studio/ServiceQueryEditorStore.ts +487 -0
  65. package/src/stores/studio/UpdateProjectServiceQuerySetupStore.ts +281 -0
  66. package/src/stores/studio/UpdateServiceQuerySetupStore.ts +314 -0
  67. package/tsconfig.json +58 -0
  68. package/tsconfig.package.json +38 -0
@@ -0,0 +1,479 @@
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
+ createContext,
19
+ useContext,
20
+ useEffect,
21
+ useMemo,
22
+ useState,
23
+ useRef,
24
+ } from 'react';
25
+ import { observer, useLocalObservable } from 'mobx-react-lite';
26
+ import { debounce, guaranteeNonNullable, isString } from '@finos/legend-shared';
27
+ import { type Project, useSDLCServerClient } from '@finos/legend-server-sdlc';
28
+ import {
29
+ type WorkspaceOption,
30
+ type ProjectOption,
31
+ ActivityBarMenu,
32
+ buildWorkspaceOption,
33
+ formatWorkspaceOptionLabel,
34
+ LEGEND_STUDIO_TEST_ID,
35
+ useLegendStudioApplicationStore,
36
+ buildProjectOption,
37
+ getProjectOptionLabelFormatter,
38
+ } from '@finos/legend-application-studio';
39
+ import { UpdateProjectServiceQuerySetupStore } from '../../stores/studio/UpdateProjectServiceQuerySetupStore.js';
40
+ import { useParams } from 'react-router';
41
+ import {
42
+ type ProjectServiceQueryUpdaterSetupPathParams,
43
+ generateProjectServiceQueryUpdaterRoute,
44
+ } from '../../stores/studio/DSL_Service_LegendStudioRouter.js';
45
+ import { flowResult } from 'mobx';
46
+ import {
47
+ compareLabelFn,
48
+ createFilter,
49
+ CustomSelectorInput,
50
+ Dialog,
51
+ GitBranchIcon,
52
+ Panel,
53
+ PanelLoadingIndicator,
54
+ PlusIcon,
55
+ PURE_ServiceIcon,
56
+ RepoIcon,
57
+ } from '@finos/legend-art';
58
+ import type { Entity } from '@finos/legend-storage';
59
+ import { extractElementNameFromPath } from '@finos/legend-graph';
60
+
61
+ const UpdateProjectServiceQuerySetupStoreContext = createContext<
62
+ UpdateProjectServiceQuerySetupStore | undefined
63
+ >(undefined);
64
+
65
+ const UpdateProjectServiceQuerySetupStoreProvider: React.FC<{
66
+ children: React.ReactNode;
67
+ }> = ({ children }) => {
68
+ const applicationStore = useLegendStudioApplicationStore();
69
+ const sdlcServerClient = useSDLCServerClient();
70
+ const store = useLocalObservable(
71
+ () =>
72
+ new UpdateProjectServiceQuerySetupStore(
73
+ applicationStore,
74
+ sdlcServerClient,
75
+ ),
76
+ );
77
+ return (
78
+ <UpdateProjectServiceQuerySetupStoreContext.Provider value={store}>
79
+ {children}
80
+ </UpdateProjectServiceQuerySetupStoreContext.Provider>
81
+ );
82
+ };
83
+
84
+ const useUpdateProjectServiceQuerySetupStore =
85
+ (): UpdateProjectServiceQuerySetupStore =>
86
+ guaranteeNonNullable(
87
+ useContext(UpdateProjectServiceQuerySetupStoreContext),
88
+ `Can't find project service query updater store in context`,
89
+ );
90
+
91
+ const withUpdateProjectServiceQuerySetupStore = (
92
+ WrappedComponent: React.FC,
93
+ ): React.FC =>
94
+ function WithUpdateProjectServiceQuerySetupStore() {
95
+ return (
96
+ <UpdateProjectServiceQuerySetupStoreProvider>
97
+ <WrappedComponent />
98
+ </UpdateProjectServiceQuerySetupStoreProvider>
99
+ );
100
+ };
101
+
102
+ const CreateWorkspaceModal = observer((props: { selectedProject: Project }) => {
103
+ const { selectedProject } = props;
104
+ const setupStore = useUpdateProjectServiceQuerySetupStore();
105
+ const applicationStore = useLegendStudioApplicationStore();
106
+ const workspaceNameInputRef = useRef<HTMLInputElement>(null);
107
+ const [workspaceName, setWorkspaceName] = useState('');
108
+
109
+ const workspaceAlreadyExists = Boolean(
110
+ setupStore.groupWorkspaces.find(
111
+ (workspace) => workspace.workspaceId === workspaceName,
112
+ ),
113
+ );
114
+ const createWorkspace = (): void => {
115
+ if (workspaceName && !workspaceAlreadyExists) {
116
+ flowResult(
117
+ setupStore.createWorkspace(selectedProject.projectId, workspaceName),
118
+ ).catch(applicationStore.alertUnhandledError);
119
+ }
120
+ };
121
+ const changeWorkspaceName: React.ChangeEventHandler<HTMLInputElement> = (
122
+ event,
123
+ ) => setWorkspaceName(event.target.value);
124
+
125
+ const handleEnter = (): void => {
126
+ workspaceNameInputRef.current?.focus();
127
+ };
128
+ const onClose = (): void => {
129
+ setupStore.setShowCreateWorkspaceModal(false);
130
+ };
131
+ const handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
132
+ event.preventDefault();
133
+ createWorkspace();
134
+ };
135
+
136
+ return (
137
+ <Dialog
138
+ open={setupStore.showCreateWorkspaceModal}
139
+ onClose={onClose}
140
+ TransitionProps={{
141
+ onEnter: handleEnter,
142
+ }}
143
+ classes={{ container: 'search-modal__container' }}
144
+ PaperProps={{ classes: { root: 'search-modal__inner-container' } }}
145
+ >
146
+ <form onSubmit={handleSubmit} className="modal modal--dark search-modal">
147
+ <div className="modal__title">Create New Workspace</div>
148
+ <Panel>
149
+ <PanelLoadingIndicator
150
+ isLoading={setupStore.createWorkspaceState.isInProgress}
151
+ />
152
+ <div className="panel__content--full">
153
+ <div className="input-group">
154
+ <input
155
+ className="input input--dark input-group__input"
156
+ ref={workspaceNameInputRef}
157
+ spellCheck={false}
158
+ disabled={setupStore.createWorkspaceState.isInProgress}
159
+ placeholder="MyWorkspace"
160
+ value={workspaceName}
161
+ onChange={changeWorkspaceName}
162
+ />
163
+ {workspaceAlreadyExists && (
164
+ <div className="input-group__error-message">
165
+ Workspace with same name already exists
166
+ </div>
167
+ )}
168
+ </div>
169
+ </div>
170
+ </Panel>
171
+ <div className="search-modal__actions">
172
+ <button
173
+ disabled={
174
+ setupStore.createWorkspaceState.isInProgress ||
175
+ !workspaceName ||
176
+ workspaceAlreadyExists
177
+ }
178
+ className="btn btn--dark"
179
+ >
180
+ Create
181
+ </button>
182
+ </div>
183
+ </form>
184
+ </Dialog>
185
+ );
186
+ });
187
+
188
+ type ServiceOption = {
189
+ label: string;
190
+ value: {
191
+ name: string;
192
+ path: string;
193
+ urlPattern: string | undefined;
194
+ entity: Entity;
195
+ };
196
+ };
197
+ const buildServiceOption = (entity: Entity): ServiceOption => ({
198
+ label: extractElementNameFromPath(entity.path),
199
+ value: {
200
+ name: extractElementNameFromPath(entity.path),
201
+ path: entity.path,
202
+ // NOTE: we don't want to assert the existence of this field even when it
203
+ // is required in the specification of service to avoid throwing error here
204
+ urlPattern: isString(entity.content.pattern)
205
+ ? entity.content.pattern
206
+ : undefined,
207
+ entity,
208
+ },
209
+ });
210
+ const formatServiceOptionLabel = (option: ServiceOption): React.ReactNode => (
211
+ <div
212
+ className="query-setup__service-option"
213
+ title={`${option.label} - ${option.value.urlPattern ?? ''} - ${
214
+ option.value.path
215
+ }`}
216
+ >
217
+ <div className="query-setup__service-option__label">{option.label}</div>
218
+ <div className="query-setup__service-option__path">{option.value.path}</div>
219
+ <div className="query-setup__service-option__pattern">
220
+ {option.value.urlPattern ?? 'no pattern'}
221
+ </div>
222
+ </div>
223
+ );
224
+
225
+ export const UpdateProjectServiceQuerySetup =
226
+ withUpdateProjectServiceQuerySetupStore(
227
+ observer(() => {
228
+ const params = useParams<ProjectServiceQueryUpdaterSetupPathParams>();
229
+ const { projectId } = params;
230
+ const setupStore = useUpdateProjectServiceQuerySetupStore();
231
+ const applicationStore = useLegendStudioApplicationStore();
232
+ const [projectSearchText, setProjectSearchText] = useState('');
233
+
234
+ // action
235
+ const disableProceedButton =
236
+ !setupStore.currentProject ||
237
+ !setupStore.currentGroupWorkspace ||
238
+ !setupStore.currentService;
239
+ const handleProceed = (): void => {
240
+ if (
241
+ setupStore.currentProject &&
242
+ setupStore.currentGroupWorkspace &&
243
+ setupStore.currentService
244
+ ) {
245
+ applicationStore.navigator.goTo(
246
+ generateProjectServiceQueryUpdaterRoute(
247
+ setupStore.currentProject.projectId,
248
+ setupStore.currentGroupWorkspace.workspaceId,
249
+ setupStore.currentService.path,
250
+ ),
251
+ );
252
+ }
253
+ };
254
+
255
+ // projects
256
+ const projectOptions = setupStore.projects.map(buildProjectOption);
257
+ const selectedProjectOption = setupStore.currentProject
258
+ ? buildProjectOption(setupStore.currentProject)
259
+ : null;
260
+ const onProjectOptionChange = (option: ProjectOption | null): void => {
261
+ if (option) {
262
+ flowResult(setupStore.changeProject(option.value)).catch(
263
+ applicationStore.alertUnhandledError,
264
+ );
265
+ } else {
266
+ setupStore.resetCurrentProject();
267
+ }
268
+ };
269
+
270
+ // project search text
271
+ const debouncedLoadProjects = useMemo(
272
+ () =>
273
+ debounce((input: string): void => {
274
+ flowResult(setupStore.loadProjects(input)).catch(
275
+ applicationStore.alertUnhandledError,
276
+ );
277
+ }, 500),
278
+ [applicationStore, setupStore],
279
+ );
280
+ const onProjectSearchTextChange = (value: string): void => {
281
+ if (value !== projectSearchText) {
282
+ setProjectSearchText(value);
283
+ debouncedLoadProjects.cancel();
284
+ debouncedLoadProjects(value);
285
+ }
286
+ };
287
+
288
+ // workspaces
289
+ const workspaceOptions = setupStore.groupWorkspaces
290
+ .map(buildWorkspaceOption)
291
+ .sort(compareLabelFn);
292
+ const selectedOption = setupStore.currentGroupWorkspace
293
+ ? buildWorkspaceOption(setupStore.currentGroupWorkspace)
294
+ : null;
295
+ const onWorkspaceChange = (option: WorkspaceOption | null): void => {
296
+ if (option) {
297
+ flowResult(setupStore.changeWorkspace(option.value)).catch(
298
+ applicationStore.alertUnhandledError,
299
+ );
300
+ } else {
301
+ setupStore.resetCurrentGroupWorkspace();
302
+ }
303
+ };
304
+ const showCreateWorkspaceModal = (): void =>
305
+ setupStore.setShowCreateWorkspaceModal(true);
306
+
307
+ // services
308
+ const serviceOptions = setupStore.services.map(buildServiceOption);
309
+ const selectedServiceOption = setupStore.currentService
310
+ ? buildServiceOption(setupStore.currentService)
311
+ : null;
312
+ const onServiceOptionChange = (option: ServiceOption | null): void => {
313
+ if (option) {
314
+ setupStore.changeService(option.value.entity);
315
+ } else {
316
+ setupStore.resetCurrentService();
317
+ }
318
+ };
319
+ const serviceFilterOption = createFilter({
320
+ ignoreCase: true,
321
+ ignoreAccents: false,
322
+ stringify: (option: ServiceOption): string =>
323
+ // NOTE: account for label, path, and URL pattern
324
+ `${option.label} - ${option.value.urlPattern ?? ''} - ${
325
+ option.value.path
326
+ }`,
327
+ });
328
+
329
+ useEffect(() => {
330
+ flowResult(setupStore.loadProjects('')).catch(
331
+ applicationStore.alertUnhandledError,
332
+ );
333
+ }, [setupStore, applicationStore]);
334
+
335
+ useEffect(() => {
336
+ setupStore.initialize(projectId);
337
+ }, [setupStore, projectId]);
338
+
339
+ return (
340
+ <div className="app__page">
341
+ <div className="service-query-setup">
342
+ <div className="service-query-setup__body">
343
+ <div className="activity-bar">
344
+ <ActivityBarMenu />
345
+ </div>
346
+ <div
347
+ className="service-query-setup__content"
348
+ data-testid={LEGEND_STUDIO_TEST_ID.SETUP__CONTENT}
349
+ >
350
+ <div className="service-query-setup__content__main">
351
+ <div className="service-query-setup__title">
352
+ <div className="service-query-setup__title__header">
353
+ Update Project Service Query
354
+ </div>
355
+ </div>
356
+ <div className="service-query-setup__selector">
357
+ <div
358
+ className="service-query-setup__selector__icon"
359
+ title="service"
360
+ >
361
+ <RepoIcon className="service-query-setup__selector__icon--project" />
362
+ </div>
363
+ <CustomSelectorInput
364
+ className="service-query-setup__selector__input"
365
+ options={projectOptions}
366
+ isLoading={setupStore.loadProjectsState.isInProgress}
367
+ onInputChange={onProjectSearchTextChange}
368
+ inputValue={projectSearchText}
369
+ value={selectedProjectOption}
370
+ onChange={onProjectOptionChange}
371
+ placeholder="Search for project..."
372
+ darkMode={true}
373
+ isClearable={true}
374
+ escapeClearsValue={true}
375
+ formatOptionLabel={getProjectOptionLabelFormatter(
376
+ applicationStore,
377
+ )}
378
+ />
379
+ </div>
380
+ <div className="service-query-setup__selector">
381
+ <div
382
+ className="service-query-setup__selector__icon"
383
+ title="workspace"
384
+ >
385
+ <GitBranchIcon className="service-query-setup__selector__icon--workspace" />
386
+ </div>
387
+ <CustomSelectorInput
388
+ className="service-query-setup__selector__input"
389
+ options={workspaceOptions}
390
+ disabled={
391
+ !setupStore.currentProject ||
392
+ setupStore.loadWorkspacesState.isInProgress
393
+ }
394
+ isLoading={setupStore.loadWorkspacesState.isInProgress}
395
+ onChange={onWorkspaceChange}
396
+ formatOptionLabel={formatWorkspaceOptionLabel}
397
+ value={selectedOption}
398
+ placeholder={
399
+ setupStore.loadWorkspacesState.isInProgress
400
+ ? 'Loading workspaces...'
401
+ : !setupStore.currentProject
402
+ ? 'In order to select a workspace, a project must be selected'
403
+ : workspaceOptions.length
404
+ ? 'Choose an existing workspace'
405
+ : setupStore.loadWorkspacesState.hasFailed
406
+ ? `Can't fetch project workspaces. Please try again or select another service`
407
+ : 'You have no workspaces. Please create one to proceed...'
408
+ }
409
+ isClearable={true}
410
+ escapeClearsValue={true}
411
+ darkMode={true}
412
+ />
413
+ <button
414
+ className="service-query-setup__selector__action btn--dark"
415
+ onClick={showCreateWorkspaceModal}
416
+ disabled={!setupStore.currentProject}
417
+ tabIndex={-1}
418
+ title="Create a Workspace"
419
+ >
420
+ <PlusIcon />
421
+ </button>
422
+ </div>
423
+ <div className="service-query-setup__selector">
424
+ <div
425
+ className="service-query-setup__selector__icon"
426
+ title="service"
427
+ >
428
+ <PURE_ServiceIcon />
429
+ </div>
430
+ <CustomSelectorInput
431
+ className="service-query-setup__selector__input"
432
+ disabled={
433
+ !setupStore.currentGroupWorkspace ||
434
+ !serviceOptions.length
435
+ }
436
+ options={serviceOptions}
437
+ value={selectedServiceOption}
438
+ onChange={onServiceOptionChange}
439
+ placeholder={
440
+ !setupStore.currentGroupWorkspace
441
+ ? 'In order to select a service, a workspace must be selected'
442
+ : serviceOptions.length
443
+ ? 'Choose an existing service'
444
+ : 'You have no services to load'
445
+ }
446
+ darkMode={true}
447
+ isClearable={true}
448
+ escapeClearsValue={true}
449
+ filterOption={serviceFilterOption}
450
+ formatOptionLabel={formatServiceOptionLabel}
451
+ />
452
+ </div>
453
+ {setupStore.showCreateWorkspaceModal &&
454
+ setupStore.currentProject && (
455
+ <CreateWorkspaceModal
456
+ selectedProject={setupStore.currentProject}
457
+ />
458
+ )}
459
+ <div className="service-query-setup__actions">
460
+ <button
461
+ className="service-query-setup__next-btn btn--dark"
462
+ onClick={handleProceed}
463
+ disabled={disableProceedButton}
464
+ >
465
+ Edit Service Query
466
+ </button>
467
+ </div>
468
+ </div>
469
+ </div>
470
+ </div>
471
+ <div
472
+ data-testid={LEGEND_STUDIO_TEST_ID.STATUS_BAR}
473
+ className="editor__status-bar"
474
+ />
475
+ </div>
476
+ </div>
477
+ );
478
+ }),
479
+ );