@finos/legend-application-studio
Version:
Legend Studio application core
1,120 lines (1,017 loc) • 34.3 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,
observable,
computed,
makeObservable,
flowResult,
flow,
} from 'mobx';
import type { EditorStore } from './EditorStore.js';
import {
type Clazz,
type GeneratorFn,
IllegalStateError,
guaranteeType,
UnsupportedOperationError,
guaranteeNonNullable,
} from '@finos/legend-shared';
import { decorateRuntimeWithNewMapping } from './editor-state/element-editor-state/RuntimeEditorState.js';
import type { DSL_LegendStudioApplicationPlugin_Extension } from '../LegendStudioApplicationPlugin.js';
import {
type GenerationTypeOption,
DEFAULT_GENERATION_SPECIFICATION_NAME,
} from './editor-state/GraphGenerationState.js';
import {
type PackageableElement,
type Store,
ModelStore,
type Connection,
type PureModelConnection,
ELEMENT_PATH_DELIMITER,
Package,
Class,
Association,
Enumeration,
ConcreteFunctionDefinition,
Profile,
Mapping,
FlatData,
Service,
PackageableConnection,
PackageableRuntime,
PureSingleExecution,
EngineRuntime,
JsonModelConnection,
FileGenerationSpecification,
GenerationSpecification,
FlatDataConnection,
Database,
PackageableElementExplicitReference,
RelationalDatabaseConnection,
DatabaseType,
DefaultH2AuthenticationStrategy,
ModelGenerationSpecification,
DataElement,
stub_Database,
Measure,
Multiplicity,
PrimitiveType,
LocalH2DatasourceSpecification,
SnowflakeDatasourceSpecification,
SnowflakePublicAuthenticationStrategy,
StoreConnections,
ConnectionPointer,
IdentifiedConnection,
generateIdentifiedConnectionId,
getMappingCompatibleRuntimes,
RuntimePointer,
GenericTypeExplicitReference,
GenericType,
DataProduct,
LakehouseRuntime,
type Runtime,
AccessPointGroup,
ModelAccessPointGroup,
stub_Mapping,
InternalDataProductType,
} from '@finos/legend-graph';
import type { DSL_Mapping_LegendStudioApplicationPlugin_Extension } from '../extensions/DSL_Mapping_LegendStudioApplicationPlugin_Extension.js';
import {
packageableConnection_setConnectionValue,
runtime_addMapping,
} from '../graph-modifier/DSL_Mapping_GraphModifierHelper.js';
import {
fileGeneration_setScopeElements,
fileGeneration_setType,
generationSpecification_addGenerationElement,
} from '../graph-modifier/DSL_Generation_GraphModifierHelper.js';
import {
service_initNewService,
service_setExecution,
} from '../graph-modifier/DSL_Service_GraphModifierHelper.js';
import type { EmbeddedDataTypeOption } from './editor-state/element-editor-state/data/DataEditorState.js';
import { dataElement_setEmbeddedData } from '../graph-modifier/DSL_Data_GraphModifierHelper.js';
import { PACKAGEABLE_ELEMENT_TYPE } from './utils/ModelClassifierUtils.js';
import {
buildElementOption,
type PackageableElementOption,
} from '@finos/legend-lego/graph-editor';
import { EmbeddedDataType } from './editor-state/ExternalFormatState.js';
import { createEmbeddedData } from './editor-state/element-editor-state/data/EmbeddedDataState.js';
import {
dataProduct_addAccessPointGroup,
dataProduct_setTitle,
dataProduct_setType,
} from '../graph-modifier/DSL_DataProduct_GraphModifierHelper.js';
export const CUSTOM_LABEL = '(custom)';
export type RuntimeOption = {
label: string;
value: PackageableRuntime | undefined;
};
export const resolvePackageAndElementName = (
_package: Package,
isPackageRoot: boolean,
name: string,
): [string, string] => {
const index = name.lastIndexOf(ELEMENT_PATH_DELIMITER);
const elementName =
index === -1 ? name : name.substring(index + 2, name.length);
const additionalPackageName = index === -1 ? '' : name.substring(0, index);
const selectedPackageName = isPackageRoot ? '' : _package.path;
const packagePath =
!selectedPackageName && !additionalPackageName
? ''
: selectedPackageName
? `${selectedPackageName}${
additionalPackageName
? `${ELEMENT_PATH_DELIMITER}${additionalPackageName}`
: ''
}`
: additionalPackageName;
return [packagePath, elementName];
};
export const handlePostCreateAction = async (
element: PackageableElement,
editorStore: EditorStore,
): Promise<void> => {
// post creation handling
if (
element instanceof FileGenerationSpecification ||
element instanceof ModelGenerationSpecification
) {
const generationElement = element;
const generationSpecifications =
editorStore.graphManagerState.graph.ownGenerationSpecifications;
let generationSpec: GenerationSpecification;
if (generationSpecifications.length) {
// TODO? handle case when more than one generation specification
generationSpec = generationSpecifications[0] as GenerationSpecification;
} else {
generationSpec = new GenerationSpecification(
DEFAULT_GENERATION_SPECIFICATION_NAME,
);
await flowResult(
editorStore.graphEditorMode.addElement(
generationSpec,
guaranteeNonNullable(generationElement.package).path,
false,
),
);
}
generationSpecification_addGenerationElement(
generationSpec,
generationElement,
);
}
const extraElementEditorPostCreateActions = editorStore.pluginManager
.getApplicationPlugins()
.flatMap(
(plugin) =>
(
plugin as DSL_LegendStudioApplicationPlugin_Extension
).getExtraElementEditorPostCreateActions?.() ?? [],
);
for (const postCreateAction of extraElementEditorPostCreateActions) {
postCreateAction(editorStore, element);
}
};
export abstract class NewElementDriver<T extends PackageableElement> {
editorStore: EditorStore;
constructor(editorStore: EditorStore) {
this.editorStore = editorStore;
}
abstract get isValid(): boolean;
abstract createElement(name: string): T;
}
export enum NewRuntimeType {
LEGACY = 'LEGACY',
LAKEHOUSE = 'LAKEHOUSE',
}
export class NewPackageableRuntimeDriver extends NewElementDriver<PackageableRuntime> {
mapping?: Mapping | undefined;
type = NewRuntimeType.LEGACY;
constructor(editorStore: EditorStore) {
super(editorStore);
makeObservable(this, {
mapping: observable,
setMapping: action,
isValid: computed,
type: observable,
setType: action,
});
const firstMapping = this.editorStore.graphManagerState.graph.mappings[0];
this.mapping = firstMapping;
if (!firstMapping) {
this.type = NewRuntimeType.LAKEHOUSE;
}
}
setType(val: NewRuntimeType): void {
this.type = val;
}
setMapping(mapping: Mapping): void {
this.mapping = mapping;
}
get isValid(): boolean {
if (this.type === NewRuntimeType.LEGACY) {
return Boolean(this.mapping);
}
return true;
}
createElement(name: string): PackageableRuntime {
const runtime = new PackageableRuntime(name);
if (this.type === NewRuntimeType.LAKEHOUSE) {
const lakehouseRuntime = new LakehouseRuntime();
lakehouseRuntime.environment = '';
lakehouseRuntime.warehouse = '';
lakehouseRuntime.connectionPointer = undefined;
runtime.runtimeValue = lakehouseRuntime;
return runtime;
}
runtime.runtimeValue = new EngineRuntime();
runtime_addMapping(
runtime.runtimeValue,
PackageableElementExplicitReference.create(
guaranteeNonNullable(this.mapping),
),
);
return runtime;
}
}
export abstract class NewConnectionValueDriver<T extends Connection> {
editorStore: EditorStore;
constructor(editorStore: EditorStore) {
this.editorStore = editorStore;
}
abstract get isValid(): boolean;
abstract getConnectionType(): string;
abstract createConnection(store: Store): T;
}
export enum CONNECTION_TYPE {
PURE_MODEL_CONNECTION = 'MODEL_CONNECTION',
FLAT_DATA_CONNECTION = 'FLAT_DATA_CONNECTION',
RELATIONAL_CONNECTION = 'RELATIONAL_CONNECTION',
}
export class NewPureModelConnectionDriver extends NewConnectionValueDriver<PureModelConnection> {
class?: Class | undefined;
constructor(editorStore: EditorStore) {
super(editorStore);
makeObservable(this, {
class: observable,
setClass: action,
isValid: computed,
});
const classes = this.editorStore.graphManagerState.graph.classes;
if (classes.length) {
this.class = classes[0];
}
}
setClass(_class: Class): void {
this.class = _class;
}
get isValid(): boolean {
return Boolean(this.class);
}
getConnectionType(): string {
return CONNECTION_TYPE.PURE_MODEL_CONNECTION;
}
createConnection(store: ModelStore): PureModelConnection {
return new JsonModelConnection(
PackageableElementExplicitReference.create(store),
PackageableElementExplicitReference.create(
guaranteeNonNullable(this.class),
),
);
}
}
export class NewFlatDataConnectionDriver extends NewConnectionValueDriver<FlatDataConnection> {
constructor(editorStore: EditorStore) {
super(editorStore);
makeObservable(this, {
isValid: computed,
});
}
get isValid(): boolean {
return true;
}
getConnectionType(): string {
return CONNECTION_TYPE.FLAT_DATA_CONNECTION;
}
createConnection(store: FlatData): FlatDataConnection {
return new FlatDataConnection(
PackageableElementExplicitReference.create(store),
);
}
}
export const DEFAULT_H2_SQL =
'-- loads sample data for getting started. See https://github.com/pthom/northwind_psql for more info\n call loadNorthwindData()';
export class NewRelationalDatabaseConnectionDriver extends NewConnectionValueDriver<RelationalDatabaseConnection> {
constructor(editorStore: EditorStore) {
super(editorStore);
makeObservable(this, {
isValid: computed,
});
}
get isValid(): boolean {
return true;
}
getConnectionType(): string {
return CONNECTION_TYPE.RELATIONAL_CONNECTION;
}
createConnection(store: Store): RelationalDatabaseConnection {
let selectedStore: Database;
if (store instanceof Database) {
selectedStore = store;
} else {
const dbs = this.editorStore.graphManagerState.usableDatabases;
selectedStore = dbs.length ? (dbs[0] as Database) : stub_Database();
}
const spec = new LocalH2DatasourceSpecification();
spec.testDataSetupSqls = [DEFAULT_H2_SQL];
return new RelationalDatabaseConnection(
PackageableElementExplicitReference.create(selectedStore),
DatabaseType.H2,
spec,
new DefaultH2AuthenticationStrategy(),
);
}
}
export class NewPackageableConnectionDriver extends NewElementDriver<PackageableConnection> {
store: Store;
newConnectionValueDriver: NewConnectionValueDriver<Connection> | undefined;
constructor(editorStore: EditorStore) {
super(editorStore);
makeObservable(this, {
store: observable,
newConnectionValueDriver: observable,
setStore: action,
changeConnectionState: action,
isValid: computed,
});
this.store = ModelStore.INSTANCE;
this.newConnectionValueDriver =
this.getNewConnectionValueDriverBasedOnStore(this.store);
}
geDriverConnectionType(): string {
return this.newConnectionValueDriver?.getConnectionType() ?? '';
}
changeConnectionState(val: string): void {
switch (val) {
case CONNECTION_TYPE.PURE_MODEL_CONNECTION:
this.newConnectionValueDriver = new NewPureModelConnectionDriver(
this.editorStore,
);
return;
case CONNECTION_TYPE.FLAT_DATA_CONNECTION:
this.newConnectionValueDriver = new NewFlatDataConnectionDriver(
this.editorStore,
);
return;
case CONNECTION_TYPE.RELATIONAL_CONNECTION:
this.newConnectionValueDriver =
new NewRelationalDatabaseConnectionDriver(this.editorStore);
return;
default: {
const extraNewConnectionDriverCreators = this.editorStore.pluginManager
.getApplicationPlugins()
.flatMap(
(plugin) =>
(
plugin as DSL_Mapping_LegendStudioApplicationPlugin_Extension
).getExtraNewConnectionDriverCreators?.() ?? [],
);
for (const creator of extraNewConnectionDriverCreators) {
const driver = creator(this.editorStore, val);
if (driver) {
this.newConnectionValueDriver = driver;
return;
}
}
this.editorStore.applicationStore.notificationService.notifyError(
new UnsupportedOperationError(
`Can't create new connection driver for type: no compatible creator available from plugins`,
val,
),
);
}
}
}
getNewConnectionValueDriverBasedOnStore(
store: Store,
): NewConnectionValueDriver<Connection> | undefined {
if (store instanceof ModelStore) {
return new NewPureModelConnectionDriver(this.editorStore);
} else if (store instanceof FlatData) {
return new NewFlatDataConnectionDriver(this.editorStore);
} else if (store instanceof Database) {
return new NewRelationalDatabaseConnectionDriver(this.editorStore);
}
const extraNewConnectionDriverCreators = this.editorStore.pluginManager
.getApplicationPlugins()
.flatMap(
(plugin) =>
(
plugin as DSL_Mapping_LegendStudioApplicationPlugin_Extension
).getExtraNewConnectionDriverCreators?.() ?? [],
);
for (const creator of extraNewConnectionDriverCreators) {
const driver = creator(this.editorStore, store);
if (driver) {
return driver;
}
}
this.editorStore.applicationStore.notificationService.notifyError(
new UnsupportedOperationError(
`Can't create new connection driver for store: no compatible creator available from plugins`,
store,
),
);
return undefined;
}
setStore(store: Store): void {
const newDriver = this.getNewConnectionValueDriverBasedOnStore(store);
if (newDriver) {
this.store = store;
this.newConnectionValueDriver = newDriver;
}
}
get isValid(): boolean {
return this.newConnectionValueDriver?.isValid ?? true;
}
createElement(name: string): PackageableConnection {
const connection = new PackageableConnection(name);
if (this.newConnectionValueDriver) {
packageableConnection_setConnectionValue(
connection,
this.newConnectionValueDriver.createConnection(this.store),
this.editorStore.changeDetectionState.observerContext,
); // default to model store
}
return connection;
}
}
export class NewLakehouseDataProductDriver extends NewElementDriver<DataProduct> {
title: string;
modeled = false;
constructor(editorStore: EditorStore) {
super(editorStore);
this.title = '';
makeObservable(this, {
title: observable,
modeled: observable,
setModeled: action,
setTitle: action,
isValid: computed,
});
}
override get isValid(): boolean {
return Boolean(this.title);
}
setTitle(val: string) {
this.title = val;
}
setModeled(val: boolean) {
this.modeled = val;
}
override createElement(name: string): DataProduct {
const dataProduct = new DataProduct(name);
dataProduct_setTitle(dataProduct, this.title);
dataProduct_setType(dataProduct, new InternalDataProductType());
if (!this.modeled) {
const defaultGroup = new AccessPointGroup();
defaultGroup.id = 'default';
dataProduct_addAccessPointGroup(dataProduct, defaultGroup);
} else {
const defaultGroup = new ModelAccessPointGroup();
defaultGroup.id = 'default';
defaultGroup.mapping =
PackageableElementExplicitReference.create(stub_Mapping());
dataProduct_addAccessPointGroup(dataProduct, defaultGroup);
}
return dataProduct;
}
}
export class NewServiceDriver extends NewElementDriver<Service> {
mappingOption?: PackageableElementOption<Mapping> | undefined;
runtimeOption: RuntimeOption;
constructor(editorStore: EditorStore) {
super(editorStore);
makeObservable(this, {
mappingOption: observable,
runtimeOption: observable,
setMappingOption: action,
setRuntimeOption: action,
runtimeOptions: computed,
isValid: computed,
createElement: action,
});
this.mappingOption =
editorStore.graphManagerState.usableMappings.map(buildElementOption)[0];
this.runtimeOption = guaranteeNonNullable(this.runtimeOptions[0]);
}
setMappingOption(val: PackageableElementOption<Mapping> | undefined): void {
this.mappingOption = val;
}
setRuntimeOption(val: RuntimeOption): void {
this.runtimeOption = val;
}
get compatibleMappingRuntimes(): PackageableRuntime[] {
return this.mappingOption?.value
? getMappingCompatibleRuntimes(
this.mappingOption.value,
this.editorStore.graphManagerState.usableRuntimes,
)
: [];
}
get runtimeOptions(): RuntimeOption[] {
return [
...this.compatibleMappingRuntimes.map((runtime) =>
buildElementOption(runtime),
),
{
label: CUSTOM_LABEL,
value: undefined,
},
];
}
get isValid(): boolean {
return Boolean(this.mappingOption);
}
createElement(name: string): Service {
const mappingOption = guaranteeNonNullable(this.mappingOption);
const _mapping = mappingOption.value;
const mapping = PackageableElementExplicitReference.create(_mapping);
const service = new Service(name);
let runtimeValue: Runtime;
if (this.runtimeOption.value) {
runtimeValue = new RuntimePointer(
PackageableElementExplicitReference.create(this.runtimeOption.value),
);
} else {
const engineRuntime = new EngineRuntime();
runtime_addMapping(engineRuntime, mapping);
decorateRuntimeWithNewMapping(engineRuntime, _mapping, this.editorStore);
runtimeValue = engineRuntime;
}
service_setExecution(
service,
new PureSingleExecution(
this.editorStore.graphManagerState.graphManager.createDefaultBasicRawLambda(),
service,
mapping,
runtimeValue,
),
this.editorStore.changeDetectionState.observerContext,
);
service_initNewService(service);
return service;
}
}
export class NewFileGenerationDriver extends NewElementDriver<FileGenerationSpecification> {
typeOption?: GenerationTypeOption | undefined;
constructor(editorStore: EditorStore) {
super(editorStore);
makeObservable(this, {
typeOption: observable,
setTypeOption: action,
});
this.typeOption = editorStore.graphState.graphGenerationState
.globalFileGenerationState.fileGenerationConfigurationOptions.length
? editorStore.graphState.graphGenerationState.globalFileGenerationState
.fileGenerationConfigurationOptions[0]
: undefined;
}
setTypeOption(typeOption: GenerationTypeOption | undefined): void {
this.typeOption = typeOption;
}
get isValid(): boolean {
return Boolean(this.typeOption);
}
createElement(name: string): FileGenerationSpecification {
const fileGeneration = new FileGenerationSpecification(name);
fileGeneration_setType(
fileGeneration,
guaranteeNonNullable(this.typeOption).value,
);
// default to all packages
fileGeneration_setScopeElements(
fileGeneration,
this.editorStore.graphManagerState.graph.root.children
.filter((element) => element instanceof Package)
.map((element) => PackageableElementExplicitReference.create(element)),
);
return fileGeneration;
}
}
// NOTE: Main reason for driver is to disallow if generation specification already exists
export class NewGenerationSpecificationDriver extends NewElementDriver<GenerationSpecification> {
constructor(editorStore: EditorStore) {
super(editorStore);
makeObservable(this, {
isValid: computed,
});
}
get isValid(): boolean {
// only one generation specification should exist
return !this.editorStore.graphManagerState.graph.ownGenerationSpecifications
.length;
}
createElement(name: string): GenerationSpecification {
return new GenerationSpecification(name);
}
}
export class NewDataElementDriver extends NewElementDriver<DataElement> {
embeddedDataOption?: EmbeddedDataTypeOption | undefined;
constructor(editorStore: EditorStore) {
super(editorStore);
makeObservable(this, {
embeddedDataOption: observable,
setEmbeddedDataOption: action,
});
this.embeddedDataOption = {
label: EmbeddedDataType.EXTERNAL_FORMAT_DATA,
value: EmbeddedDataType.EXTERNAL_FORMAT_DATA,
};
}
setEmbeddedDataOption(typeOption: EmbeddedDataTypeOption | undefined): void {
this.embeddedDataOption = typeOption;
}
createElement(name: string): DataElement {
const embeddedDataOption = guaranteeNonNullable(this.embeddedDataOption);
const dataElement = new DataElement(name);
const data = createEmbeddedData(embeddedDataOption.value, this.editorStore);
dataElement_setEmbeddedData(
dataElement,
data,
this.editorStore.changeDetectionState.observerContext,
);
return dataElement;
}
get isValid(): boolean {
return Boolean(this.embeddedDataOption);
}
}
export class NewElementState {
editorStore: EditorStore;
showModal = false;
showType = false;
type: string;
_package?: Package | undefined;
name = '';
newElementDriver?: NewElementDriver<PackageableElement> | undefined;
constructor(editorStore: EditorStore) {
makeObservable(this, {
showModal: observable,
showType: observable,
type: observable,
_package: observable,
name: observable,
newElementDriver: observable,
selectedPackage: computed,
isValid: computed,
setShowModal: action,
setName: action,
setShowType: action,
setNewElementDriver: action,
setPackage: action,
setElementType: action,
openModal: action,
closeModal: action,
createElement: action,
save: flow,
});
this.editorStore = editorStore;
this.type = PACKAGEABLE_ELEMENT_TYPE.PACKAGE;
}
get selectedPackage(): Package {
return this._package
? this._package
: this.editorStore.explorerTreeState.getSelectedNodePackage();
}
get isValid(): boolean {
return this.newElementDriver?.isValid ?? true;
}
setShowModal(val: boolean): void {
this.showModal = val;
}
setName(name: string): void {
this.name = name;
}
setShowType(showType: boolean): void {
this.showType = showType;
}
setNewElementDriver(
newElementDriver?: NewElementDriver<PackageableElement>,
): void {
this.newElementDriver = newElementDriver;
}
setPackage(_package?: Package): void {
this._package = _package;
}
getNewElementDriver<T extends NewElementDriver<PackageableElement>>(
clazz: Clazz<T>,
): T {
return guaranteeType(
this.newElementDriver,
clazz,
`New element driver is not of the specified type (this is likely caused by calling this method at the wrong place)`,
);
}
setElementType(newType: string): void {
if (this.type !== newType) {
let driver: NewElementDriver<PackageableElement> | undefined = undefined;
switch (newType) {
case PACKAGEABLE_ELEMENT_TYPE.RUNTIME:
driver = new NewPackageableRuntimeDriver(this.editorStore);
break;
case PACKAGEABLE_ELEMENT_TYPE.CONNECTION:
driver = new NewPackageableConnectionDriver(this.editorStore);
break;
case PACKAGEABLE_ELEMENT_TYPE.FILE_GENERATION:
driver = new NewFileGenerationDriver(this.editorStore);
break;
case PACKAGEABLE_ELEMENT_TYPE.GENERATION_SPECIFICATION:
driver = new NewGenerationSpecificationDriver(this.editorStore);
break;
case PACKAGEABLE_ELEMENT_TYPE.DATA:
driver = new NewDataElementDriver(this.editorStore);
break;
case PACKAGEABLE_ELEMENT_TYPE.SERVICE:
driver = new NewServiceDriver(this.editorStore);
break;
case PACKAGEABLE_ELEMENT_TYPE._DATA_PRODUCT:
driver = new NewLakehouseDataProductDriver(this.editorStore);
break;
default: {
const extraNewElementDriverCreators = this.editorStore.pluginManager
.getApplicationPlugins()
.flatMap(
(plugin) =>
(
plugin as DSL_LegendStudioApplicationPlugin_Extension
).getExtraNewElementDriverCreators?.() ?? [],
);
for (const creator of extraNewElementDriverCreators) {
const _driver = creator(this.editorStore, newType);
if (_driver) {
driver = _driver;
break;
}
}
break;
}
}
this.setNewElementDriver(driver);
this.type = newType;
}
}
openModal(type?: string, _package?: Package): void {
this.setShowModal(true);
this.setElementType(type ?? PACKAGEABLE_ELEMENT_TYPE.PACKAGE);
this.setPackage(_package);
this.setShowType(!type);
}
closeModal(): void {
this.setShowModal(false);
this.setElementType(PACKAGEABLE_ELEMENT_TYPE.PACKAGE);
this.setPackage(undefined);
this.setShowType(false);
this.setName('');
}
*save(): GeneratorFn<void> {
if (this.name && this.isValid) {
const [packagePath, elementName] = resolvePackageAndElementName(
this.selectedPackage,
this._package === this.editorStore.graphManagerState.graph.root,
this.name,
);
if (
this.editorStore.graphManagerState.graph.getNullablePackage(
packagePath,
) === this.editorStore.graphManagerState.graph.root &&
this.type !== PACKAGEABLE_ELEMENT_TYPE.PACKAGE
) {
throw new IllegalStateError(
`Can't create elements for type other than 'package' in root package`,
);
} else {
if (
this.editorStore.applicationStore.config.options
.TEMPORARY__enableLocalConnectionBuilder &&
this.type === PACKAGEABLE_ELEMENT_TYPE.TEMPORARY__LOCAL_CONNECTION
) {
// NOTE: this is temporary until we have proper support for local connection
// For now, we aim to fulfill the PoC for SnowflakeApp use case and will generate
// everything: mapping, store, connection, runtime, etc.
const store = new Database(`${this.name}_Database`);
const mapping = new Mapping(`${this.name}_Mapping`);
// connection
const connection = new PackageableConnection(
`${this.name}_LocalConnection`,
);
const _suffix = `${packagePath.replaceAll(
ELEMENT_PATH_DELIMITER,
'-',
)}-${connection.name}`;
const datasourceSpecification = new SnowflakeDatasourceSpecification(
`legend-local-snowflake-accountName-${_suffix}`,
`legend-local-snowflake-region-${_suffix}`,
`legend-local-snowflake-warehouseName-${_suffix}`,
`legend-local-snowflake-databaseName-${_suffix}`,
);
datasourceSpecification.cloudType = `legend-local-snowflake-cloudType-${_suffix}`;
datasourceSpecification.role = `legend-local-snowflake-role-${_suffix}`;
const connectionValue = new RelationalDatabaseConnection(
PackageableElementExplicitReference.create(store),
DatabaseType.Snowflake,
datasourceSpecification,
new SnowflakePublicAuthenticationStrategy(
`legend-local-snowflake-privateKeyVaultReference-${_suffix}`,
`legend-local-snowflake-passphraseVaultReference-${_suffix}`,
`legend-local-snowflake-publicuserName-${_suffix}`,
),
);
connectionValue.localMode = true;
connection.connectionValue = connectionValue;
// runtime
const runtime = new PackageableRuntime(`${this.name}_Runtime`);
const engineRuntime = new EngineRuntime();
engineRuntime.mappings = [
PackageableElementExplicitReference.create(mapping),
];
const storeConnections = new StoreConnections(
PackageableElementExplicitReference.create(store),
);
storeConnections.storeConnections = [
new IdentifiedConnection(
generateIdentifiedConnectionId(engineRuntime),
new ConnectionPointer(
PackageableElementExplicitReference.create(connection),
),
),
];
engineRuntime.connections = [storeConnections];
runtime.runtimeValue = engineRuntime;
// add the elements
yield flowResult(
this.editorStore.graphEditorMode.addElement(
store,
packagePath,
false,
),
);
yield flowResult(
this.editorStore.graphEditorMode.addElement(
connection,
packagePath,
false,
),
);
yield flowResult(
this.editorStore.graphEditorMode.addElement(
mapping,
packagePath,
false,
),
);
yield flowResult(
this.editorStore.graphEditorMode.addElement(
runtime,
packagePath,
false,
),
);
} else {
const element = this.createElement(elementName);
yield flowResult(
this.editorStore.graphEditorMode.addElement(
element,
packagePath,
true,
),
);
// post creation handling
yield handlePostCreateAction(element, this.editorStore);
}
}
}
this.closeModal();
}
createElement(name: string): PackageableElement {
let element: PackageableElement | undefined;
switch (this.type) {
case PACKAGEABLE_ELEMENT_TYPE.PACKAGE:
element = new Package(name);
break;
case PACKAGEABLE_ELEMENT_TYPE.CLASS:
element = new Class(name);
break;
case PACKAGEABLE_ELEMENT_TYPE.ASSOCIATION:
element = new Association(name);
break;
case PACKAGEABLE_ELEMENT_TYPE.ENUMERATION:
element = new Enumeration(name);
break;
case PACKAGEABLE_ELEMENT_TYPE.MEASURE:
element = new Measure(name);
break;
case PACKAGEABLE_ELEMENT_TYPE.PROFILE:
element = new Profile(name);
break;
// default for function -> return type: String, return Multiplicity 1
case PACKAGEABLE_ELEMENT_TYPE.FUNCTION: {
const fn = new ConcreteFunctionDefinition(
name,
GenericTypeExplicitReference.create(
new GenericType(PrimitiveType.STRING),
),
Multiplicity.ONE,
);
// default to empty string
fn.expressionSequence =
this.editorStore.graphManagerState.graphManager.createDefaultBasicRawLambda()
.body as object[];
element = fn;
break;
}
case PACKAGEABLE_ELEMENT_TYPE.MAPPING:
element = new Mapping(name);
break;
case PACKAGEABLE_ELEMENT_TYPE.FLAT_DATA_STORE:
element = new FlatData(name);
break;
case PACKAGEABLE_ELEMENT_TYPE.DATABASE:
element = new Database(name);
break;
case PACKAGEABLE_ELEMENT_TYPE.SERVICE: {
element =
this.getNewElementDriver(NewServiceDriver).createElement(name);
break;
}
case PACKAGEABLE_ELEMENT_TYPE.CONNECTION:
element = this.getNewElementDriver(
NewPackageableConnectionDriver,
).createElement(name);
break;
case PACKAGEABLE_ELEMENT_TYPE.RUNTIME:
element = this.getNewElementDriver(
NewPackageableRuntimeDriver,
).createElement(name);
break;
case PACKAGEABLE_ELEMENT_TYPE.FILE_GENERATION:
element = this.getNewElementDriver(
NewFileGenerationDriver,
).createElement(name);
break;
case PACKAGEABLE_ELEMENT_TYPE.DATA:
element =
this.getNewElementDriver(NewDataElementDriver).createElement(name);
break;
case PACKAGEABLE_ELEMENT_TYPE.GENERATION_SPECIFICATION:
element = new GenerationSpecification(name);
break;
case PACKAGEABLE_ELEMENT_TYPE._DATA_PRODUCT:
element = this.getNewElementDriver(
NewLakehouseDataProductDriver,
).createElement(name);
break;
default: {
const extraNewElementFromStateCreators = this.editorStore.pluginManager
.getApplicationPlugins()
.flatMap(
(plugin) =>
(
plugin as DSL_LegendStudioApplicationPlugin_Extension
).getExtraNewElementFromStateCreators?.() ?? [],
);
for (const creator of extraNewElementFromStateCreators) {
const _element = creator(this.type, name, this);
if (_element) {
element = _element;
break;
}
}
if (!element) {
throw new UnsupportedOperationError(
`Can't create element of type '${this.type}': no compatible element creator available from plugins`,
);
}
}
}
return element;
}
}