@finos/legend-extension-dsl-data-space-studio
Version:
Legend extension for Data Space DSL - Studio
402 lines • 21.3 kB
JavaScript
/**
* 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 {} from '@finos/legend-graph';
import { StoreProjectData, ProjectDependencyCoordinates, ProjectVersionEntities, MASTER_SNAPSHOT_ALIAS, } from '@finos/legend-server-depot';
import { ActionState, assertErrorThrown, guaranteeNonNullable, LogEvent, } from '@finos/legend-shared';
import {} from '@finos/legend-storage';
import { fetchProjectConfigurationStatus, generateEditorRoute, LEGEND_STUDIO_APP_EVENT, EditorStore, generateReviewRoute, } from '@finos/legend-application-studio';
import { DEFAULT_TAB_SIZE, ActionAlertType, ActionAlertActionType, } from '@finos/legend-application';
import { makeObservable, observable, computed, action, flow, flowResult, } from 'mobx';
import { Project, WorkspaceType, Workspace, ProjectConfiguration, EntityChangeType, } from '@finos/legend-server-sdlc';
import { DATA_SPACE_ELEMENT_CLASSIFIER_PATH, DSL_DataSpace_getGraphManagerExtension, } from '@finos/legend-extension-dsl-data-space/graph';
import { generateDataSpaceTemplateQueryPromotionRoute } from '@finos/legend-extension-dsl-data-space/application';
const projectDependencyToProjectCoordinates = (projectDependency) => new ProjectDependencyCoordinates(guaranteeNonNullable(projectDependency.groupId), guaranteeNonNullable(projectDependency.artifactId), projectDependency.versionId);
const DEFAULT_WORKSPACE_NAME_PREFIX = 'promote-as-template-query';
export class DataSpaceTemplateQueryPromotionReviewerStore {
applicationStore;
sdlcServerClient;
depotServerClient;
initState = ActionState.create();
promoteState = ActionState.create();
loadQueryState = ActionState.create();
loadWorkspacesState = ActionState.create();
editorStore;
graphManagerExtension;
currentQuery;
currentQueryInfo;
currentQueryProject;
currentProject;
currentProjectConfiguration;
currentProjectConfigurationStatus;
currentProjectEntities = [];
dependencyEntities = [];
groupWorkspaces = [];
workspaceName = '';
dataSpacePath;
dataSpaceEntity;
templateQueryId = 'template_id';
templateQueryTitle = 'template_title';
templateQueryDescription = '';
constructor(applicationStore, sdlcServerClient, depotServerClient) {
makeObservable(this, {
editorStore: observable,
graphManagerExtension: observable,
currentQuery: observable,
currentQueryInfo: observable,
currentQueryProject: observable,
currentProject: observable,
currentProjectConfiguration: observable,
currentProjectConfigurationStatus: observable,
currentProjectEntities: observable,
dataSpaceEntity: observable,
groupWorkspaces: observable,
workspaceName: observable,
templateQueryId: observable,
templateQueryTitle: observable,
templateQueryDescription: observable,
isWorkspaceNameValid: computed,
isTemplateQueryIdValid: computed,
setWorkspaceName: action,
setTemplateQueryId: action,
setTemplateQueryTitle: action,
setTemplateQueryDescription: action,
initialize: flow,
loadQuery: flow,
loadProject: flow,
promoteAsTemplateQuery: flow,
});
this.applicationStore = applicationStore;
this.sdlcServerClient = sdlcServerClient;
this.depotServerClient = depotServerClient;
this.editorStore = new EditorStore(applicationStore, sdlcServerClient, depotServerClient);
this.graphManagerExtension = DSL_DataSpace_getGraphManagerExtension(this.editorStore.graphManagerState.graphManager);
}
setWorkspaceName(val) {
this.workspaceName = val;
}
setTemplateQueryId(val) {
this.templateQueryId = val;
}
setTemplateQueryTitle(val) {
this.templateQueryTitle = val;
}
setTemplateQueryDescription(val) {
this.templateQueryDescription = val;
}
get isWorkspaceNameValid() {
return !this.groupWorkspaces.some((ws) => ws.workspaceId === this.workspaceName);
}
get isTemplateQueryIdValid() {
if (this.dataSpaceEntity) {
return this.graphManagerExtension.IsTemplateQueryIdValid(this.dataSpaceEntity, this.templateQueryId);
}
return false;
}
*initialize(queryId, dataSpacePath) {
if (!this.initState.isInInitialState) {
return;
}
try {
this.initState.inProgress();
yield this.graphManagerExtension.graphManager.initialize({
env: this.applicationStore.config.env,
tabSize: DEFAULT_TAB_SIZE,
clientConfig: {
baseUrl: this.applicationStore.config.engineServerUrl,
queryBaseUrl: this.applicationStore.config.engineQueryServerUrl,
enableCompression: true,
},
}, {
tracerService: this.applicationStore.tracerService,
});
this.dataSpacePath = dataSpacePath;
if (queryId) {
let query;
try {
query = (yield this.graphManagerExtension.graphManager.getLightQuery(queryId));
}
catch {
query = undefined;
}
if (query) {
yield flowResult(this.loadQuery(query));
}
else {
this.applicationStore.notificationService.notifyError(`Unable to find query with ID: ${queryId}`);
}
}
if (this.currentQuery) {
this.currentQueryProject = StoreProjectData.serialization.fromJson((yield this.depotServerClient.getProject(this.currentQuery.groupId, this.currentQuery.artifactId)));
const projectData = (yield Promise.all([
this.depotServerClient.getVersionEntities(this.currentQuery.groupId, this.currentQuery.artifactId, MASTER_SNAPSHOT_ALIAS),
this.sdlcServerClient.getConfiguration(this.currentQueryProject.projectId, undefined),
]));
const [currentProjectEntities, currentProjectConfiguration] = [
projectData[0],
ProjectConfiguration.serialization.fromJson(projectData[1]),
];
this.currentProjectConfiguration = currentProjectConfiguration;
const dependencyEntities = (yield this.depotServerClient.collectDependencyEntities([
...currentProjectConfiguration.projectDependencies,
]
.map(projectDependencyToProjectCoordinates)
.map((p) => ProjectDependencyCoordinates.serialization.toJson(p)), true, true))
.map((p) => ProjectVersionEntities.serialization.fromJson(p))
.flatMap((info) => info.entities);
this.dependencyEntities = dependencyEntities;
this.currentProjectEntities = currentProjectEntities;
this.dataSpaceEntity = guaranteeNonNullable(currentProjectEntities.filter((entity) => entity.path === dataSpacePath &&
entity.classifierPath === DATA_SPACE_ELEMENT_CLASSIFIER_PATH)[0], `Can't find data product entity with path ${this.dataSpaceEntity}`);
this.initState.pass();
}
}
catch (error) {
assertErrorThrown(error);
this.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.GENERIC_FAILURE), error);
this.applicationStore.alertService.setBlockingAlert({
message: `Can't initialize template query promotion reviewer store`,
});
this.initState.fail();
}
}
*loadQuery(query) {
this.currentQuery = query;
this.templateQueryTitle = query.name;
try {
this.loadQueryState.inProgress();
this.currentQueryInfo =
(yield this.graphManagerExtension.graphManager.getQueryInfo(query.id));
this.currentQueryProject = StoreProjectData.serialization.fromJson((yield this.depotServerClient.getProject(this.currentQuery.groupId, this.currentQuery.artifactId)));
const updatedQueryName = query.name.replace(/[^a-zA-Z0-9]/g, '');
this.setWorkspaceName(`${DEFAULT_WORKSPACE_NAME_PREFIX}-${updatedQueryName}`);
this.applicationStore.navigationService.navigator.updateCurrentLocation(generateDataSpaceTemplateQueryPromotionRoute(this.currentQuery.groupId, this.currentQuery.artifactId, this.currentQuery.versionId, this.dataSpacePath, query.id));
const currentProject = Project.serialization.fromJson((yield this.sdlcServerClient.getProject(this.currentQueryProject.projectId)));
yield flowResult(this.loadProject(currentProject));
}
catch (error) {
assertErrorThrown(error);
this.applicationStore.notificationService.notifyError(error);
}
finally {
this.loadQueryState.complete();
}
}
*loadProject(project) {
this.currentProject = project;
this.currentProjectConfigurationStatus = undefined;
this.loadWorkspacesState.inProgress();
try {
this.currentProjectConfigurationStatus =
(yield fetchProjectConfigurationStatus(project.projectId, undefined, this.applicationStore, this.sdlcServerClient));
this.groupWorkspaces = (yield this.sdlcServerClient.getGroupWorkspaces(project.projectId))
.map((v) => Workspace.serialization.fromJson(v))
.filter((workspace) => workspace.workspaceType === WorkspaceType.GROUP);
this.loadWorkspacesState.pass();
}
catch (error) {
assertErrorThrown(error);
this.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.SDLC_MANAGER_FAILURE), error);
this.applicationStore.notificationService.notifyError(error);
this.loadWorkspacesState.fail();
}
}
*promoteAsTemplateQuery() {
const query = this.currentQuery;
const project = this.currentProject;
if (this.promoteState.isInProgress ||
!query ||
!this.currentQueryInfo ||
!this.currentProjectConfiguration ||
!project ||
!this.workspaceName ||
!this.templateQueryTitle ||
!this.dataSpaceEntity ||
!this.isWorkspaceNameValid ||
!this.isTemplateQueryIdValid) {
return;
}
try {
this.promoteState.inProgress();
// 1. prepare project entities
this.applicationStore.alertService.setBlockingAlert({
message: `Fetching and updating project...`,
prompt: 'Please do not close the application',
showLoading: true,
});
// update datasapce entity
const updatedDataSpaceEntity = (yield this.graphManagerExtension.addNewExecutableToDataSpaceEntity(this.dataSpaceEntity, this.currentQueryInfo, {
id: this.templateQueryId,
title: this.templateQueryTitle,
description: this.templateQueryDescription,
}));
guaranteeNonNullable(this.currentProjectEntities.filter((entity) => entity.path === this.dataSpacePath &&
entity.classifierPath === DATA_SPACE_ELEMENT_CLASSIFIER_PATH)[0]).content = updatedDataSpaceEntity.content;
// 2. check if the graph compiles properly
this.applicationStore.alertService.setBlockingAlert({
message: `Checking workspace compilation status...`,
prompt: 'Please do not close the application',
showLoading: true,
});
let compilationFailed = false;
try {
yield this.graphManagerExtension.graphManager.compileEntities([
...this.dependencyEntities,
...this.currentProjectEntities,
]);
}
catch {
compilationFailed = true;
}
// 3. proceed to setup the workspace
const setupWorkspace = async () => {
let workspace;
try {
this.applicationStore.alertService.setBlockingAlert({
message: `Creating workspace...`,
prompt: 'Please do not close the application',
showLoading: true,
});
// i. create workspace
workspace = Workspace.serialization.fromJson(await this.sdlcServerClient.createWorkspace(project.projectId, undefined, this.workspaceName, WorkspaceType.GROUP));
// ii. update data product
this.applicationStore.alertService.setBlockingAlert({
message: `Generating code commit...`,
prompt: 'Please do not close the application',
showLoading: true,
});
await this.sdlcServerClient.performEntityChanges(project.projectId, workspace, {
message: 'promote-as-template-query: promote query as a template query to data product',
entityChanges: [
{
classifierPath: updatedDataSpaceEntity.classifierPath,
entityPath: updatedDataSpaceEntity.path,
content: updatedDataSpaceEntity.content,
type: EntityChangeType.MODIFY,
},
],
});
// iii create review
this.applicationStore.alertService.setBlockingAlert({
message: `Generating code review...`,
prompt: 'Please do not close the application',
showLoading: true,
});
await flowResult(this.editorStore.initialize(project.projectId, undefined, workspace.workspaceId, workspace.workspaceType, undefined));
const workspaceReviewState = this.editorStore.workspaceReviewState;
const workspaceContainsSnapshotDependencies = this.editorStore.projectConfigurationEditorState
.containsSnapshotDependencies;
const isCreateReviewDisabled = Boolean(workspaceReviewState.workspaceReview) ||
workspaceContainsSnapshotDependencies ||
!workspaceReviewState.canCreateReview ||
workspaceReviewState.sdlcState.isActiveProjectSandbox;
workspaceReviewState.reviewTitle =
'code review - promote query as a template query to data product';
if (!isCreateReviewDisabled) {
await flowResult(workspaceReviewState.createWorkspaceReview(workspaceReviewState.reviewTitle));
}
else {
this.applicationStore.notificationService.notifyError(`Can't create code review`);
}
// iv. complete, redirect user to the service query editor screen
this.applicationStore.alertService.setBlockingAlert(undefined);
this.promoteState.pass();
this.applicationStore.alertService.setActionAlertInfo({
message: `Successfully promoted query into data product '${this.dataSpacePath}'. Now your template query can be found in workspace '${this.workspaceName}' of project '${project.name}' (${project.projectId})`,
prompt: compilationFailed
? `The workspace might not compile at the moment, please make sure to fix the issue and submit a review to make the data product part of the project to complete template query promotion`
: `Please make sure to get the generated code-review reviewed and approved`,
type: ActionAlertType.STANDARD,
actions: compilationFailed
? [
{
label: 'Open Workspace',
type: ActionAlertActionType.PROCEED,
handler: () => {
this.applicationStore.navigationService.navigator.goToLocation(generateEditorRoute(project.projectId, undefined, this.workspaceName, WorkspaceType.GROUP));
},
default: true,
},
]
: [
{
label: 'Open Code Review',
type: ActionAlertActionType.PROCEED,
handler: () => {
if (workspaceReviewState.workspaceReview) {
this.applicationStore.navigationService.navigator.visitAddress(this.applicationStore.navigationService.navigator.generateAddress(generateReviewRoute(workspaceReviewState.workspaceReview.projectId, workspaceReviewState.workspaceReview.id)));
}
},
default: true,
},
{
label: 'Open Workspace',
type: ActionAlertActionType.PROCEED,
handler: () => {
this.applicationStore.navigationService.navigator.goToLocation(generateEditorRoute(project.projectId, undefined, this.workspaceName, WorkspaceType.GROUP));
},
},
],
});
}
catch (error) {
assertErrorThrown(error);
this.applicationStore.alertService.setBlockingAlert(undefined);
this.applicationStore.notificationService.notifyError(`Can't set up workspace: ${error.message}`);
if (workspace) {
await this.sdlcServerClient.deleteWorkspace(project.projectId, workspace);
}
this.promoteState.fail();
}
};
this.applicationStore.alertService.setBlockingAlert(undefined);
if (compilationFailed) {
this.applicationStore.alertService.setActionAlertInfo({
message: `We have found compilation issues with the workspace. Your query can still be promoted, but you would need to fix compilation issues afterwards`,
prompt: `Do you still want to proceed to promote the query as a template query?`,
type: ActionAlertType.STANDARD,
actions: [
{
label: `Proceed`,
type: ActionAlertActionType.PROCEED_WITH_CAUTION,
handler: () => {
setupWorkspace().catch(this.applicationStore.alertUnhandledError);
},
},
{
label: 'Abort',
type: ActionAlertActionType.PROCEED,
handler: () => {
this.promoteState.fail();
},
default: true,
},
],
});
}
else {
yield setupWorkspace();
}
this.promoteState.pass();
}
catch (error) {
assertErrorThrown(error);
this.applicationStore.alertService.setBlockingAlert(undefined);
this.applicationStore.notificationService.notifyError(error);
this.promoteState.fail();
}
}
}
//# sourceMappingURL=DataSpaceTemplateQueryPromotionReviewerStore.js.map