@finos/legend-studio
Version:
375 lines (344 loc) • 11.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 { observable, action, makeAutoObservable, flowResult } from 'mobx';
import { LEGEND_STUDIO_APP_EVENT } from '../LegendStudioAppEvent.js';
import {
type GeneratorFn,
type PlainObject,
assertErrorThrown,
LogEvent,
ActionState,
} from '@finos/legend-shared';
import { generateSetupRoute } from '../LegendStudioRouter.js';
import {
type SDLCServerClient,
WorkspaceType,
ImportReport,
Project,
Review,
Workspace,
WorkspaceAccessType,
} from '@finos/legend-server-sdlc';
import type { LegendStudioApplicationStore } from '../LegendStudioBaseStore.js';
interface ImportProjectSuccessReport {
projectId: string;
projectName: string;
reviewUrl: string;
}
export interface ProjectOption {
label: string;
value: string;
}
const buildProjectOption = (project: Project): ProjectOption => ({
label: project.name,
value: project.projectId,
});
export interface WorkspaceOption {
label: string;
value: Workspace;
__isNew__?: boolean | undefined;
}
const buildWorkspaceOption = (workspace: Workspace): WorkspaceOption => ({
label: workspace.workspaceId,
value: workspace,
});
export interface WorkspaceIdentifier {
workspaceId: string;
workspaceType: WorkspaceType;
}
export class WorkspaceSetupStore {
applicationStore: LegendStudioApplicationStore;
sdlcServerClient: SDLCServerClient;
currentProjectId?: string | undefined;
currentWorkspaceIdentifier?: WorkspaceIdentifier | undefined;
projects?: Map<string, Project> | undefined;
workspacesByProject = new Map<string, Map<string, Workspace>>();
loadWorkspacesState = ActionState.create();
createWorkspaceState = ActionState.create();
createOrImportProjectState = ActionState.create();
loadProjectsState = ActionState.create();
showCreateProjectModal = false;
showCreateWorkspaceModal = false;
importProjectSuccessReport?: ImportProjectSuccessReport | undefined;
constructor(
applicationStore: LegendStudioApplicationStore,
sdlcServerClient: SDLCServerClient,
) {
makeAutoObservable(this, {
applicationStore: false,
sdlcServerClient: false,
setShowCreateProjectModal: action,
setShowCreateWorkspaceModal: action,
setCurrentProjectId: action,
setCurrentWorkspaceIdentifier: action,
setImportProjectSuccessReport: action,
});
this.applicationStore = applicationStore;
this.sdlcServerClient = sdlcServerClient;
}
get currentProject(): Project | undefined {
return this.projects && this.currentProjectId
? this.projects.get(this.currentProjectId)
: undefined;
}
get currentProjectWorkspaces(): Map<string, Workspace> | undefined {
return this.currentProjectId
? this.workspacesByProject.get(this.currentProjectId)
: undefined;
}
get currentWorkspace(): Workspace | undefined {
return this.currentProjectWorkspaces && this.currentWorkspaceCompositeId
? this.currentProjectWorkspaces.get(this.currentWorkspaceCompositeId)
: undefined;
}
get currentWorkspaceCompositeId(): string | undefined {
return this.currentWorkspaceIdentifier
? this.buildWorkspaceCompositeId(this.currentWorkspaceIdentifier)
: undefined;
}
init(
workspaceId: string | undefined,
groupWorkspaceId: string | undefined,
): void {
if (workspaceId) {
this.setCurrentWorkspaceIdentifier({
workspaceId,
workspaceType: WorkspaceType.USER,
});
} else if (groupWorkspaceId) {
this.setCurrentWorkspaceIdentifier({
workspaceId: groupWorkspaceId,
workspaceType: WorkspaceType.GROUP,
});
} else {
this.setCurrentWorkspaceIdentifier(undefined);
}
}
setShowCreateProjectModal(val: boolean): void {
this.showCreateProjectModal = val;
}
setShowCreateWorkspaceModal(val: boolean): void {
this.showCreateWorkspaceModal = val;
}
setCurrentProjectId(id: string | undefined): void {
this.currentProjectId = id;
}
setCurrentWorkspaceIdentifier(val: WorkspaceIdentifier | undefined): void {
this.currentWorkspaceIdentifier = val;
}
setImportProjectSuccessReport(
importProjectSuccessReport: ImportProjectSuccessReport | undefined,
): void {
this.importProjectSuccessReport = importProjectSuccessReport;
}
*fetchProjects(): GeneratorFn<void> {
this.loadProjectsState.inProgress();
try {
const projects = (
(yield this.sdlcServerClient.getProjects(
undefined,
undefined,
undefined,
undefined,
)) as PlainObject<Project>[]
).map((v) => Project.serialization.fromJson(v));
const projectIndex = observable<string, Project>(new Map());
projects.forEach((project) =>
projectIndex.set(project.projectId, project),
);
this.projects = projectIndex;
this.loadProjectsState.pass();
} catch (error) {
assertErrorThrown(error);
this.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.WORKSPACE_SETUP_FAILURE),
error,
);
this.applicationStore.notifyError(error);
this.loadProjectsState.fail();
}
}
*createProject(
name: string,
description: string,
groupId: string,
artifactId: string,
tags: string[] = [],
): GeneratorFn<void> {
this.createOrImportProjectState.inProgress();
try {
const createdProject = Project.serialization.fromJson(
(yield this.sdlcServerClient.createProject({
name,
description,
groupId,
artifactId,
tags,
})) as PlainObject<Project>,
);
this.applicationStore.notifySuccess(
`Project '${name}' is succesfully created`,
);
yield flowResult(this.fetchProjects());
this.projects?.set(createdProject.projectId, createdProject);
this.applicationStore.navigator.goTo(
generateSetupRoute(createdProject.projectId),
);
this.setShowCreateProjectModal(false);
} catch (error) {
assertErrorThrown(error);
this.applicationStore.notifyError(error);
} finally {
this.createOrImportProjectState.reset();
}
}
*importProject(
id: string,
groupId: string,
artifactId: string,
): GeneratorFn<void> {
this.createOrImportProjectState.inProgress();
try {
const report = ImportReport.serialization.fromJson(
(yield this.sdlcServerClient.importProject({
id,
groupId,
artifactId,
})) as PlainObject<ImportReport>,
);
const importReview = Review.serialization.fromJson(
(yield this.sdlcServerClient.getReview(
report.project.projectId,
report.reviewId,
)) as PlainObject<Review>,
);
this.setImportProjectSuccessReport({
projectName: report.project.name,
projectId: report.project.projectId,
reviewUrl: importReview.webURL,
});
yield flowResult(this.fetchProjects());
this.projects?.set(report.project.projectId, report.project);
this.setCurrentProjectId(report.project.projectId);
} catch (error) {
assertErrorThrown(error);
this.applicationStore.notifyError(error);
} finally {
this.createOrImportProjectState.reset();
}
}
get projectOptions(): ProjectOption[] {
return this.projects
? Array.from(this.projects.values()).map(buildProjectOption)
: [];
}
get currentProjectWorkspaceOptions(): WorkspaceOption[] {
return this.currentProjectWorkspaces
? Array.from(this.currentProjectWorkspaces.values()).map(
buildWorkspaceOption,
)
: [];
}
*fetchWorkspaces(projectId: string): GeneratorFn<void> {
this.loadWorkspacesState.inProgress();
try {
const workspacesInConflictResolutionIds = (
(yield this.sdlcServerClient.getWorkspacesInConflictResolutionMode(
projectId,
)) as Workspace[]
).map((workspace) => workspace.workspaceId);
const workspaceIndex = observable<string, Workspace>(new Map());
(
(yield this.sdlcServerClient.getWorkspaces(
projectId,
)) as PlainObject<Workspace>[]
)
.map((v) => Workspace.serialization.fromJson(v))
.forEach((workspace) => {
// NOTE we don't handle workspaces that only have conflict resolution but no standard workspace
// since that indicates bad state of the SDLC server
if (
workspacesInConflictResolutionIds.includes(workspace.workspaceId)
) {
workspace.accessType = WorkspaceAccessType.CONFLICT_RESOLUTION;
}
workspaceIndex.set(
this.buildWorkspaceCompositeId(workspace),
workspace,
);
});
this.workspacesByProject.set(projectId, workspaceIndex);
} catch (error) {
assertErrorThrown(error);
// TODO handle error when fetching workspaces for an individual project
this.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.WORKSPACE_SETUP_FAILURE),
error,
);
} finally {
this.loadWorkspacesState.reset();
}
}
buildWorkspaceCompositeId(workspace: WorkspaceIdentifier): string {
return `${workspace.workspaceType}/${workspace.workspaceId}`;
}
*createWorkspace(
projectId: string,
workspaceId: string,
workspaceType: WorkspaceType,
): GeneratorFn<void> {
this.createWorkspaceState.inProgress();
try {
const workspace = Workspace.serialization.fromJson(
(yield this.sdlcServerClient.createWorkspace(
projectId,
workspaceId,
workspaceType,
)) as PlainObject<Workspace>,
);
const existingWorkspaceForProject: Map<string, Workspace> | undefined =
this.workspacesByProject.get(projectId);
if (existingWorkspaceForProject) {
existingWorkspaceForProject.set(
this.buildWorkspaceCompositeId(workspace),
workspace,
);
} else {
const workspaceIndex = observable<string, Workspace>(new Map());
workspaceIndex.set(
this.buildWorkspaceCompositeId(workspace),
workspace,
);
this.workspacesByProject.set(projectId, workspaceIndex);
}
this.applicationStore.notifySuccess(
`Workspace '${workspace.workspaceId}' is succesfully created`,
);
this.setCurrentProjectId(projectId);
this.setCurrentWorkspaceIdentifier(workspace);
this.setShowCreateWorkspaceModal(false);
this.createWorkspaceState.pass();
} catch (error) {
assertErrorThrown(error);
this.applicationStore.log.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.WORKSPACE_SETUP_FAILURE),
error,
);
this.applicationStore.notifyError(error);
this.createWorkspaceState.fail();
}
}
}