@finos/legend-studio
Version:
444 lines (419 loc) • 14.7 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, flowResult, makeAutoObservable } from 'mobx';
import type { EditorStore } from './EditorStore.js';
import {
type PlainObject,
type GeneratorFn,
assertErrorThrown,
LogEvent,
NetworkClientError,
HttpStatus,
guaranteeNonNullable,
assertTrue,
} from '@finos/legend-shared';
import { CHANGE_DETECTION_EVENT } from './ChangeDetectionEvent.js';
import { EDITOR_MODE, ACTIVITY_MODE } from './EditorConfig.js';
import {
type Entity,
extractEntityNameFromPath,
} from '@finos/legend-model-storage';
import {
type EntityDiff,
type WorkspaceType,
Workflow,
Project,
Revision,
RevisionAlias,
Version,
Workspace,
WorkspaceAccessType,
} from '@finos/legend-server-sdlc';
import { LEGEND_STUDIO_APP_EVENT } from './LegendStudioAppEvent.js';
export const entityDiffSorter = (a: EntityDiff, b: EntityDiff): number =>
extractEntityNameFromPath(a.newPath ?? a.oldPath ?? '').localeCompare(
extractEntityNameFromPath(b.newPath ?? b.oldPath ?? ''),
);
export class EditorSDLCState {
editorStore: EditorStore;
currentProject?: Project | undefined;
currentWorkspace?: Workspace | undefined;
remoteWorkspaceRevision?: Revision | undefined;
currentRevision?: Revision | undefined;
isWorkspaceOutdated = false;
workspaceWorkflows: Workflow[] = [];
projectVersions: Version[] = [];
isCheckingIfWorkspaceIsOutdated = false;
isFetchingProjectVersions = false;
isFetchingProject = false;
constructor(editorStore: EditorStore) {
makeAutoObservable(this, {
editorStore: false,
setCurrentProject: action,
setCurrentWorkspace: action,
setCurrentRevision: action,
setWorkspaceLatestRevision: action,
});
this.editorStore = editorStore;
}
get activeProject(): Project {
return guaranteeNonNullable(
this.currentProject,
`Active project has not been properly set`,
);
}
get activeWorkspace(): Workspace {
return guaranteeNonNullable(
this.currentWorkspace,
`Active workspace has not been properly set`,
);
}
get activeRevision(): Revision {
return guaranteeNonNullable(
this.currentRevision,
`Active revision has not been properly set`,
);
}
get activeRemoteWorkspaceRevision(): Revision {
return guaranteeNonNullable(
this.remoteWorkspaceRevision,
`Active workspace latest revision has not been properly set`,
);
}
get isWorkspaceOutOfSync(): boolean {
return this.activeRemoteWorkspaceRevision.id !== this.activeRevision.id;
}
setCurrentProject = (val: Project): void => {
this.currentProject = val;
};
setCurrentWorkspace = (val: Workspace): void => {
this.currentWorkspace = val;
};
setCurrentRevision = (val: Revision): void => {
this.currentRevision = val;
};
setWorkspaceLatestRevision = (val: Revision): void => {
this.remoteWorkspaceRevision = val;
};
*fetchCurrentProject(
projectId: string,
options?: { suppressNotification?: boolean },
): GeneratorFn<void> {
try {
this.isFetchingProject = true;
this.currentProject = Project.serialization.fromJson(
(yield this.editorStore.sdlcServerClient.getProject(
projectId,
)) as PlainObject<Project>,
);
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
if (!options?.suppressNotification) {
this.editorStore.applicationStore.notifyError(error);
}
} finally {
this.isFetchingProject = false;
}
}
*fetchCurrentWorkspace(
projectId: string,
workspaceId: string,
workspaceType: WorkspaceType,
options?: { suppressNotification?: boolean },
): GeneratorFn<void> {
try {
this.currentWorkspace = Workspace.serialization.fromJson(
(yield this.editorStore.sdlcServerClient.getWorkspace(
projectId,
workspaceId,
workspaceType,
)) as PlainObject<Workspace>,
);
const isInConflictResolutionMode = (yield flowResult(
this.checkIfCurrentWorkspaceIsInConflictResolutionMode(),
)) as boolean;
if (isInConflictResolutionMode) {
this.editorStore.setMode(EDITOR_MODE.CONFLICT_RESOLUTION);
this.currentWorkspace.accessType =
WorkspaceAccessType.CONFLICT_RESOLUTION;
this.editorStore.setActiveActivity(ACTIVITY_MODE.CONFLICT_RESOLUTION);
}
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
if (!options?.suppressNotification) {
this.editorStore.applicationStore.notifyError(error);
}
}
}
*fetchProjectVersions(): GeneratorFn<void> {
try {
this.isFetchingProjectVersions = true;
this.projectVersions = (
(yield this.editorStore.sdlcServerClient.getVersions(
this.activeProject.projectId,
)) as PlainObject<Version>[]
).map((v) => Version.serialization.fromJson(v));
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
} finally {
this.isFetchingProjectVersions = false;
}
}
*checkIfCurrentWorkspaceIsInConflictResolutionMode(): GeneratorFn<boolean> {
return (yield this.editorStore.sdlcServerClient.checkIfWorkspaceIsInConflictResolutionMode(
this.activeProject.projectId,
this.activeWorkspace,
)) as boolean;
}
*fetchRemoteWorkspaceRevision(
projectId: string,
workspace: Workspace,
): GeneratorFn<void> {
try {
this.editorStore.localChangesState.workspaceSyncState.setIncomingRevisions(
[],
);
const latestRevision = Revision.serialization.fromJson(
this.editorStore.isInConflictResolutionMode
? ((yield this.editorStore.sdlcServerClient.getConflictResolutionRevision(
projectId,
workspace,
RevisionAlias.CURRENT,
)) as PlainObject<Revision>)
: ((yield this.editorStore.sdlcServerClient.getRevision(
projectId,
workspace,
RevisionAlias.CURRENT,
)) as PlainObject<Revision>),
);
this.setWorkspaceLatestRevision(latestRevision);
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notifyError(error);
}
}
*fetchCurrentRevision(
projectId: string,
workspace: Workspace,
): GeneratorFn<void> {
try {
const currentRevision = Revision.serialization.fromJson(
this.editorStore.isInConflictResolutionMode
? ((yield this.editorStore.sdlcServerClient.getConflictResolutionRevision(
projectId,
workspace,
RevisionAlias.CURRENT,
)) as PlainObject<Revision>)
: ((yield this.editorStore.sdlcServerClient.getRevision(
projectId,
workspace,
RevisionAlias.CURRENT,
)) as PlainObject<Revision>),
);
this.setCurrentRevision(currentRevision);
this.setWorkspaceLatestRevision(currentRevision);
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notifyError(error);
}
}
*checkIfWorkspaceIsOutdated(): GeneratorFn<void> {
try {
this.isCheckingIfWorkspaceIsOutdated = true;
this.isWorkspaceOutdated = this.editorStore.isInConflictResolutionMode
? ((yield this.editorStore.sdlcServerClient.isConflictResolutionOutdated(
this.activeProject.projectId,
this.activeWorkspace,
)) as boolean)
: ((yield this.editorStore.sdlcServerClient.isWorkspaceOutdated(
this.activeProject.projectId,
this.activeWorkspace,
)) as boolean);
} 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.isCheckingIfWorkspaceIsOutdated = false;
}
}
handleChangeDetectionRefreshIssue(error: Error): void {
if (
!this.currentProject ||
!this.currentWorkspace ||
(error instanceof NetworkClientError &&
error.response.status === HttpStatus.NOT_FOUND)
) {
this.editorStore.setBlockingAlert({
message: 'Current project or workspace no longer exists',
prompt: 'Please refresh the application',
});
} else {
this.editorStore.setBlockingAlert({ message: error.message });
}
}
*buildWorkspaceLatestRevisionEntityHashesIndex(): GeneratorFn<void> {
try {
let entities: Entity[] = [];
if (!this.editorStore.isInConflictResolutionMode) {
// fetch latest revision
// NOTE: this check is already covered in conflict resolution mode so we don't need to do it here
const latestRevision = Revision.serialization.fromJson(
(yield this.editorStore.sdlcServerClient.getRevision(
this.activeProject.projectId,
this.activeWorkspace,
RevisionAlias.CURRENT,
)) as PlainObject<Revision>,
);
// make sure there is no good recovery from this, at this point all users work risk conflict
assertTrue(
latestRevision.id === this.activeRevision.id,
`Can't run local change detection: current workspace revision is not the latest. Please backup your work and refresh the application`,
);
entities =
(yield this.editorStore.sdlcServerClient.getEntitiesByRevision(
this.activeProject.projectId,
this.activeWorkspace,
this.activeRevision.id,
)) as Entity[];
} else {
entities =
(yield this.editorStore.sdlcServerClient.getEntitiesByRevision(
this.activeProject.projectId,
this.activeWorkspace,
RevisionAlias.CURRENT,
)) as Entity[];
}
this.editorStore.changeDetectionState.workspaceLocalLatestRevisionState.setEntities(
entities,
);
yield flowResult(
this.editorStore.changeDetectionState.workspaceLocalLatestRevisionState.buildEntityHashesIndex(
entities,
LogEvent.create(
CHANGE_DETECTION_EVENT.CHANGE_DETECTION_LOCAL_HASHES_INDEX_BUILT,
),
),
);
this.editorStore.refreshCurrentEntityDiffEditorState();
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notifyError(error);
}
}
*buildWorkspaceBaseRevisionEntityHashesIndex(): GeneratorFn<void> {
try {
const workspaceBaseEntities =
(yield this.editorStore.sdlcServerClient.getEntitiesByRevision(
this.activeProject.projectId,
this.activeWorkspace,
RevisionAlias.BASE,
)) as Entity[];
this.editorStore.changeDetectionState.workspaceBaseRevisionState.setEntities(
workspaceBaseEntities,
);
yield flowResult(
this.editorStore.changeDetectionState.workspaceBaseRevisionState.buildEntityHashesIndex(
workspaceBaseEntities,
LogEvent.create(
CHANGE_DETECTION_EVENT.CHANGE_DETECTION_WORKSPACE_HASHES_INDEX_BUILT,
),
),
);
this.editorStore.refreshCurrentEntityDiffEditorState();
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notifyError(error);
}
}
*buildProjectLatestRevisionEntityHashesIndex(): GeneratorFn<void> {
try {
const projectLatestEntities =
(yield this.editorStore.sdlcServerClient.getEntities(
this.activeProject.projectId,
undefined,
)) as Entity[];
this.editorStore.changeDetectionState.projectLatestRevisionState.setEntities(
projectLatestEntities,
);
yield flowResult(
this.editorStore.changeDetectionState.projectLatestRevisionState.buildEntityHashesIndex(
projectLatestEntities,
LogEvent.create(
CHANGE_DETECTION_EVENT.CHANGE_DETECTION_PROJECT_LATEST_HASHES_INDEX_BUILT,
),
),
);
this.editorStore.refreshCurrentEntityDiffEditorState();
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notifyError(error);
}
}
*fetchWorkspaceWorkflows(): GeneratorFn<void> {
try {
this.workspaceWorkflows = (
(yield this.editorStore.sdlcServerClient.getWorkflowsByRevision(
this.activeProject.projectId,
this.activeWorkspace,
RevisionAlias.CURRENT,
)) as PlainObject<Workflow>[]
).map((v) => Workflow.serialization.fromJson(v));
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notifyError(error);
}
}
}