@finos/legend-application-studio 28.4.3 → 28.5.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 (123) hide show
  1. package/lib/__lib__/LegendStudioDocumentation.d.ts +1 -0
  2. package/lib/__lib__/LegendStudioDocumentation.d.ts.map +1 -1
  3. package/lib/__lib__/LegendStudioDocumentation.js +1 -0
  4. package/lib/__lib__/LegendStudioDocumentation.js.map +1 -1
  5. package/lib/__lib__/LegendStudioNavigation.d.ts +12 -2
  6. package/lib/__lib__/LegendStudioNavigation.d.ts.map +1 -1
  7. package/lib/__lib__/LegendStudioNavigation.js +92 -37
  8. package/lib/__lib__/LegendStudioNavigation.js.map +1 -1
  9. package/lib/components/LegendStudioWebApplication.d.ts.map +1 -1
  10. package/lib/components/LegendStudioWebApplication.js +6 -0
  11. package/lib/components/LegendStudioWebApplication.js.map +1 -1
  12. package/lib/components/editor/ActivityBar.js +1 -1
  13. package/lib/components/editor/ActivityBar.js.map +1 -1
  14. package/lib/components/editor/Editor.d.ts.map +1 -1
  15. package/lib/components/editor/Editor.js +10 -2
  16. package/lib/components/editor/Editor.js.map +1 -1
  17. package/lib/components/editor/StatusBar.d.ts.map +1 -1
  18. package/lib/components/editor/StatusBar.js +4 -1
  19. package/lib/components/editor/StatusBar.js.map +1 -1
  20. package/lib/components/editor/__test-utils__/EditorComponentTestUtils.d.ts.map +1 -1
  21. package/lib/components/editor/__test-utils__/EditorComponentTestUtils.js +1 -1
  22. package/lib/components/editor/__test-utils__/EditorComponentTestUtils.js.map +1 -1
  23. package/lib/components/editor/editor-group/GrammarTextEditor.d.ts.map +1 -1
  24. package/lib/components/editor/editor-group/GrammarTextEditor.js +6 -3
  25. package/lib/components/editor/editor-group/GrammarTextEditor.js.map +1 -1
  26. package/lib/components/editor/editor-group/project-configuration-editor/ProjectDependencyEditor.d.ts +4 -0
  27. package/lib/components/editor/editor-group/project-configuration-editor/ProjectDependencyEditor.d.ts.map +1 -1
  28. package/lib/components/editor/editor-group/service-editor/testable/ServiceTestsEditor.d.ts +2 -3
  29. package/lib/components/editor/editor-group/service-editor/testable/ServiceTestsEditor.d.ts.map +1 -1
  30. package/lib/components/editor/editor-group/service-editor/testable/ServiceTestsEditor.js +9 -9
  31. package/lib/components/editor/editor-group/service-editor/testable/ServiceTestsEditor.js.map +1 -1
  32. package/lib/components/editor/side-bar/ProjectOverview.d.ts.map +1 -1
  33. package/lib/components/editor/side-bar/ProjectOverview.js +109 -7
  34. package/lib/components/editor/side-bar/ProjectOverview.js.map +1 -1
  35. package/lib/components/project-view/ProjectViewer.js +1 -1
  36. package/lib/components/project-view/ProjectViewer.js.map +1 -1
  37. package/lib/components/workspace-review/WorkspaceReview.js +1 -1
  38. package/lib/components/workspace-review/WorkspaceReview.js.map +1 -1
  39. package/lib/components/workspace-setup/CreateWorkspaceModal.d.ts +5 -1
  40. package/lib/components/workspace-setup/CreateWorkspaceModal.d.ts.map +1 -1
  41. package/lib/components/workspace-setup/CreateWorkspaceModal.js +34 -6
  42. package/lib/components/workspace-setup/CreateWorkspaceModal.js.map +1 -1
  43. package/lib/components/workspace-setup/WorkspaceSelectorUtils.d.ts.map +1 -1
  44. package/lib/components/workspace-setup/WorkspaceSelectorUtils.js +1 -1
  45. package/lib/components/workspace-setup/WorkspaceSelectorUtils.js.map +1 -1
  46. package/lib/components/workspace-setup/WorkspaceSetup.d.ts +1 -0
  47. package/lib/components/workspace-setup/WorkspaceSetup.d.ts.map +1 -1
  48. package/lib/components/workspace-setup/WorkspaceSetup.js +2 -1
  49. package/lib/components/workspace-setup/WorkspaceSetup.js.map +1 -1
  50. package/lib/index.css +2 -2
  51. package/lib/index.css.map +1 -1
  52. package/lib/package.json +1 -1
  53. package/lib/stores/LegendStudioApplicationPlugin.d.ts +1 -1
  54. package/lib/stores/LegendStudioApplicationPlugin.d.ts.map +1 -1
  55. package/lib/stores/editor/EditorSDLCState.d.ts +8 -2
  56. package/lib/stores/editor/EditorSDLCState.d.ts.map +1 -1
  57. package/lib/stores/editor/EditorSDLCState.js +29 -3
  58. package/lib/stores/editor/EditorSDLCState.js.map +1 -1
  59. package/lib/stores/editor/EditorStore.d.ts +1 -1
  60. package/lib/stores/editor/EditorStore.d.ts.map +1 -1
  61. package/lib/stores/editor/EditorStore.js +8 -5
  62. package/lib/stores/editor/EditorStore.js.map +1 -1
  63. package/lib/stores/editor/StandardEditorMode.d.ts.map +1 -1
  64. package/lib/stores/editor/StandardEditorMode.js +1 -1
  65. package/lib/stores/editor/StandardEditorMode.js.map +1 -1
  66. package/lib/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestEditorState.d.ts +5 -5
  67. package/lib/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestEditorState.d.ts.map +1 -1
  68. package/lib/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestEditorState.js +19 -8
  69. package/lib/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestEditorState.js.map +1 -1
  70. package/lib/stores/editor/editor-state/project-configuration-editor-state/ProjectConfigurationEditorState.d.ts.map +1 -1
  71. package/lib/stores/editor/editor-state/project-configuration-editor-state/ProjectConfigurationEditorState.js +1 -1
  72. package/lib/stores/editor/editor-state/project-configuration-editor-state/ProjectConfigurationEditorState.js.map +1 -1
  73. package/lib/stores/editor/sidebar-state/ProjectOverviewState.d.ts +9 -3
  74. package/lib/stores/editor/sidebar-state/ProjectOverviewState.d.ts.map +1 -1
  75. package/lib/stores/editor/sidebar-state/ProjectOverviewState.js +71 -7
  76. package/lib/stores/editor/sidebar-state/ProjectOverviewState.js.map +1 -1
  77. package/lib/stores/editor/sidebar-state/WorkspaceReviewState.d.ts.map +1 -1
  78. package/lib/stores/editor/sidebar-state/WorkspaceReviewState.js +6 -6
  79. package/lib/stores/editor/sidebar-state/WorkspaceReviewState.js.map +1 -1
  80. package/lib/stores/editor/sidebar-state/WorkspaceUpdaterState.d.ts.map +1 -1
  81. package/lib/stores/editor/sidebar-state/WorkspaceUpdaterState.js +2 -2
  82. package/lib/stores/editor/sidebar-state/WorkspaceUpdaterState.js.map +1 -1
  83. package/lib/stores/workspace-review/WorkspaceReviewStore.d.ts +3 -1
  84. package/lib/stores/workspace-review/WorkspaceReviewStore.d.ts.map +1 -1
  85. package/lib/stores/workspace-review/WorkspaceReviewStore.js +13 -7
  86. package/lib/stores/workspace-review/WorkspaceReviewStore.js.map +1 -1
  87. package/lib/stores/workspace-setup/ProjectConfigurationStatus.d.ts +1 -1
  88. package/lib/stores/workspace-setup/ProjectConfigurationStatus.d.ts.map +1 -1
  89. package/lib/stores/workspace-setup/ProjectConfigurationStatus.js +2 -2
  90. package/lib/stores/workspace-setup/ProjectConfigurationStatus.js.map +1 -1
  91. package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts +6 -2
  92. package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts.map +1 -1
  93. package/lib/stores/workspace-setup/WorkspaceSetupStore.js +45 -12
  94. package/lib/stores/workspace-setup/WorkspaceSetupStore.js.map +1 -1
  95. package/package.json +7 -7
  96. package/src/__lib__/LegendStudioDocumentation.ts +1 -0
  97. package/src/__lib__/LegendStudioNavigation.ts +106 -25
  98. package/src/components/LegendStudioWebApplication.tsx +6 -0
  99. package/src/components/editor/ActivityBar.tsx +1 -1
  100. package/src/components/editor/Editor.tsx +15 -2
  101. package/src/components/editor/StatusBar.tsx +5 -1
  102. package/src/components/editor/__test-utils__/EditorComponentTestUtils.tsx +1 -0
  103. package/src/components/editor/editor-group/GrammarTextEditor.tsx +6 -3
  104. package/src/components/editor/editor-group/project-configuration-editor/ProjectDependencyEditor.tsx +1 -1
  105. package/src/components/editor/editor-group/service-editor/testable/ServiceTestsEditor.tsx +13 -14
  106. package/src/components/editor/side-bar/ProjectOverview.tsx +340 -8
  107. package/src/components/project-view/ProjectViewer.tsx +1 -1
  108. package/src/components/workspace-review/WorkspaceReview.tsx +1 -1
  109. package/src/components/workspace-setup/CreateWorkspaceModal.tsx +63 -4
  110. package/src/components/workspace-setup/WorkspaceSelectorUtils.tsx +13 -6
  111. package/src/components/workspace-setup/WorkspaceSetup.tsx +3 -0
  112. package/src/stores/LegendStudioApplicationPlugin.ts +1 -1
  113. package/src/stores/editor/EditorSDLCState.ts +45 -0
  114. package/src/stores/editor/EditorStore.ts +15 -2
  115. package/src/stores/editor/StandardEditorMode.ts +1 -0
  116. package/src/stores/editor/editor-state/element-editor-state/service/testable/ServiceTestEditorState.ts +31 -10
  117. package/src/stores/editor/editor-state/project-configuration-editor-state/ProjectConfigurationEditorState.ts +1 -0
  118. package/src/stores/editor/sidebar-state/ProjectOverviewState.ts +135 -1
  119. package/src/stores/editor/sidebar-state/WorkspaceReviewState.ts +6 -1
  120. package/src/stores/editor/sidebar-state/WorkspaceUpdaterState.ts +2 -0
  121. package/src/stores/workspace-review/WorkspaceReviewStore.ts +15 -1
  122. package/src/stores/workspace-setup/ProjectConfigurationStatus.ts +2 -0
  123. package/src/stores/workspace-setup/WorkspaceSetupStore.ts +71 -7
@@ -984,7 +984,8 @@ export const GrammarTextEditor = observer(() => {
984
984
  (plugin) =>
985
985
  (
986
986
  plugin as DSL_LegendStudioApplicationPlugin_Extension
987
- ).getAutoFoldingCandidateToken?.() ?? [],
987
+ ).getExtraGrammarTextEditorAutoFoldingElementCreatorKeywords?.() ??
988
+ [],
988
989
  );
989
990
  const foldingClass = editor?.getContribution('editor.contrib.folding');
990
991
 
@@ -1056,7 +1057,8 @@ export const GrammarTextEditor = observer(() => {
1056
1057
  (plugin) =>
1057
1058
  (
1058
1059
  plugin as DSL_LegendStudioApplicationPlugin_Extension
1059
- ).getAutoFoldingCandidateToken?.() ?? [],
1060
+ ).getExtraGrammarTextEditorAutoFoldingElementCreatorKeywords?.() ??
1061
+ [],
1060
1062
  );
1061
1063
  const foldingClass = editor.getContribution('editor.contrib.folding');
1062
1064
 
@@ -1110,7 +1112,8 @@ export const GrammarTextEditor = observer(() => {
1110
1112
  (plugin) =>
1111
1113
  (
1112
1114
  plugin as DSL_LegendStudioApplicationPlugin_Extension
1113
- ).getAutoFoldingCandidateToken?.() ?? [],
1115
+ ).getExtraGrammarTextEditorAutoFoldingElementCreatorKeywords?.() ??
1116
+ [],
1114
1117
  );
1115
1118
  const foldingClass = editor.getContribution('editor.contrib.folding');
1116
1119
 
@@ -97,7 +97,7 @@ import {
97
97
  import { LEGEND_STUDIO_TEST_ID } from '../../../../__lib__/LegendStudioTesting.js';
98
98
  import { useEditorStore } from '../../EditorStoreProvider.js';
99
99
 
100
- interface VersionOption {
100
+ export interface VersionOption {
101
101
  label: string;
102
102
  value: string;
103
103
  }
@@ -43,7 +43,6 @@ import {
43
43
  PlayIcon,
44
44
  } from '@finos/legend-art';
45
45
  import {
46
- type Binding,
47
46
  type ValueSpecification,
48
47
  PrimitiveInstanceValue,
49
48
  PrimitiveType,
@@ -147,8 +146,8 @@ export const ExternalFormatParameterEditorModal = observer(
147
146
  isReadOnly: boolean;
148
147
  onClose: () => void;
149
148
  updateParamValue: (val: string) => void;
150
- bindingParamPair: {
151
- binding: Binding;
149
+ contentTypeParamPair: {
150
+ contentType: string;
152
151
  param: string;
153
152
  };
154
153
  }) => {
@@ -157,7 +156,7 @@ export const ExternalFormatParameterEditorModal = observer(
157
156
  isReadOnly,
158
157
  onClose,
159
158
  updateParamValue,
160
- bindingParamPair,
159
+ contentTypeParamPair,
161
160
  } = props;
162
161
  const paramValue =
163
162
  paramState.varExpression.genericType?.value.rawType === PrimitiveType.BYTE
@@ -190,7 +189,7 @@ export const ExternalFormatParameterEditorModal = observer(
190
189
  updateInput={updateParamValue}
191
190
  isReadOnly={isReadOnly}
192
191
  language={
193
- bindingParamPair.binding.contentType ===
192
+ contentTypeParamPair.contentType ===
194
193
  ContentType.APPLICATION_JSON.toString()
195
194
  ? CODE_EDITOR_LANGUAGE.JSON
196
195
  : CODE_EDITOR_LANGUAGE.TEXT
@@ -215,21 +214,21 @@ const ServiceTestParameterEditor = observer(
215
214
  isReadOnly: boolean;
216
215
  paramState: ServiceValueSpecificationTestParameterState;
217
216
  serviceTestState: ServiceTestState;
218
- bindingParamPair:
217
+ contentTypeParamPair:
219
218
  | {
220
- binding: Binding;
219
+ contentType: string;
221
220
  param: string;
222
221
  }
223
222
  | undefined;
224
223
  }) => {
225
- const { serviceTestState, paramState, isReadOnly, bindingParamPair } =
224
+ const { serviceTestState, paramState, isReadOnly, contentTypeParamPair } =
226
225
  props;
227
226
  const [showPopUp, setShowPopUp] = useState(false);
228
227
  const setupState = serviceTestState.setupState;
229
228
  const paramIsRequired =
230
229
  paramState.varExpression.multiplicity.lowerBound > 0;
231
- const type = bindingParamPair
232
- ? bindingParamPair.binding.contentType
230
+ const type = contentTypeParamPair
231
+ ? contentTypeParamPair.contentType
233
232
  : paramState.varExpression.genericType?.value.rawType.name ?? 'unknown';
234
233
  const paramValue =
235
234
  paramState.varExpression.genericType?.value.rawType === PrimitiveType.BYTE
@@ -273,7 +272,7 @@ const ServiceTestParameterEditor = observer(
273
272
  </button>
274
273
  </div>
275
274
  <>
276
- {bindingParamPair ? (
275
+ {contentTypeParamPair ? (
277
276
  <div className="service-test-editor__setup__parameter__code-editor">
278
277
  <textarea
279
278
  className="panel__content__form__section__textarea value-spec-editor__input"
@@ -295,7 +294,7 @@ const ServiceTestParameterEditor = observer(
295
294
  isReadOnly={isReadOnly}
296
295
  onClose={closePopUp}
297
296
  updateParamValue={updateParamValue}
298
- bindingParamPair={bindingParamPair}
297
+ contentTypeParamPair={contentTypeParamPair}
299
298
  />
300
299
  )}
301
300
  <div className="service-test-editor__setup__parameter__value__actions">
@@ -526,8 +525,8 @@ const ServiceTestSetupEditor = observer(
526
525
  isReadOnly={isReadOnly}
527
526
  paramState={paramState}
528
527
  serviceTestState={serviceTestState}
529
- bindingParamPair={setupState
530
- .getBindingWithParamFromQuery()
528
+ contentTypeParamPair={setupState
529
+ .getContentTypeWithParamFromQuery()
531
530
  .find(
532
531
  (pair) =>
533
532
  pair.param === paramState.parameterValue.name,
@@ -37,6 +37,7 @@ import {
37
37
  ModalFooter,
38
38
  MenuContentItem,
39
39
  MenuContent,
40
+ PanelFormBooleanField,
40
41
  } from '@finos/legend-art';
41
42
  import { PROJECT_OVERVIEW_ACTIVITY_MODE } from '../../../stores/editor/sidebar-state/ProjectOverviewState.js';
42
43
  import {
@@ -52,10 +53,24 @@ import {
52
53
  NewVersionType,
53
54
  WorkspaceType,
54
55
  areWorkspacesEquivalent,
56
+ Review,
57
+ ReviewState,
58
+ Patch,
55
59
  } from '@finos/legend-server-sdlc';
56
60
  import { useEditorStore } from '../EditorStoreProvider.js';
57
61
  import { useApplicationStore } from '@finos/legend-application';
58
62
  import { useLegendStudioApplicationStore } from '../../LegendStudioFrameworkProvider.js';
63
+ import {
64
+ ActionState,
65
+ assertErrorThrown,
66
+ guaranteeNonNullable,
67
+ LogEvent,
68
+ } from '@finos/legend-shared';
69
+ import type { VersionOption } from '../editor-group/project-configuration-editor/ProjectDependencyEditor.js';
70
+ import { DocumentationLink } from '@finos/legend-lego/application';
71
+ import { LEGEND_STUDIO_DOCUMENTATION_KEY } from '../../../__lib__/LegendStudioDocumentation.js';
72
+ import type { PatchOption } from '../../workspace-setup/CreateWorkspaceModal.js';
73
+ import { LEGEND_STUDIO_APP_EVENT } from '../../../__lib__/LegendStudioEvent.js';
59
74
 
60
75
  const ShareProjectModal = observer(
61
76
  (props: { open: boolean; closeModal: () => void }) => {
@@ -180,10 +195,10 @@ const WorkspaceViewer = observer((props: { workspace: Workspace }) => {
180
195
  const { workspace } = props;
181
196
  const editorStore = useEditorStore();
182
197
  const applicationStore = useApplicationStore();
183
- const isActive = areWorkspacesEquivalent(
184
- editorStore.sdlcState.activeWorkspace,
185
- workspace,
186
- );
198
+ const isActive =
199
+ areWorkspacesEquivalent(editorStore.sdlcState.activeWorkspace, workspace) &&
200
+ workspace.source ===
201
+ editorStore.sdlcState.activePatch?.patchReleaseVersionId.id;
187
202
  const [isSelectedFromContextMenu, setIsSelectedFromContextMenu] =
188
203
  useState(false);
189
204
  const onContextMenuOpen = (): void => setIsSelectedFromContextMenu(true);
@@ -210,6 +225,7 @@ const WorkspaceViewer = observer((props: { workspace: Workspace }) => {
210
225
  applicationStore.navigationService.navigator.generateAddress(
211
226
  generateEditorRoute(
212
227
  workspace.projectId,
228
+ editorStore.sdlcState.activePatch?.patchReleaseVersionId.id,
213
229
  workspace.workspaceId,
214
230
  workspace.workspaceType,
215
231
  ),
@@ -226,9 +242,14 @@ const WorkspaceViewer = observer((props: { workspace: Workspace }) => {
226
242
  <UserIcon />
227
243
  )}
228
244
  </div>
229
- <div className="project-overview__item__link__content__name">
245
+ <div className="project-overview__item__link__content__name project-overview__workspace__viewer__label">
230
246
  {workspace.workspaceId}
231
247
  </div>
248
+ {workspace.source && (
249
+ <div className="project-overview__workspace__viewer__source">
250
+ {`patch/${workspace.source}`}
251
+ </div>
252
+ )}
232
253
  </div>
233
254
  </button>
234
255
  </ContextMenu>
@@ -276,7 +297,7 @@ const WorkspacesViewer = observer(() => {
276
297
  >
277
298
  {workspaces.map((workspace) => (
278
299
  <WorkspaceViewer
279
- key={`${workspace.workspaceType}.${workspace.workspaceId}`}
300
+ key={`${workspace.workspaceType}.${workspace.workspaceId}.${workspace.source}`}
280
301
  workspace={workspace}
281
302
  />
282
303
  ))}
@@ -324,7 +345,11 @@ const ReleaseEditor = observer(() => {
324
345
  flowResult(projectOverviewState.fetchLatestProjectVersion()).catch(
325
346
  applicationStore.alertUnhandledError,
326
347
  );
327
- }, [applicationStore, projectOverviewState]);
348
+ }, [
349
+ applicationStore,
350
+ editorStore.sdlcState.activePatch,
351
+ projectOverviewState,
352
+ ]);
328
353
 
329
354
  return (
330
355
  <div className="panel side-bar__panel project-overview__panel project-overview__release">
@@ -337,7 +362,11 @@ const ReleaseEditor = observer(() => {
337
362
  </div>
338
363
  <div className="panel__content project-overview__release__panel__content project-overview__release__content">
339
364
  <PanelLoadingIndicator isLoading={isDispatchingAction} />
340
- <div className="project-overview__release__editor">
365
+ <div
366
+ className={clsx(
367
+ 'project-overview__release__editor project-overview__release__editor__project',
368
+ )}
369
+ >
341
370
  <textarea
342
371
  className="project-overview__release__editor__input input--dark"
343
372
  spellCheck={false}
@@ -483,6 +512,306 @@ const ReleaseEditor = observer(() => {
483
512
  );
484
513
  });
485
514
 
515
+ const PatchEditor = observer(() => {
516
+ const editorStore = useEditorStore();
517
+ const applicationStore = useLegendStudioApplicationStore();
518
+ const projectOverviewState = editorStore.projectOverviewState;
519
+ const [versionOptions] = useState<VersionOption[]>(
520
+ editorStore.sdlcState.projectVersions.map((v) => ({
521
+ label: v.id.id,
522
+ value: v.id.id,
523
+ })),
524
+ );
525
+ const [selectedVersionOption, setSelectedVersionOption] =
526
+ useState<VersionOption | null>(null);
527
+ const patches = projectOverviewState.patches;
528
+ const [selectedPatchOption, setSelectedPatchOption] =
529
+ useState<PatchOption | null>(null);
530
+ const [workspaceName, setWorkspaceName] = useState<string>('');
531
+ const [isGroupWorkspace, setIsGroupWorkspace] = useState<boolean>(true);
532
+ const [committedReviews, setCommittedReviews] = useState<Review[]>([]);
533
+ const [fetchSelectedPatchCommittedReviews] = useState(ActionState.create());
534
+ const onPatchOptionChange = async (
535
+ val: PatchOption | null,
536
+ ): Promise<void> => {
537
+ if (
538
+ (val !== null || selectedPatchOption !== null) &&
539
+ (!val || !selectedPatchOption || val.value !== selectedPatchOption.value)
540
+ ) {
541
+ setSelectedPatchOption(val);
542
+ fetchSelectedPatchCommittedReviews.inProgress();
543
+ try {
544
+ if (val && val.value instanceof Patch) {
545
+ const reviews = await editorStore.sdlcServerClient.getReviews(
546
+ projectOverviewState.sdlcState.activeProject.projectId,
547
+ val.value.patchReleaseVersionId.id,
548
+ {
549
+ state: ReviewState.COMMITTED,
550
+ },
551
+ );
552
+ setCommittedReviews(
553
+ reviews.map((v) => Review.serialization.fromJson(v)),
554
+ );
555
+ } else {
556
+ setCommittedReviews([]);
557
+ }
558
+ } catch (error) {
559
+ assertErrorThrown(error);
560
+ editorStore.applicationStore.logService.error(
561
+ LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
562
+ error,
563
+ );
564
+ } finally {
565
+ fetchSelectedPatchCommittedReviews.reset();
566
+ }
567
+ } else {
568
+ setCommittedReviews([]);
569
+ }
570
+ };
571
+ const onVersionOptionChange = (val: VersionOption | null): void => {
572
+ if (
573
+ (val !== null || selectedVersionOption !== null) &&
574
+ (!val ||
575
+ !selectedVersionOption ||
576
+ val.value !== selectedVersionOption.value)
577
+ ) {
578
+ setSelectedVersionOption(val);
579
+ }
580
+ };
581
+ const createPatch = (): void => {
582
+ if (selectedVersionOption) {
583
+ flowResult(
584
+ projectOverviewState.createPatch(
585
+ selectedVersionOption.value,
586
+ workspaceName,
587
+ isGroupWorkspace ? WorkspaceType.GROUP : WorkspaceType.USER,
588
+ ),
589
+ ).catch(applicationStore.alertUnhandledError);
590
+ }
591
+ };
592
+ const toggleGroupWorkspace = (): void => {
593
+ setIsGroupWorkspace(!isGroupWorkspace);
594
+ };
595
+ const changeWorkspaceName: React.ChangeEventHandler<HTMLInputElement> = (
596
+ event,
597
+ ) => setWorkspaceName(event.target.value);
598
+ const isDispatchingAction =
599
+ projectOverviewState.isFetchingLatestVersion ||
600
+ projectOverviewState.isFetchingCurrentProjectRevision ||
601
+ projectOverviewState.isCreatingVersion;
602
+ const { latestProjectVersion, currentProjectRevision } = projectOverviewState;
603
+ const createPatchRelease = applicationStore.guardUnhandledError(() =>
604
+ flowResult(
605
+ projectOverviewState.createPatchVersion(
606
+ guaranteeNonNullable(selectedPatchOption).label,
607
+ ),
608
+ ),
609
+ );
610
+ const isCurrentProjectVersionLatest =
611
+ Boolean(latestProjectVersion) &&
612
+ latestProjectVersion?.revisionId === currentProjectRevision?.id;
613
+ const canCreateVersion =
614
+ !isCurrentProjectVersionLatest &&
615
+ !isDispatchingAction &&
616
+ editorStore.sdlcServerClient.features.canCreateVersion;
617
+ const patchBlurb = `Releasing above mentioned version would delete the upstream branch created for doing this patch release. By doing so you won't be access any of the branches created for development for doing this patch release`;
618
+
619
+ useEffect(() => {
620
+ flowResult(projectOverviewState.fetchPatches()).catch(
621
+ applicationStore.alertUnhandledError,
622
+ );
623
+ }, [
624
+ applicationStore.alertUnhandledError,
625
+ applicationStore.notificationService,
626
+ editorStore.sdlcServerClient,
627
+ editorStore.sdlcState.activeProject.projectId,
628
+
629
+ projectOverviewState,
630
+ ]);
631
+
632
+ return (
633
+ <div className="panel side-bar__panel project-overview__panel project-overview__patch">
634
+ <div className="panel__header">
635
+ <div className="panel__header__title">
636
+ <div className="panel__header__title__content">
637
+ Create Patch
638
+ <DocumentationLink
639
+ className="project-overview__patch__documentation"
640
+ documentationKey={LEGEND_STUDIO_DOCUMENTATION_KEY.CREATE_PATCH}
641
+ />
642
+ </div>
643
+ </div>
644
+ </div>
645
+ <div className="panel__content project-overview__panel__content">
646
+ <div
647
+ className={clsx('project-overview__patch__create', {
648
+ 'project-overview__patch__create--progress':
649
+ projectOverviewState.createPatchState.isInProgress,
650
+ })}
651
+ >
652
+ <PanelLoadingIndicator
653
+ isLoading={projectOverviewState.createPatchState.isInProgress}
654
+ />
655
+ <div className="project-overview__patch__content__progress-msg">
656
+ {projectOverviewState.createPatchState.message}
657
+ </div>
658
+ <div className="panel__content__form">
659
+ <div className="panel__content__form__section">
660
+ <div className="panel__content__form__section__header__label">
661
+ Source Version
662
+ </div>
663
+ <CustomSelectorInput
664
+ className="project-overview__patch__source__version__selector"
665
+ options={versionOptions}
666
+ onChange={onVersionOptionChange}
667
+ value={selectedVersionOption}
668
+ placeholder={'Select source version'}
669
+ isClearable={true}
670
+ escapeClearsValue={true}
671
+ darkMode={true}
672
+ />
673
+ </div>
674
+ </div>
675
+ <div className="panel__content__form">
676
+ <div className="panel__content__form__section">
677
+ <div className="panel__content__form__section__header__label">
678
+ Workspace Name
679
+ </div>
680
+ <input
681
+ className="panel__content__form__section__input"
682
+ title="Workspace Name"
683
+ spellCheck={false}
684
+ value={workspaceName}
685
+ placeholder="Workspace Name"
686
+ onChange={changeWorkspaceName}
687
+ />
688
+ </div>
689
+ </div>
690
+ <div className="panel__content__form__section project-overview__patch__workspace__type__button">
691
+ <PanelFormBooleanField
692
+ name="Group Workspace"
693
+ prompt="Group workspaces can be accessed by all users in the project"
694
+ value={isGroupWorkspace}
695
+ isReadOnly={false}
696
+ update={toggleGroupWorkspace}
697
+ />
698
+ </div>
699
+ <div className="panel__content__form__section__list__new-item__add">
700
+ <button
701
+ disabled={
702
+ projectOverviewState.createPatchState.isInProgress ||
703
+ !editorStore.sdlcServerClient.features.canCreateVersion ||
704
+ !selectedVersionOption
705
+ }
706
+ onClick={(): void => createPatch()}
707
+ className="panel__content__form__section__list__new-item__add-btn btn btn--dark project-overview__patch__create__button"
708
+ >
709
+ Create
710
+ </button>
711
+ </div>
712
+ </div>
713
+ <div className="project-overview__patch__release">
714
+ <div className="panel__header">
715
+ <div className="panel__header__title">
716
+ <div className="panel__header__title__content">Release Patch</div>
717
+ </div>
718
+ </div>
719
+ <div className="project-overview__patch__release">
720
+ <PanelLoadingIndicator isLoading={isDispatchingAction} />
721
+ <>
722
+ <div className="panel__content__form">
723
+ <div className="panel__content__form__section">
724
+ <div className="panel__content__form__section__header__label">
725
+ Patch
726
+ </div>
727
+ <CustomSelectorInput
728
+ className="project-overview__patch__source__version__selector"
729
+ options={patches.map((p) => ({
730
+ label: p.patchReleaseVersionId.id,
731
+ value: p,
732
+ }))}
733
+ onChange={onPatchOptionChange}
734
+ value={selectedPatchOption}
735
+ placeholder={'Select patch you want to release'}
736
+ isClearable={true}
737
+ escapeClearsValue={true}
738
+ darkMode={true}
739
+ />
740
+ </div>
741
+ </div>
742
+ <div className="project-overview__patch__release__content">
743
+ {patchBlurb}
744
+ </div>
745
+ <div className="panel__content__form__section__list__new-item__add">
746
+ <button
747
+ className="panel__content__form__section__list__new-item__add-btn btn btn--dark project-overview__patch__release__button"
748
+ onClick={createPatchRelease}
749
+ disabled={!canCreateVersion && !selectedPatchOption}
750
+ title={'Create a patch release'}
751
+ >
752
+ Release
753
+ </button>
754
+ </div>
755
+ </>
756
+ </div>
757
+ </div>
758
+
759
+ <div className="project-overview__release__info">
760
+ <div className="panel project-overview__release__info__reviews">
761
+ <div className="panel__header">
762
+ <div className="panel__header__title">
763
+ <div className="panel__header__title__content">
764
+ COMMITTED REVIEWS
765
+ </div>
766
+ <div
767
+ className="side-bar__panel__title__info"
768
+ title="All committed reviews in the patch since it got created"
769
+ >
770
+ <InfoCircleIcon />
771
+ </div>
772
+ </div>
773
+ <div
774
+ className="side-bar__panel__header__changes-count"
775
+ data-testid={
776
+ LEGEND_STUDIO_TEST_ID.SIDEBAR_PANEL_HEADER__CHANGES_COUNT
777
+ }
778
+ >
779
+ {committedReviews.length}
780
+ </div>
781
+ </div>
782
+ <PanelContent>
783
+ {committedReviews.map((review) => (
784
+ <button
785
+ key={review.id}
786
+ className="side-bar__panel__item workspace-updater__review__link"
787
+ tabIndex={-1}
788
+ onClick={(): void =>
789
+ applicationStore.navigationService.navigator.visitAddress(
790
+ applicationStore.navigationService.navigator.generateAddress(
791
+ generateReviewRoute(review.projectId, review.id),
792
+ ),
793
+ )
794
+ }
795
+ title="See review"
796
+ >
797
+ <div className="workspace-updater__review">
798
+ <span className="workspace-updater__review__name">
799
+ {review.title}
800
+ </span>
801
+ <span className="workspace-updater__review__info">
802
+ {review.author.name}
803
+ </span>
804
+ </div>
805
+ </button>
806
+ ))}
807
+ </PanelContent>
808
+ </div>
809
+ </div>
810
+ </div>
811
+ </div>
812
+ );
813
+ });
814
+
486
815
  const VersionsViewer = observer(() => {
487
816
  const editorStore = useEditorStore();
488
817
  const applicationStore = useLegendStudioApplicationStore();
@@ -815,6 +1144,7 @@ export const ProjectOverviewActivityBar = observer(() => {
815
1144
  },
816
1145
  { mode: PROJECT_OVERVIEW_ACTIVITY_MODE.VERSIONS, title: 'Versions' },
817
1146
  { mode: PROJECT_OVERVIEW_ACTIVITY_MODE.WORKSPACES, title: 'Workspaces' },
1147
+ { mode: PROJECT_OVERVIEW_ACTIVITY_MODE.PATCH, title: 'Patch' },
818
1148
  ]
819
1149
  .filter((activity): activity is ProjectOverviewActivityDisplay =>
820
1150
  Boolean(activity),
@@ -876,6 +1206,8 @@ export const ProjectOverview = observer(() => {
876
1206
  return <VersionsViewer />;
877
1207
  case PROJECT_OVERVIEW_ACTIVITY_MODE.WORKSPACES:
878
1208
  return <WorkspacesViewer />;
1209
+ case PROJECT_OVERVIEW_ACTIVITY_MODE.PATCH:
1210
+ return <PatchEditor />;
879
1211
  default:
880
1212
  return null;
881
1213
  }
@@ -115,7 +115,7 @@ const ProjectViewerStatusBar = observer(() => {
115
115
  onClick={(): void =>
116
116
  applicationStore.navigationService.navigator.visitAddress(
117
117
  applicationStore.navigationService.navigator.generateAddress(
118
- generateSetupRoute(projectId),
118
+ generateSetupRoute(projectId, undefined),
119
119
  ),
120
120
  )
121
121
  }
@@ -87,7 +87,7 @@ const WorkspaceReviewStatusBar = observer(() => {
87
87
  onClick={(): void =>
88
88
  applicationStore.navigationService.navigator.visitAddress(
89
89
  applicationStore.navigationService.navigator.generateAddress(
90
- generateSetupRoute(reviewStore.projectId),
90
+ generateSetupRoute(reviewStore.projectId, undefined),
91
91
  ),
92
92
  )
93
93
  }
@@ -25,14 +25,23 @@ import {
25
25
  PanelDivider,
26
26
  PanelForm,
27
27
  PanelFormBooleanField,
28
+ CustomSelectorInput,
28
29
  } from '@finos/legend-art';
29
30
  import { flowResult } from 'mobx';
30
- import { type Project, WorkspaceType } from '@finos/legend-server-sdlc';
31
+ import { type Project, WorkspaceType, Patch } from '@finos/legend-server-sdlc';
31
32
  import { useApplicationStore } from '@finos/legend-application';
32
33
  import { LEGEND_STUDIO_DOCUMENTATION_KEY } from '../../__lib__/LegendStudioDocumentation.js';
33
- import { useWorkspaceSetupStore } from './WorkspaceSetup.js';
34
+ import {
35
+ DEFAULT_WORKSPACE_SOURCE,
36
+ useWorkspaceSetupStore,
37
+ } from './WorkspaceSetup.js';
34
38
  import { DocumentationLink } from '@finos/legend-lego/application';
35
39
 
40
+ export interface PatchOption {
41
+ label: string;
42
+ value: Patch | string;
43
+ }
44
+
36
45
  export const CreateWorkspaceModal = observer(
37
46
  (props: { selectedProject: Project }) => {
38
47
  const { selectedProject } = props;
@@ -41,6 +50,35 @@ export const CreateWorkspaceModal = observer(
41
50
  const workspaceNameInputRef = useRef<HTMLInputElement>(null);
42
51
  const [workspaceName, setWorkspaceName] = useState('');
43
52
  const [isGroupWorkspace, setIsGroupWorkspace] = useState<boolean>(true);
53
+ const [patchOptions] = useState<PatchOption[]>(
54
+ [
55
+ {
56
+ label: DEFAULT_WORKSPACE_SOURCE,
57
+ value: DEFAULT_WORKSPACE_SOURCE,
58
+ } as PatchOption,
59
+ ].concat(
60
+ setupStore.patches.map((p) => ({
61
+ label: `patch/${p.patchReleaseVersionId.id}`,
62
+ value: p,
63
+ })),
64
+ ),
65
+ );
66
+ const [selectedPatchOption, setSelectedPatchOption] =
67
+ useState<PatchOption | null>({
68
+ label: DEFAULT_WORKSPACE_SOURCE,
69
+ value: DEFAULT_WORKSPACE_SOURCE,
70
+ });
71
+
72
+ const onPatchOptionChange = (val: PatchOption | null): void => {
73
+ if (
74
+ (val !== null || selectedPatchOption !== null) &&
75
+ (!val ||
76
+ !selectedPatchOption ||
77
+ val.value !== selectedPatchOption.value)
78
+ ) {
79
+ setSelectedPatchOption(val);
80
+ }
81
+ };
44
82
 
45
83
  const workspaceAlreadyExists = Boolean(
46
84
  setupStore.workspaces.find(
@@ -49,7 +87,12 @@ export const CreateWorkspaceModal = observer(
49
87
  ((workspace.workspaceType === WorkspaceType.GROUP &&
50
88
  isGroupWorkspace) ||
51
89
  (workspace.workspaceType === WorkspaceType.USER &&
52
- !isGroupWorkspace)),
90
+ !isGroupWorkspace)) &&
91
+ ((!workspace.source &&
92
+ !(selectedPatchOption?.value instanceof Patch)) ||
93
+ (selectedPatchOption?.value instanceof Patch &&
94
+ workspace.source ===
95
+ selectedPatchOption.value.patchReleaseVersionId.id)),
53
96
  ),
54
97
  );
55
98
  const createWorkspace = (): void => {
@@ -60,6 +103,9 @@ export const CreateWorkspaceModal = observer(
60
103
  flowResult(
61
104
  setupStore.createWorkspace(
62
105
  selectedProject.projectId,
106
+ selectedPatchOption?.value instanceof Patch
107
+ ? selectedPatchOption.value.patchReleaseVersionId.id
108
+ : undefined,
63
109
  workspaceName,
64
110
  isGroupWorkspace ? WorkspaceType.GROUP : WorkspaceType.USER,
65
111
  ),
@@ -130,7 +176,20 @@ export const CreateWorkspaceModal = observer(
130
176
  : ''
131
177
  }
132
178
  />
133
-
179
+ <div className="workspace-setup__create-workspace-modal__form__workspace--source">
180
+ <div className="workspace-setup__create-workspace-modal__form__workspace--source__label">
181
+ Workspace Source
182
+ </div>
183
+ <CustomSelectorInput
184
+ className="workspace-setup__create-workspace-modal__form__workspace--source__selector"
185
+ options={patchOptions}
186
+ onChange={onPatchOptionChange}
187
+ value={selectedPatchOption}
188
+ isClearable={true}
189
+ escapeClearsValue={true}
190
+ darkMode={true}
191
+ />
192
+ </div>
134
193
  <PanelFormBooleanField
135
194
  name="Group Workspace"
136
195
  prompt="Group workspaces can be accessed by all users in the project"