@finos/legend-application-studio
Version:
Legend Studio application core
415 lines (384 loc) • 14.6 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 type { EditorStore } from '../../EditorStore.js';
import { EditorState } from '../EditorState.js';
import {
action,
computed,
flow,
observable,
makeObservable,
flowResult,
} from 'mobx';
import {
type GeneratorFn,
type PlainObject,
LogEvent,
assertErrorThrown,
guaranteeNonNullable,
hashArray,
ActionState,
prettyCONSTName,
} from '@finos/legend-shared';
import type { EditorSDLCState } from '../../EditorSDLCState.js';
import {
type ProjectConfiguration,
ProjectStructureVersion,
UpdateProjectConfigurationCommand,
UpdatePlatformConfigurationsCommand,
ProjectType,
} from '@finos/legend-server-sdlc';
import { LEGEND_STUDIO_APP_EVENT } from '../../../../__lib__/LegendStudioEvent.js';
import { SNAPSHOT_ALIAS, StoreProjectData } from '@finos/legend-server-depot';
import { ProjectDependencyEditorState } from './ProjectDependencyEditorState.js';
export enum CONFIGURATION_EDITOR_TAB {
PROJECT_STRUCTURE = 'PROJECT_STRUCTURE',
PROJECT_DEPENDENCIES = 'PROJECT_DEPENDENCIES',
PLATFORM_CONFIGURATIONS = 'PLATFORM_CONFIGURATIONS',
ADVANCED = 'ADVANCED',
}
export const projectTypeTabFilter = (
mode: ProjectType,
tab: CONFIGURATION_EDITOR_TAB,
): boolean => {
if (
mode === ProjectType.EMBEDDED &&
tab === CONFIGURATION_EDITOR_TAB.PLATFORM_CONFIGURATIONS
) {
return false;
}
return true;
};
export class ProjectConfigurationEditorState extends EditorState {
readonly sdlcState: EditorSDLCState;
readonly updatingConfigurationState = ActionState.create();
readonly fetchingProjectVersionsState = ActionState.create();
projectDependencyEditorState: ProjectDependencyEditorState;
originalProjectConfiguration?: ProjectConfiguration | undefined; // TODO: we might want to remove this when we do change detection for project configuration
projectConfiguration?: ProjectConfiguration | undefined;
selectedTab: CONFIGURATION_EDITOR_TAB;
isReadOnly = false;
projects = new Map<string, StoreProjectData>();
versions = new Map<string, string[]>();
latestProjectStructureVersion: ProjectStructureVersion | undefined;
manualOverwrite = false;
associatedProjectsAndVersionsFetched = false;
constructor(editorStore: EditorStore, sdlcState: EditorSDLCState) {
super(editorStore);
makeObservable(this, {
originalProjectConfiguration: observable,
updatingConfigurationState: observable,
projectConfiguration: observable,
selectedTab: observable,
isReadOnly: observable,
manualOverwrite: observable,
projects: observable,
versions: observable,
associatedProjectsAndVersionsFetched: observable,
fetchingProjectVersionsState: observable,
latestProjectStructureVersion: observable,
projectDependencyEditorState: observable,
originalConfig: computed,
isGroupIdChanged: computed,
isArtifactIdChanged: computed,
setOriginalProjectConfiguration: action,
setProjectConfiguration: action,
setSelectedTab: action,
setManualOverwrite: action,
fectchAssociatedProjectsAndVersions: flow,
updateProjectConfiguration: flow,
updateToLatestStructure: flow,
updateConfigs: flow,
fetchLatestProjectStructureVersion: flow,
changeProjectType: flow,
});
this.projectDependencyEditorState = new ProjectDependencyEditorState(
this,
this.editorStore,
);
this.selectedTab = CONFIGURATION_EDITOR_TAB.PROJECT_STRUCTURE;
this.isReadOnly = editorStore.isInViewerMode;
this.sdlcState = sdlcState;
}
setOriginalProjectConfiguration(
projectConfiguration: ProjectConfiguration,
): void {
this.originalProjectConfiguration = projectConfiguration;
}
setProjectConfiguration(projectConfiguration: ProjectConfiguration): void {
this.projectConfiguration = projectConfiguration;
}
setSelectedTab(tab: CONFIGURATION_EDITOR_TAB): void {
this.selectedTab = tab;
}
setManualOverwrite(value: boolean): void {
this.manualOverwrite = value;
}
get label(): string {
return 'config';
}
override match(tab: EditorState): boolean {
return tab instanceof ProjectConfigurationEditorState;
}
get currentProjectConfiguration(): ProjectConfiguration {
return guaranteeNonNullable(
this.projectConfiguration,
'Project configuration must exist',
);
}
get originalConfig(): ProjectConfiguration {
return guaranteeNonNullable(
this.originalProjectConfiguration,
'Original project configuration is not set',
);
}
get isGroupIdChanged(): boolean {
return (
this.currentProjectConfiguration.groupId !==
this.originalProjectConfiguration?.groupId
);
}
get isArtifactIdChanged(): boolean {
return (
this.currentProjectConfiguration.artifactId !==
this.originalProjectConfiguration?.artifactId
);
}
get containsSnapshotDependencies(): boolean {
return Boolean(
this.originalProjectConfiguration?.projectDependencies.some(
(dependency) => dependency.versionId.endsWith(SNAPSHOT_ALIAS),
),
);
}
get isInEmbeddedMode(): boolean {
return (
this.originalProjectConfiguration?.projectType === ProjectType.EMBEDDED
);
}
*fectchAssociatedProjectsAndVersions(): GeneratorFn<void> {
this.fetchingProjectVersionsState.inProgress();
try {
(
(yield this.editorStore.depotServerClient.getProjects()) as PlainObject<StoreProjectData>[]
)
.map((v) => StoreProjectData.serialization.fromJson(v))
.forEach((project) => this.projects.set(project.coordinates, project));
// fetch the versions for the dependency projects
for (const dep of this.projectConfiguration?.projectDependencies ?? []) {
const project = this.projects.get(dep.projectId);
if (project) {
const _versions =
(yield this.editorStore.depotServerClient.getVersions(
guaranteeNonNullable(dep.groupId),
guaranteeNonNullable(dep.artifactId),
true,
)) as string[];
this.versions.set(project.coordinates, _versions);
}
}
this.associatedProjectsAndVersionsFetched = true;
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.logService.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notificationService.notifyError(
`Can't get project dependencies data. Error:\n${error.message}`,
);
} finally {
this.fetchingProjectVersionsState.complete();
}
}
*updateProjectConfiguration(
updateConfigurationCommand: UpdateProjectConfigurationCommand,
): GeneratorFn<void> {
try {
this.updatingConfigurationState.inProgress();
yield this.editorStore.sdlcServerClient.updateConfiguration(
this.editorStore.sdlcState.activeProject.projectId,
this.editorStore.sdlcState.activeWorkspace,
UpdateProjectConfigurationCommand.serialization.toJson(
updateConfigurationCommand,
),
);
this.editorStore.reset();
// reset editor
yield flowResult(
this.editorStore.sdlcState.fetchCurrentWorkspace(
this.editorStore.sdlcState.activeProject.projectId,
this.editorStore.sdlcState.activePatch?.patchReleaseVersionId.id,
this.editorStore.sdlcState.activeWorkspace.workspaceId,
this.editorStore.sdlcState.activeWorkspace.workspaceType,
),
);
yield flowResult(
this.sdlcState.fetchCurrentRevision(
this.editorStore.sdlcState.activeProject.projectId,
this.editorStore.sdlcState.activeWorkspace,
),
);
yield flowResult(this.editorStore.initMode());
this.editorStore.tabManagerState.openTab(
this.editorStore.projectConfigurationEditorState,
);
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.logService.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notificationService.notifyError(error);
} finally {
this.updatingConfigurationState.complete();
}
}
*updateToLatestStructure(): GeneratorFn<void> {
if (this.latestProjectStructureVersion) {
try {
let latestStructure = this.latestProjectStructureVersion;
if (this.isInEmbeddedMode) {
const projectStructureVersion = new ProjectStructureVersion();
projectStructureVersion.version =
this.latestProjectStructureVersion.version;
// extension version does not exists in embedded mode
projectStructureVersion.extensionVersion = undefined;
latestStructure = projectStructureVersion;
}
const updateCommand = new UpdateProjectConfigurationCommand(
this.currentProjectConfiguration.groupId,
this.currentProjectConfiguration.artifactId,
latestStructure,
`update project configuration from ${this.editorStore.applicationStore.config.appName}: update to latest project structure`,
);
yield flowResult(this.updateProjectConfiguration(updateCommand));
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.logService.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notificationService.notifyError(
error,
);
}
}
}
*changeProjectType(): GeneratorFn<void> {
try {
const newProjectType = this.isInEmbeddedMode
? ProjectType.MANAGED
: ProjectType.EMBEDDED;
const updateCommand = new UpdateProjectConfigurationCommand(
this.currentProjectConfiguration.groupId,
this.currentProjectConfiguration.artifactId,
undefined,
`update project configuration from ${
this.editorStore.applicationStore.config.appName
}: changed project type to ${prettyCONSTName(newProjectType)}`,
);
updateCommand.projectType = newProjectType;
yield flowResult(this.updateProjectConfiguration(updateCommand));
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.logService.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notificationService.notifyError(error);
}
}
// TODO: we will probably need to remove this in the future when we have a better strategy for change detection and persistence of project config
// See https://github.com/finos/legend-studio/issues/952
*updateConfigs(): GeneratorFn<void> {
this.updatingConfigurationState.inProgress();
this.editorStore.applicationStore.alertService.setBlockingAlert({
message: `Updating project configuration...`,
prompt: `Please do not reload the application`,
showLoading: true,
});
try {
const updateProjectConfigurationCommand =
new UpdateProjectConfigurationCommand(
this.currentProjectConfiguration.groupId,
this.currentProjectConfiguration.artifactId,
this.currentProjectConfiguration.projectStructureVersion,
`update project configuration from ${this.editorStore.applicationStore.config.appName}`,
);
if (
hashArray(this.originalConfig.platformConfigurations ?? []) !==
hashArray(this.currentProjectConfiguration.platformConfigurations ?? [])
) {
updateProjectConfigurationCommand.platformConfigurations =
new UpdatePlatformConfigurationsCommand(
this.currentProjectConfiguration.platformConfigurations,
);
}
if (
this.originalConfig.runDependencyTests !==
this.currentProjectConfiguration.runDependencyTests
) {
updateProjectConfigurationCommand.runDependencyTests =
this.currentProjectConfiguration.runDependencyTests;
}
updateProjectConfigurationCommand.projectDependenciesToAdd =
this.currentProjectConfiguration.projectDependencies.filter(
(dep) =>
!this.originalConfig.projectDependencies.find(
(originalProjDep) => originalProjDep.hashCode === dep.hashCode,
),
);
updateProjectConfigurationCommand.projectDependenciesToRemove =
this.originalConfig.projectDependencies.filter(
(originalProjDep) =>
!this.currentProjectConfiguration.projectDependencies.find(
(dep) => dep.hashCode === originalProjDep.hashCode,
),
);
yield flowResult(
this.updateProjectConfiguration(updateProjectConfigurationCommand),
);
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.logService.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notificationService.notifyError(error);
} finally {
this.updatingConfigurationState.complete();
this.editorStore.applicationStore.alertService.setBlockingAlert(
undefined,
);
}
}
*fetchLatestProjectStructureVersion(): GeneratorFn<void> {
try {
this.latestProjectStructureVersion =
ProjectStructureVersion.serialization.fromJson(
(yield this.editorStore.sdlcServerClient.getLatestProjectStructureVersion()) as PlainObject<ProjectStructureVersion>,
);
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.logService.error(
LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE),
error,
);
this.editorStore.applicationStore.notificationService.notifyError(error);
}
}
}