@finos/legend-studio
Version:
385 lines (367 loc) • 13.5 kB
text/typescript
/**
* Copyright (c) 2020-present, Goldman Sachs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { action, makeAutoObservable, flowResult } from 'mobx';
import type { EditorStore } from '../EditorStore.js';
import type { EditorSDLCState } from '../EditorSDLCState.js';
import { CHANGE_DETECTION_EVENT } from '../ChangeDetectionEvent.js';
import { LEGEND_STUDIO_APP_EVENT } from '../LegendStudioAppEvent.js';
import {
type GeneratorFn,
type PlainObject,
LogEvent,
assertErrorThrown,
assertNonNullable,
guaranteeNonNullable,
} from '@finos/legend-shared';
import { EntityDiffViewState } from '../editor-state/entity-diff-editor-state/EntityDiffViewState.js';
import { SPECIAL_REVISION_ALIAS } from '../editor-state/entity-diff-editor-state/EntityDiffEditorState.js';
import { generateSetupRoute } from '../LegendStudioRouter.js';
import type { Entity } from '@finos/legend-model-storage';
import {
type Revision,
EntityDiff,
Review,
ReviewState,
RevisionAlias,
} from '@finos/legend-server-sdlc';
import { ActionAlertActionType } from '@finos/legend-application';
export class WorkspaceReviewState {
editorStore: EditorStore;
sdlcState: EditorSDLCState;
reviewTitle = '';
committedReviewsBetweenWorkspaceBaseAndProjectLatest: Review[] = [];
workspaceReview?: Review | undefined;
isUpdatingWorkspace = false;
isRefreshingWorkspaceUpdater = false;
isFetchingCurrentWorkspaceReview = false;
isRefreshingWorkspaceChangesDetector = false;
isClosingWorkspaceReview = false;
isCreatingWorkspaceReview = false;
isCommittingWorkspaceReview = false;
isRecreatingWorkspaceAfterCommittingReview = false;
constructor(editorStore: EditorStore, sdlcState: EditorSDLCState) {
makeAutoObservable(this, {
editorStore: false,
sdlcState: false,
setReviewTitle: action,
openReviewChange: action,
});
this.editorStore = editorStore;
this.sdlcState = sdlcState;
}
setReviewTitle = (val: string): void => {
this.reviewTitle = val;
};
openReviewChange(diff: EntityDiff): void {
const fromEntityGetter = (
entityPath: string | undefined,
): Entity | undefined =>
entityPath
? this.editorStore.changeDetectionState.workspaceBaseRevisionState.entities.find(
(e) => e.path === entityPath,
)
: undefined;
const toEntityGetter = (
entityPath: string | undefined,
): Entity | undefined =>
entityPath
? this.editorStore.changeDetectionState.workspaceLocalLatestRevisionState.entities.find(
(e) => e.path === entityPath,
)
: undefined;
const fromEntity = EntityDiff.shouldOldEntityExist(diff)
? guaranteeNonNullable(
fromEntityGetter(diff.getValidatedOldPath()),
`Can't find entity with path '${diff.oldPath}'`,
)
: undefined;
const toEntity = EntityDiff.shouldNewEntityExist(diff)
? guaranteeNonNullable(
toEntityGetter(diff.getValidatedNewPath()),
`Can't find entity with path '${diff.newPath}'`,
)
: undefined;
this.editorStore.openEntityDiff(
new EntityDiffViewState(
this.editorStore,
SPECIAL_REVISION_ALIAS.WORKSPACE_BASE,
SPECIAL_REVISION_ALIAS.WORKSPACE_HEAD,
diff.oldPath,
diff.newPath,
fromEntity,
toEntity,
fromEntityGetter,
toEntityGetter,
),
);
}
*refreshWorkspaceChanges(): GeneratorFn<void> {
const startTime = Date.now();
this.isRefreshingWorkspaceChangesDetector = true;
try {
// ======= (RE)START CHANGE DETECTION =======
this.editorStore.changeDetectionState.stop();
yield Promise.all([
this.sdlcState.buildWorkspaceLatestRevisionEntityHashesIndex(),
this.sdlcState.buildWorkspaceBaseRevisionEntityHashesIndex(),
]);
this.editorStore.changeDetectionState.start();
yield Promise.all([
this.editorStore.changeDetectionState.computeAggregatedProjectLatestChanges(
true,
),
this.editorStore.changeDetectionState.computeAggregatedWorkspaceChanges(
true,
),
]);
this.editorStore.applicationStore.log.info(
LogEvent.create(CHANGE_DETECTION_EVENT.CHANGE_DETECTION_RESTARTED),
Date.now() - startTime,
'ms',
);
// ======= FINISHED (RE)START CHANGE DETECTION =======
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notifyError(error);
this.sdlcState.handleChangeDetectionRefreshIssue(error);
} finally {
this.isRefreshingWorkspaceChangesDetector = false;
}
}
*fetchCurrentWorkspaceReview(): GeneratorFn<void> {
try {
this.isFetchingCurrentWorkspaceReview = true;
const currentWorkspaceRevision =
(yield this.editorStore.sdlcServerClient.getRevision(
this.sdlcState.activeProject.projectId,
this.sdlcState.activeWorkspace,
RevisionAlias.CURRENT,
)) as Revision;
const reviews = (yield this.editorStore.sdlcServerClient.getReviews(
this.sdlcState.activeProject.projectId,
ReviewState.OPEN,
[currentWorkspaceRevision.id, currentWorkspaceRevision.id],
undefined,
undefined,
1,
)) as Review[];
const review = reviews.find(
(r) =>
r.workspaceId === this.sdlcState.activeWorkspace.workspaceId &&
r.workspaceType === this.sdlcState.activeWorkspace.workspaceType,
) as PlainObject<Review> | undefined;
if (reviews.length) {
try {
assertNonNullable(
review,
`Opened review associated with HEAD revision '${currentWorkspaceRevision.id}' of workspace '${this.sdlcState.activeWorkspace.workspaceType}' found, but the retrieved review does not belong to the workspace`,
);
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.notifyWarning(error.message);
}
}
this.workspaceReview = review
? Review.serialization.fromJson(review)
: undefined;
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notifyError(error);
this.sdlcState.handleChangeDetectionRefreshIssue(error);
} finally {
this.isFetchingCurrentWorkspaceReview = false;
}
}
*recreateWorkspaceAfterCommittingReview(): GeneratorFn<void> {
try {
this.isRecreatingWorkspaceAfterCommittingReview = true;
this.editorStore.setBlockingAlert({
message: 'Re-creating workspace...',
prompt: 'Please do not close the application',
showLoading: true,
});
yield this.editorStore.sdlcServerClient.createWorkspace(
this.sdlcState.activeProject.projectId,
this.sdlcState.activeWorkspace.workspaceId,
this.sdlcState.activeWorkspace.workspaceType,
);
this.editorStore.applicationStore.navigator.reload();
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notifyError(error);
} finally {
this.editorStore.setBlockingAlert(undefined);
this.isRecreatingWorkspaceAfterCommittingReview = false;
}
}
*closeWorkspaceReview(): GeneratorFn<void> {
if (!this.workspaceReview) {
return;
}
this.isClosingWorkspaceReview = true;
try {
yield this.editorStore.sdlcServerClient.rejectReview(
this.sdlcState.activeProject.projectId,
this.workspaceReview.id,
);
this.workspaceReview = undefined;
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notifyError(error);
} finally {
this.isClosingWorkspaceReview = false;
}
}
/**
* Below functions will be specific to properties on the project such as reviews, tags, etc..
*/
*createWorkspaceReview(
title: string,
reviewDescription?: string,
): GeneratorFn<void> {
// NOTE: We will only allow having dependencies on snapshots in workspace, not in project
// Therefore, we block creation of review and committing review containing a change
// in dependency on snapshot versions
if (
this.editorStore.projectConfigurationEditorState
.containsSnapshotDependencies
) {
this.editorStore.applicationStore.notifyWarning(
`Can't create review: workspace contains snapshot dependencies`,
);
return;
}
this.isCreatingWorkspaceReview = true;
try {
const description =
reviewDescription ??
`review from ${this.editorStore.applicationStore.config.appName} for workspace ${this.sdlcState.activeWorkspace.workspaceId}`;
this.workspaceReview = Review.serialization.fromJson(
(yield this.editorStore.sdlcServerClient.createReview(
this.sdlcState.activeProject.projectId,
{
workspaceId: this.sdlcState.activeWorkspace.workspaceId,
title,
workspaceType: this.sdlcState.activeWorkspace.workspaceType,
description,
},
)) as PlainObject<Review>,
);
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notifyError(error);
} finally {
this.isCreatingWorkspaceReview = false;
}
}
*commitWorkspaceReview(review: Review): GeneratorFn<void> {
// NOTE: We will only allow having dependencies on snapshots in workspace, not in project
// Therefore, we block creation of review and committing review containing a change
// in dependency on snapshot versions
if (
this.editorStore.projectConfigurationEditorState
.containsSnapshotDependencies
) {
this.editorStore.applicationStore.notifyWarning(
`Can't commit review: workspace contains snapshot dependencies`,
);
return;
}
this.isCommittingWorkspaceReview = true;
// check if the workspace is in conflict resolution mode
try {
const isInConflictResolutionMode = (yield flowResult(
this.sdlcState.checkIfCurrentWorkspaceIsInConflictResolutionMode(),
)) as boolean;
if (isInConflictResolutionMode) {
this.editorStore.setBlockingAlert({
message: 'Workspace is in conflict resolution mode',
prompt: 'Please refresh the application',
});
return;
}
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.notifyWarning(
'Failed to check if current workspace is in conflict resolution mode',
);
return;
}
try {
yield this.editorStore.sdlcServerClient.commitReview(
this.sdlcState.activeProject.projectId,
review.id,
{ message: `${review.title} [review]` },
);
this.editorStore.setActionAlertInfo({
message: 'Committed review successfully',
prompt:
'You can create a new workspace with the same name or leave for the start page',
onEnter: (): void => this.editorStore.setBlockGlobalHotkeys(true),
onClose: (): void => this.editorStore.setBlockGlobalHotkeys(false),
actions: [
{
label: 'Create new workspace',
handler: this.editorStore.applicationStore.guardUnhandledError(() =>
flowResult(this.recreateWorkspaceAfterCommittingReview()),
),
type: ActionAlertActionType.PROCEED,
},
{
label: 'Leave',
type: ActionAlertActionType.PROCEED,
handler: (): void =>
this.editorStore.applicationStore.navigator.goTo(
generateSetupRoute(
this.editorStore.sdlcState.activeProject.projectId,
),
),
default: true,
},
],
});
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notifyError(error);
} finally {
this.isCommittingWorkspaceReview = false;
}
}
}