@finos/legend-application-studio
Version:
Legend Studio application core
302 lines (278 loc) • 11.2 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 {
guaranteeNonNullable,
type GeneratorFn,
assertErrorThrown,
StopWatch,
LogEvent,
assertNonEmptyString,
} from '@finos/legend-shared';
import type { EditorStore } from '../editor/EditorStore.js';
import { flow, flowResult, makeObservable, observable } from 'mobx';
import type { ShowcaseViewerPathParams } from '../../__lib__/LegendStudioNavigation.js';
import { LEGEND_STUDIO_APP_EVENT } from '../../__lib__/LegendStudioEvent.js';
import {
ShowcaseRegistryServerClient,
type Showcase,
} from '@finos/legend-server-showcase';
import {
DependencyGraphBuilderError,
GRAPH_MANAGER_EVENT,
GraphBuilderError,
GraphDataDeserializationError,
createGraphBuilderReport,
} from '@finos/legend-graph';
import { DEFAULT_TAB_SIZE } from '@finos/legend-application';
import { payloadDebugger } from '../editor/panel-group/DevToolPanelState.js';
import { LegendStudioTelemetryHelper } from '../../__lib__/LegendStudioTelemetryHelper.js';
import { GRAPH_EDITOR_MODE } from '../editor/EditorConfig.js';
import type { Entity } from '@finos/legend-storage';
export class ShowcaseViewerStore {
readonly editorStore: EditorStore;
private readonly showcaseServerClient?: ShowcaseRegistryServerClient;
_showcase: Showcase | undefined;
constructor(editorStore: EditorStore) {
this.editorStore = editorStore;
makeObservable(this, {
_showcase: observable,
initialize: flow,
buildGraph: flow,
});
if (this.editorStore.applicationStore.config.showcaseServerUrl) {
this.showcaseServerClient = new ShowcaseRegistryServerClient({
baseUrl: this.editorStore.applicationStore.config.showcaseServerUrl,
});
}
}
private get client(): ShowcaseRegistryServerClient {
return guaranteeNonNullable(
this.showcaseServerClient,
`Showcase registry server client is not configured`,
);
}
get showcase(): Showcase {
return guaranteeNonNullable(this._showcase);
}
setShowcase(val: Showcase): void {
this._showcase = val;
}
*initialize(params: ShowcaseViewerPathParams): GeneratorFn<void> {
if (!this.editorStore.initState.isInInitialState) {
return;
}
const showcasePath = params.showcasePath;
this.editorStore.initState.inProgress();
const onLeave = (hasBuildSucceeded: boolean): void => {
this.editorStore.initState.complete(hasBuildSucceeded);
};
try {
const stopWatch = new StopWatch();
assertNonEmptyString(showcasePath, 'Showcase path expected');
// fetch showcase
this.editorStore.initState.setMessage(`Fetching Showcase Data...`);
this._showcase = (yield this.client.getShowcase(
showcasePath,
)) as Showcase;
LegendStudioTelemetryHelper.logEvent_ShowcaseManagerShowcaseProjectLaunch(
this.editorStore.applicationStore.telemetryService,
{
showcasePath: showcasePath,
},
);
// initialize graph manager
yield this.editorStore.graphManagerState.graphManager.initialize(
{
env: this.editorStore.applicationStore.config.env,
tabSize: DEFAULT_TAB_SIZE,
clientConfig: {
baseUrl: this.editorStore.applicationStore.config.engineServerUrl,
queryBaseUrl:
this.editorStore.applicationStore.config.engineQueryServerUrl,
enableCompression: true,
payloadDebugger,
},
},
{
tracerService: this.editorStore.applicationStore.tracerService,
},
);
// convert code to entities
stopWatch.record();
const grammar = this.showcase.code;
this.editorStore.initState.setMessage(
`Converting showcase code to entities...`,
);
const entities =
(yield this.editorStore.graphManagerState.graphManager.pureCodeToEntities(
grammar,
// we want to keep section index so we read imports correctly
{
TEMPORARY__keepSectionIndex: true,
},
)) as Entity[];
this.editorStore.initState.setMessage(undefined);
stopWatch.record(GRAPH_MANAGER_EVENT.FETCH_GRAPH_ENTITIES__SUCCESS);
const graphBuilderResult = (yield flowResult(
this.buildGraph(entities),
)) as boolean;
if (!graphBuilderResult) {
onLeave(false);
return;
}
this.editorStore.initState.setMessage(`Generating elements...`);
if (
this.editorStore.graphManagerState.graph.ownGenerationSpecifications
.length
) {
yield flowResult(
this.editorStore.graphState.graphGenerationState.globalGenerate(),
);
}
// build explorer tree
this.editorStore.explorerTreeState.buildImmutableModelTrees();
this.editorStore.explorerTreeState.build();
// observe graph: we still capture user changes on forms
yield flowResult(this.editorStore.changeDetectionState.observeGraph());
// complete actions
this.editorStore.initState.setMessage(undefined);
onLeave(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(error);
onLeave(false);
}
}
*buildGraph(entities: Entity[]): GeneratorFn<boolean> {
try {
const stopWatch = new StopWatch();
yield this.editorStore.graphManagerState.initializeSystem();
// reset
this.editorStore.graphManagerState.resetGraph();
// build dependencies
stopWatch.record();
const dependencyManager =
this.editorStore.graphManagerState.graphManager.createDependencyManager();
this.editorStore.graphManagerState.graph.dependencyManager =
dependencyManager;
const dependency_buildReport = createGraphBuilderReport();
yield this.editorStore.graphManagerState.graphManager.buildDependencies(
this.editorStore.graphManagerState.coreModel,
this.editorStore.graphManagerState.systemModel,
dependencyManager,
new Map(),
this.editorStore.graphManagerState.dependenciesBuildState,
{},
dependency_buildReport,
);
// build graph
const graph_buildReport = createGraphBuilderReport();
yield this.editorStore.graphManagerState.graphManager.buildGraph(
this.editorStore.graphManagerState.graph,
entities,
this.editorStore.graphManagerState.graphBuildState,
undefined,
graph_buildReport,
);
// report
stopWatch.record(GRAPH_MANAGER_EVENT.INITIALIZE_GRAPH__SUCCESS);
const graphBuilderReportData = {
timings:
this.editorStore.applicationStore.timeService.finalizeTimingsRecord(
stopWatch,
),
dependencies: dependency_buildReport,
dependenciesCount:
this.editorStore.graphManagerState.graph.dependencyManager
.numberOfDependencies,
graph: graph_buildReport,
};
LegendStudioTelemetryHelper.logEvent_GraphInitializationSucceeded(
this.editorStore.applicationStore.telemetryService,
graphBuilderReportData,
);
this.editorStore.applicationStore.logService.info(
LogEvent.create(GRAPH_MANAGER_EVENT.INITIALIZE_GRAPH__SUCCESS),
graphBuilderReportData,
);
// fetch available editor configurations
yield Promise.all([
this.editorStore.graphState.graphGenerationState.globalFileGenerationState.fetchAvailableFileGenerationDescriptions(),
this.editorStore.graphState.graphGenerationState.externalFormatState.fetchExternalFormatDescriptions(),
this.editorStore.graphState.fetchAvailableFunctionActivatorConfigurations(),
]);
return true;
} catch (error) {
assertErrorThrown(error);
// if graph builder fails, we fall back to text-mode
this.editorStore.applicationStore.logService.error(
LogEvent.create(GRAPH_MANAGER_EVENT.GRAPH_BUILDER_FAILURE),
error,
);
if (error instanceof DependencyGraphBuilderError) {
// no recovery if dependency models cannot be built, this makes assumption that all dependencies models are compiled successfully
// TODO: we might want to handle this more gracefully when we can show people the dependency model element in the future
this.editorStore.applicationStore.notificationService.notifyError(
`Can't initialize dependency models. Error: ${error.message}`,
);
this.editorStore.applicationStore.alertService.setBlockingAlert({
message: `Can't initialize dependencies`,
prompt: 'Please use editor to better invesigate the issue',
});
} else if (error instanceof GraphDataDeserializationError) {
// if something goes wrong with de-serialization, we can't really do anything but to alert
this.editorStore.applicationStore.notificationService.notifyError(
`Can't deserialize graph. Error: ${error.message}`,
);
this.editorStore.applicationStore.alertService.setBlockingAlert({
message: `Can't deserialize graph`,
prompt: 'Please use editor to better invesigate the issue',
});
} else if (error instanceof GraphBuilderError) {
// TODO: we should split this into 2 notifications when we support multiple notifications
this.editorStore.applicationStore.notificationService.notifyError(
`Can't build graph. Redirected to text mode for debugging. Error: ${error.message}`,
);
this.editorStore.applicationStore.logService.error(
LogEvent.create(GRAPH_MANAGER_EVENT.GRAPH_BUILDER_FAILURE),
error,
);
yield flowResult(
this.editorStore.switchModes(GRAPH_EDITOR_MODE.GRAMMAR_TEXT, {
isGraphBuildFailure: true,
}),
);
if (this.editorStore.graphEditorMode.mode === GRAPH_EDITOR_MODE.FORM) {
// nothing we can do here so we will just block the user
this.editorStore.applicationStore.alertService.setBlockingAlert({
message: `Can't compose Pure code from graph models`,
prompt: 'Please use editor to better invesigate the issue',
});
return false;
}
} else {
this.editorStore.applicationStore.notificationService.notifyError(
error,
);
}
return false;
}
}
}