@finos/legend-application-studio
Version:
Legend Studio application core
187 lines (172 loc) • 6.16 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 GeneratorFn,
assertErrorThrown,
LogEvent,
ActionState,
uniqBy,
} from '@finos/legend-shared';
import { observable, makeObservable, flow, flowResult, action } from 'mobx';
import { languages as monacoLanguagesAPI } from 'monaco-editor';
import {
type Database,
type PackageableConnection,
guaranteeRelationalDatabaseConnection,
GRAPH_MANAGER_EVENT,
} from '@finos/legend-graph';
import type { EditorStore } from '../EditorStore.js';
import type { CommandRegistrar } from '@finos/legend-application';
import { STO_RELATIONAL_LEGEND_STUDIO_COMMAND_KEY } from '../../../__lib__/STO_Relational_LegendStudioCommand.js';
import { PANEL_MODE } from '../EditorConfig.js';
import { DatabaseSchemaExplorerState } from '../editor-state/element-editor-state/connection/DatabaseBuilderState.js';
import {
AbstractSQLPlaygroundState,
CsvSqlExecutionResult,
} from '@finos/legend-query-builder';
const DEFAULT_SQL_TEXT = `--Start building your SQL. Note that you can also drag-and-drop nodes from schema explorer\n`;
export class StudioSQLPlaygroundPanelState
extends AbstractSQLPlaygroundState
implements CommandRegistrar
{
readonly editorStore: EditorStore;
isFetchingSchema = ActionState.create();
connection?: PackageableConnection | undefined;
database?: Database | undefined;
schemaExplorerState?: DatabaseSchemaExplorerState | undefined;
databaseSchema: monacoLanguagesAPI.CompletionItem[];
constructor(editorStore: EditorStore) {
super();
makeObservable(this, {
isFetchingSchema: observable,
connection: observable,
database: observable,
schemaExplorerState: observable,
databaseSchema: observable,
setConnection: action,
setDataBaseSchema: action,
executeRawSQL: flow,
});
this.sqlEditorTextModel.setValue(DEFAULT_SQL_TEXT);
this.editorStore = editorStore;
this.databaseSchema = [];
}
setConnection(val: PackageableConnection | undefined): void {
this.connection = val;
if (val) {
const connection = guaranteeRelationalDatabaseConnection(val);
this.database = connection.store?.value;
this.schemaExplorerState = new DatabaseSchemaExplorerState(
this.editorStore,
connection,
);
} else {
this.database = undefined;
this.schemaExplorerState = undefined;
}
this.sqlEditorTextModel.setValue(DEFAULT_SQL_TEXT);
}
setDataBaseSchema(val: monacoLanguagesAPI.CompletionItem[]): void {
this.databaseSchema = val;
}
fetchSchemaMetaData(): GeneratorFn<void> | undefined {
return this.schemaExplorerState?.fetchDatabaseMetadata();
}
override getCodeCompletionSuggestions(): string[] {
const base = super.getCodeCompletionSuggestions();
if (this.schemaExplorerState?.treeData) {
const dbLabelText = uniqBy(
Array.from(this.schemaExplorerState.treeData.nodes.values()).map(
(value) => value.label,
),
(label) => label,
);
const dbLabelsCompletionItem = uniqBy(
dbLabelText.map(
(value) =>
({
label: value,
kind: monacoLanguagesAPI.CompletionItemKind.Field,
insertTextRules:
monacoLanguagesAPI.CompletionItemInsertTextRule.InsertAsSnippet,
insertText: `${value} `,
}) as monacoLanguagesAPI.CompletionItem,
),
(val) => val.label,
);
this.setDataBaseSchema(dbLabelsCompletionItem);
return base.concat(dbLabelText);
}
return base;
}
override registerCommands(): void {
this.editorStore.applicationStore.commandService.registerCommand({
key: STO_RELATIONAL_LEGEND_STUDIO_COMMAND_KEY.SQL_PLAYGROUND_EXECUTE,
trigger: () =>
this.editorStore.isInitialized &&
this.editorStore.activePanelMode === PANEL_MODE.SQL_PLAYGROUND &&
Boolean(this.connection) &&
Boolean(this.sqlText.length),
action: () => {
flowResult(this.executeRawSQL()).catch(
this.editorStore.applicationStore.alertUnhandledError,
);
},
});
}
override deregisterCommands(): void {
[STO_RELATIONAL_LEGEND_STUDIO_COMMAND_KEY.SQL_PLAYGROUND_EXECUTE].forEach(
(key) =>
this.editorStore.applicationStore.commandService.deregisterCommand(key),
);
}
override *executeRawSQL(): GeneratorFn<void> {
if (!this.connection || this.executeRawSQLState.isInProgress) {
return;
}
try {
this.executeRawSQLState.inProgress();
let sql = this.sqlText;
const currentSelection = this.sqlEditor?.getSelection();
if (currentSelection) {
const selectionValue =
this.sqlEditorTextModel.getValueInRange(currentSelection);
if (selectionValue.trim() !== '') {
sql = selectionValue;
}
}
const start = Date.now();
const value =
(yield this.editorStore.graphManagerState.graphManager.executeRawSQL(
guaranteeRelationalDatabaseConnection(this.connection),
sql,
)) as string;
this.sqlExecutionResult = new CsvSqlExecutionResult(
value,
Date.now() - start,
);
} catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.logService.error(
LogEvent.create(GRAPH_MANAGER_EVENT.EXECUTION_FAILURE),
error,
);
this.editorStore.applicationStore.notificationService.notifyError(error);
} finally {
this.executeRawSQLState.complete();
}
}
}