mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-14 01:25:37 -05:00
enable table designer for table script in sql database project (#19237)
* add 'open in designer' to context menu of tables in sql projects * fix tests * Address comments * enable table designer for sql database proj * update label and issues on init * vbump sts * use promisified fs * pr comments Co-authored-by: Alan Ren <alanren@microsoft.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "4.0.0.5",
|
||||
"version": "4.0.0.9",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-net6.0.zip",
|
||||
"Windows_64": "win-x64-net6.0.zip",
|
||||
|
||||
@@ -9,13 +9,18 @@ import * as vscode from 'vscode';
|
||||
import { sqlProviderName } from '../constants';
|
||||
import { generateUuid } from 'vscode-languageclient/lib/utils/uuid';
|
||||
import { ITelemetryEventProperties, Telemetry } from '../telemetry';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const NewTableText = localize('tableDesigner.NewTable', "New Table");
|
||||
export function registerTableDesignerCommands(appContext: AppContext) {
|
||||
appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.newTable', async (context: azdata.ObjectExplorerContext) => {
|
||||
const connectionString = await azdata.connection.getConnectionString(context.connectionProfile.id, true);
|
||||
const tableIcon = context.nodeInfo.nodeSubType as azdata.designers.TableIcon;
|
||||
const telemetryInfo = await getTelemetryInfo(context, tableIcon);
|
||||
await azdata.designers.openTableDesigner(sqlProviderName, {
|
||||
title: NewTableText,
|
||||
tooltip: `${context.connectionProfile.serverName} - ${context.connectionProfile.databaseName} - ${NewTableText}`,
|
||||
server: context.connectionProfile.serverName,
|
||||
database: context.connectionProfile.databaseName,
|
||||
isNewTable: true,
|
||||
@@ -35,6 +40,8 @@ export function registerTableDesignerCommands(appContext: AppContext) {
|
||||
const tableIcon = context.nodeInfo.nodeSubType as azdata.designers.TableIcon;
|
||||
const telemetryInfo = await getTelemetryInfo(context, tableIcon);
|
||||
await azdata.designers.openTableDesigner(sqlProviderName, {
|
||||
title: `${schema}.${name}`,
|
||||
tooltip: `${server} - ${database} - ${schema}.${name}`,
|
||||
server: server,
|
||||
database: database,
|
||||
isNewTable: false,
|
||||
|
||||
@@ -190,6 +190,11 @@
|
||||
"command": "sqlDatabaseProjects.convertToSdkStyleProject",
|
||||
"title": "%sqlDatabaseProjects.convertToSdkStyleProject%",
|
||||
"category": "%sqlDatabaseProjects.displayName%"
|
||||
},
|
||||
{
|
||||
"command": "sqlDatabaseProjects.openInDesigner",
|
||||
"title": "%sqlDatabaseProjects.openInDesigner%",
|
||||
"category": "%sqlDatabaseProjects.displayName%"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
@@ -305,6 +310,10 @@
|
||||
{
|
||||
"command": "sqlDatabaseProjects.convertToSdkStyleProject",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "sqlDatabaseProjects.openInDesigner",
|
||||
"when": "false"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
@@ -388,6 +397,11 @@
|
||||
"when": "view == dataworkspace.views.main && viewItem == databaseProject.itemType.file.externalStreamingJob",
|
||||
"group": "5_dbProjects_streamingJob"
|
||||
},
|
||||
{
|
||||
"command": "sqlDatabaseProjects.openInDesigner",
|
||||
"when": "azdataAvailable && view == dataworkspace.views.main && viewItem == databaseProject.itemType.file.table && config.workbench.enablePreviewFeatures",
|
||||
"group": "6_dbProjects_openInDesigner"
|
||||
},
|
||||
{
|
||||
"command": "sqlDatabaseProjects.exclude",
|
||||
"when": "view == dataworkspace.views.main && viewItem == databaseProject.itemType.folder || viewItem =~ /^databaseProject.itemType.file/",
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"sqlDatabaseProjects.changeTargetPlatform": "Change Target Platform",
|
||||
"sqlDatabaseProjects.generateProjectFromOpenApiSpec": "Generate SQL Project from OpenAPI/Swagger spec",
|
||||
"sqlDatabaseProjects.convertToSdkStyleProject": "Convert to SDK-style project",
|
||||
"sqlDatabaseProjects.openInDesigner": "Open in Designer",
|
||||
|
||||
"sqlDatabaseProjects.Settings": "Database Projects",
|
||||
"sqlDatabaseProjects.dotnetInstallLocation": "Full path to .NET SDK on the machine.",
|
||||
|
||||
@@ -529,6 +529,7 @@ export enum DatabaseProjectItemType {
|
||||
folder = 'databaseProject.itemType.folder',
|
||||
file = 'databaseProject.itemType.file',
|
||||
externalStreamingJob = 'databaseProject.itemType.file.externalStreamingJob',
|
||||
table = 'databaseProject.itemType.file.table',
|
||||
referencesRoot = 'databaseProject.itemType.referencesRoot',
|
||||
reference = 'databaseProject.itemType.reference',
|
||||
dataSourceRoot = 'databaseProject.itemType.dataSourceRoot',
|
||||
|
||||
@@ -17,6 +17,9 @@ import { WorkspaceTreeItem } from 'dataworkspace';
|
||||
import * as constants from '../common/constants';
|
||||
import { SqlDatabaseProjectProvider } from '../projectProvider/projectProvider';
|
||||
import { GenerateProjectFromOpenApiSpecOptions, ItemType } from 'sqldbproj';
|
||||
import { TableFileNode } from '../models/tree/fileFolderTreeItem';
|
||||
import { ProjectRootTreeItem } from '../models/tree/projectTreeItem';
|
||||
import { getAzdataApi } from '../common/utils';
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
@@ -56,35 +59,56 @@ export default class MainController implements vscode.Disposable {
|
||||
|
||||
private async initializeDatabaseProjects(): Promise<void> {
|
||||
// init commands
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.properties', async (node: WorkspaceTreeItem) => { return vscode.window.showErrorMessage(`Properties not yet implemented: ${node.element.uri.path}`); }); // TODO
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.properties', async (node: WorkspaceTreeItem) => { return vscode.window.showErrorMessage(`Properties not yet implemented: ${node.element.uri.path}`); })); // TODO
|
||||
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.build', async (node: WorkspaceTreeItem) => { return this.projectsController.buildProject(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.publish', async (node: WorkspaceTreeItem) => { return this.projectsController.publishProject(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.schemaCompare', async (node: WorkspaceTreeItem) => { return this.projectsController.schemaCompare(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.schemaComparePublishProjectChanges', async (operationId: string, projectFilePath: string, folderStructure: string): Promise<mssql.SchemaComparePublishProjectResult> => { return await this.projectsController.schemaComparePublishProjectChanges(operationId, projectFilePath, folderStructure); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.updateProjectFromDatabase', async (node: azdataType.IConnectionProfile | vscodeMssql.ITreeNodeInfo | WorkspaceTreeItem) => { await this.projectsController.updateProjectFromDatabase(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.createProjectFromDatabase', async (context: azdataType.IConnectionProfile | vscodeMssql.ITreeNodeInfo | undefined) => { return this.projectsController.createProjectFromDatabase(context); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.generateProjectFromOpenApiSpec', async (options?: GenerateProjectFromOpenApiSpecOptions) => { return this.projectsController.generateProjectFromOpenApiSpec(options); });
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.build', async (node: WorkspaceTreeItem) => { return this.projectsController.buildProject(node); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.publish', async (node: WorkspaceTreeItem) => { return this.projectsController.publishProject(node); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.schemaCompare', async (node: WorkspaceTreeItem) => { return this.projectsController.schemaCompare(node); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.schemaComparePublishProjectChanges', async (operationId: string, projectFilePath: string, folderStructure: string): Promise<mssql.SchemaComparePublishProjectResult> => { return await this.projectsController.schemaComparePublishProjectChanges(operationId, projectFilePath, folderStructure); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.updateProjectFromDatabase', async (node: azdataType.IConnectionProfile | vscodeMssql.ITreeNodeInfo | WorkspaceTreeItem) => { await this.projectsController.updateProjectFromDatabase(node); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.createProjectFromDatabase', async (context: azdataType.IConnectionProfile | vscodeMssql.ITreeNodeInfo | undefined) => { return this.projectsController.createProjectFromDatabase(context); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.generateProjectFromOpenApiSpec', async (options?: GenerateProjectFromOpenApiSpecOptions) => { return this.projectsController.generateProjectFromOpenApiSpec(options); }));
|
||||
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.newScript', async (node: WorkspaceTreeItem) => { return this.projectsController.addItemPromptFromNode(node, ItemType.script); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.newPreDeploymentScript', async (node: WorkspaceTreeItem) => { return this.projectsController.addItemPromptFromNode(node, ItemType.preDeployScript); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.newPostDeploymentScript', async (node: WorkspaceTreeItem) => { return this.projectsController.addItemPromptFromNode(node, ItemType.postDeployScript); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.newTable', async (node: WorkspaceTreeItem) => { return this.projectsController.addItemPromptFromNode(node, ItemType.table); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.newView', async (node: WorkspaceTreeItem) => { return this.projectsController.addItemPromptFromNode(node, ItemType.view); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.newStoredProcedure', async (node: WorkspaceTreeItem) => { return this.projectsController.addItemPromptFromNode(node, ItemType.storedProcedure); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.newExternalStreamingJob', async (node: WorkspaceTreeItem) => { return this.projectsController.addItemPromptFromNode(node, ItemType.externalStreamingJob); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.newItem', async (node: WorkspaceTreeItem) => { return this.projectsController.addItemPromptFromNode(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.addExistingItem', async (node: WorkspaceTreeItem) => { return this.projectsController.addExistingItemPrompt(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.newFolder', async (node: WorkspaceTreeItem) => { return this.projectsController.addFolderPrompt(node); });
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.newScript', async (node: WorkspaceTreeItem) => { return this.projectsController.addItemPromptFromNode(node, ItemType.script); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.newPreDeploymentScript', async (node: WorkspaceTreeItem) => { return this.projectsController.addItemPromptFromNode(node, ItemType.preDeployScript); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.newPostDeploymentScript', async (node: WorkspaceTreeItem) => { return this.projectsController.addItemPromptFromNode(node, ItemType.postDeployScript); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.newTable', async (node: WorkspaceTreeItem) => { return this.projectsController.addItemPromptFromNode(node, ItemType.table); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.newView', async (node: WorkspaceTreeItem) => { return this.projectsController.addItemPromptFromNode(node, ItemType.view); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.newStoredProcedure', async (node: WorkspaceTreeItem) => { return this.projectsController.addItemPromptFromNode(node, ItemType.storedProcedure); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.newExternalStreamingJob', async (node: WorkspaceTreeItem) => { return this.projectsController.addItemPromptFromNode(node, ItemType.externalStreamingJob); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.newItem', async (node: WorkspaceTreeItem) => { return this.projectsController.addItemPromptFromNode(node); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.addExistingItem', async (node: WorkspaceTreeItem) => { return this.projectsController.addExistingItemPrompt(node); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.newFolder', async (node: WorkspaceTreeItem) => { return this.projectsController.addFolderPrompt(node); }));
|
||||
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.addDatabaseReference', async (node: WorkspaceTreeItem) => { return this.projectsController.addDatabaseReference(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.openContainingFolder', async (node: WorkspaceTreeItem) => { return this.projectsController.openContainingFolder(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.editProjectFile', async (node: WorkspaceTreeItem) => { return this.projectsController.editProjectFile(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.convertToSdkStyleProject', async (node: WorkspaceTreeItem) => { return this.projectsController.convertToSdkStyleProject(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.delete', async (node: WorkspaceTreeItem) => { return this.projectsController.delete(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.exclude', async (node: WorkspaceTreeItem) => { return this.projectsController.exclude(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.changeTargetPlatform', async (node: WorkspaceTreeItem) => { return this.projectsController.changeTargetPlatform(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.validateExternalStreamingJob', async (node: WorkspaceTreeItem) => { return this.projectsController.validateExternalStreamingJob(node); });
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.addDatabaseReference', async (node: WorkspaceTreeItem) => { return this.projectsController.addDatabaseReference(node); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.openContainingFolder', async (node: WorkspaceTreeItem) => { return this.projectsController.openContainingFolder(node); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.editProjectFile', async (node: WorkspaceTreeItem) => { return this.projectsController.editProjectFile(node); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.convertToSdkStyleProject', async (node: WorkspaceTreeItem) => { return this.projectsController.convertToSdkStyleProject(node); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.delete', async (node: WorkspaceTreeItem) => { return this.projectsController.delete(node); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.exclude', async (node: WorkspaceTreeItem) => { return this.projectsController.exclude(node); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.changeTargetPlatform', async (node: WorkspaceTreeItem) => { return this.projectsController.changeTargetPlatform(node); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.validateExternalStreamingJob', async (node: WorkspaceTreeItem) => { return this.projectsController.validateExternalStreamingJob(node); }));
|
||||
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.openInDesigner', async (node: WorkspaceTreeItem) => {
|
||||
if (node?.element instanceof TableFileNode) {
|
||||
const tableFileNode = node.element as TableFileNode;
|
||||
const projectNode = tableFileNode.root as ProjectRootTreeItem;
|
||||
const filePath = tableFileNode.fileSystemUri.fsPath;
|
||||
const projectPath = projectNode.project.projectFilePath;
|
||||
const targetVersion = projectNode.project.getProjectTargetVersion();
|
||||
await getAzdataApi()!.designers.openTableDesigner('MSSQL', {
|
||||
title: tableFileNode.friendlyName,
|
||||
tooltip: `${projectPath} - ${tableFileNode.friendlyName}`,
|
||||
id: filePath,
|
||||
isNewTable: false,
|
||||
tableScriptPath: filePath,
|
||||
projectFilePath: projectPath,
|
||||
allScripts: projectNode.project.files.map(entry => entry.fsUri.fsPath),
|
||||
targetVersion: targetVersion
|
||||
}, {
|
||||
'ProjectTargetVersion': targetVersion
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
IconPathHelper.setExtensionContext(this.extensionContext);
|
||||
|
||||
|
||||
@@ -245,10 +245,20 @@ export class Project implements ISqlProject {
|
||||
|
||||
// create a FileProjectEntry for each file
|
||||
const fileEntries: FileProjectEntry[] = [];
|
||||
filesSet.forEach(f => {
|
||||
for (let f of Array.from(filesSet.values())) {
|
||||
const typeEntry = entriesWithType.find(e => e.relativePath === f);
|
||||
fileEntries.push(this.createFileProjectEntry(f, EntryType.File, typeEntry ? typeEntry.typeAttribute : undefined));
|
||||
});
|
||||
let containsCreateTableStatement;
|
||||
|
||||
// read file to check if it has a "Create Table" statement
|
||||
const fullPath = path.join(utils.getPlatformSafeFileEntryPath(this.projectFolderPath), utils.getPlatformSafeFileEntryPath(f));
|
||||
|
||||
if (await utils.exists(fullPath)) {
|
||||
const fileContents = await fs.readFile(fullPath);
|
||||
containsCreateTableStatement = fileContents.toString().toLowerCase().includes('create table');
|
||||
}
|
||||
|
||||
fileEntries.push(this.createFileProjectEntry(f, EntryType.File, typeEntry ? typeEntry.typeAttribute : undefined, containsCreateTableStatement));
|
||||
}
|
||||
|
||||
return fileEntries;
|
||||
}
|
||||
@@ -1078,13 +1088,14 @@ export class Project implements ISqlProject {
|
||||
return this.getCollectionProjectPropertyValue(constants.DatabaseSource);
|
||||
}
|
||||
|
||||
public createFileProjectEntry(relativePath: string, entryType: EntryType, sqlObjectType?: string): FileProjectEntry {
|
||||
public createFileProjectEntry(relativePath: string, entryType: EntryType, sqlObjectType?: string, containsCreateTableStatement?: boolean): FileProjectEntry {
|
||||
let platformSafeRelativePath = utils.getPlatformSafeFileEntryPath(relativePath);
|
||||
return new FileProjectEntry(
|
||||
Uri.file(path.join(this.projectFolderPath, platformSafeRelativePath)),
|
||||
utils.convertSlashesForSqlProj(relativePath),
|
||||
entryType,
|
||||
sqlObjectType);
|
||||
sqlObjectType,
|
||||
containsCreateTableStatement);
|
||||
}
|
||||
|
||||
private findOrCreateItemGroup(containedTag?: string, prePostScriptExist?: { scriptExist: boolean; }): Element {
|
||||
|
||||
@@ -27,12 +27,14 @@ export class FileProjectEntry extends ProjectEntry implements IFileProjectEntry
|
||||
fsUri: Uri;
|
||||
relativePath: string;
|
||||
sqlObjectType: string | undefined;
|
||||
containsCreateTableStatement: boolean | undefined;
|
||||
|
||||
constructor(uri: Uri, relativePath: string, entryType: EntryType, sqlObjectType?: string) {
|
||||
constructor(uri: Uri, relativePath: string, entryType: EntryType, sqlObjectType?: string, containsCreateTableStatement?: boolean) {
|
||||
super(entryType);
|
||||
this.fsUri = uri;
|
||||
this.relativePath = relativePath;
|
||||
this.sqlObjectType = sqlObjectType;
|
||||
this.containsCreateTableStatement = containsCreateTableStatement;
|
||||
}
|
||||
|
||||
public override toString(): string {
|
||||
|
||||
@@ -80,6 +80,15 @@ export class ExternalStreamingJobFileNode extends FileNode {
|
||||
}
|
||||
}
|
||||
|
||||
export class TableFileNode extends FileNode {
|
||||
public override get treeItem(): vscode.TreeItem {
|
||||
const treeItem = super.treeItem;
|
||||
treeItem.contextValue = DatabaseProjectItemType.table;
|
||||
|
||||
return treeItem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two folder/file tree nodes so that folders come before files, then alphabetically
|
||||
* @param a a folder or file tree node
|
||||
|
||||
@@ -80,6 +80,8 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem {
|
||||
case EntryType.File:
|
||||
if (entry.sqlObjectType === ExternalStreamingJob) {
|
||||
newNode = new fileTree.ExternalStreamingJobFileNode(entry.fsUri, parentNode);
|
||||
} else if (entry.containsCreateTableStatement) {
|
||||
newNode = new fileTree.TableFileNode(entry.fsUri, parentNode);
|
||||
}
|
||||
else {
|
||||
newNode = new fileTree.FileNode(entry.fsUri, parentNode);
|
||||
|
||||
66
src/sql/azdata.proposed.d.ts
vendored
66
src/sql/azdata.proposed.d.ts
vendored
@@ -735,29 +735,21 @@ declare module 'azdata' {
|
||||
*/
|
||||
export interface TableInfo {
|
||||
/**
|
||||
* The server name.
|
||||
* Used as the table designer editor's tab header text.
|
||||
*/
|
||||
server: string;
|
||||
title: string;
|
||||
/**
|
||||
* The database name
|
||||
* Used as the table designer editor's tab header hover text.
|
||||
*/
|
||||
database: string;
|
||||
/**
|
||||
* The schema name, only required for existing table.
|
||||
*/
|
||||
schema?: string;
|
||||
/**
|
||||
* The table name, only required for existing table.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* A boolean value indicates whether a new table is being designed.
|
||||
*/
|
||||
isNewTable: boolean;
|
||||
tooltip: string;
|
||||
/**
|
||||
* Unique identifier of the table. Will be used to decide whether a designer is already opened for the table.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* A boolean value indicates whether a new table is being designed.
|
||||
*/
|
||||
isNewTable: boolean;
|
||||
/**
|
||||
* Extension can store additional information that the provider needs to uniquely identify a table.
|
||||
*/
|
||||
@@ -781,6 +773,14 @@ declare module 'azdata' {
|
||||
* The initial state of the designer.
|
||||
*/
|
||||
viewModel: DesignerViewModel;
|
||||
/**
|
||||
* The new table info after initialization.
|
||||
*/
|
||||
tableInfo: TableInfo;
|
||||
/**
|
||||
* The issues.
|
||||
*/
|
||||
issues?: DesignerIssue[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -933,6 +933,10 @@ declare module 'azdata' {
|
||||
* Additional primary key properties. Common primary key properties: primaryKeyName, primaryKeyDescription.
|
||||
*/
|
||||
additionalPrimaryKeyProperties?: DesignerDataPropertyInfo[];
|
||||
/**
|
||||
* Whether to use advanced save mode. for advanced save mode, a publish changes dialog will be opened with preview of changes.
|
||||
*/
|
||||
useAdvancedSaveMode: boolean;
|
||||
}
|
||||
|
||||
export interface TableDesignerBuiltInTableViewOptions extends DesignerTablePropertiesBase {
|
||||
@@ -1107,7 +1111,7 @@ declare module 'azdata' {
|
||||
/**
|
||||
* the path of the edit target.
|
||||
*/
|
||||
path: DesignerEditPath;
|
||||
path: DesignerPropertyPath;
|
||||
/**
|
||||
* the new value.
|
||||
*/
|
||||
@@ -1115,7 +1119,7 @@ declare module 'azdata' {
|
||||
}
|
||||
|
||||
/**
|
||||
* The path of the edit target.
|
||||
* The path of the property.
|
||||
* Below are the 3 scenarios and their expected path.
|
||||
* Note: 'index-{x}' in the description below are numbers represent the index of the object in the list.
|
||||
* 1. 'Add' scenario
|
||||
@@ -1129,7 +1133,7 @@ declare module 'azdata' {
|
||||
* a. ['propertyName1',index-1]. Example: remove a column from the columns property: ['columns',0'].
|
||||
* b. ['propertyName1',index-1,'propertyName2',index-2]. Example: remove a column mapping from a foreign key's column mapping table: ['foreignKeys',0,'mappings',0].
|
||||
*/
|
||||
export type DesignerEditPath = (string | number)[];
|
||||
export type DesignerPropertyPath = (string | number)[];
|
||||
|
||||
/**
|
||||
* Severity of the messages returned by the provider after processing an edit.
|
||||
@@ -1139,6 +1143,28 @@ declare module 'azdata' {
|
||||
*/
|
||||
export type DesignerIssueSeverity = 'error' | 'warning' | 'information';
|
||||
|
||||
/**
|
||||
* Represents the issue in the designer
|
||||
*/
|
||||
export interface DesignerIssue {
|
||||
/**
|
||||
* Severity of the issue.
|
||||
*/
|
||||
severity: DesignerIssueSeverity,
|
||||
/**
|
||||
* Path of the property that is associated with the issue.
|
||||
*/
|
||||
propertyPath?: DesignerPropertyPath,
|
||||
/**
|
||||
* Description of the issue.
|
||||
*/
|
||||
description: string,
|
||||
/**
|
||||
* Url to a web page that has the explaination of the issue.
|
||||
*/
|
||||
moreInfoLink?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The result returned by the table designer provider after handling an edit request.
|
||||
*/
|
||||
@@ -1158,7 +1184,7 @@ declare module 'azdata' {
|
||||
/**
|
||||
* Issues of current state.
|
||||
*/
|
||||
issues?: { severity: DesignerIssueSeverity, description: string, propertyPath?: DesignerEditPath }[];
|
||||
issues?: DesignerIssue[];
|
||||
/**
|
||||
* The input validation error.
|
||||
*/
|
||||
|
||||
@@ -51,7 +51,7 @@ export interface DesignerComponentInput {
|
||||
/**
|
||||
* Start initilizing the designer input object.
|
||||
*/
|
||||
initialize(): void;
|
||||
initialize(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Start processing the edit made in the designer, the OnEditProcessed event will be fired when the processing is done.
|
||||
|
||||
@@ -11,13 +11,10 @@ import { TableDesignerProvider } from 'sql/workbench/services/tableDesigner/comm
|
||||
import * as azdata from 'azdata';
|
||||
import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Schemas } from 'sql/base/common/schemas';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
const NewTable: string = localize('tableDesigner.newTable', "New Table");
|
||||
|
||||
enum TableIcon {
|
||||
Basic = 'Basic',
|
||||
Temporal = 'Temporal',
|
||||
@@ -43,19 +40,20 @@ export class TableDesignerInput extends EditorInput {
|
||||
tableInfo: azdata.designers.TableInfo,
|
||||
telemetryInfo: { [key: string]: string },
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@INotificationService private readonly _notificationService: INotificationService) {
|
||||
super();
|
||||
this._designerComponentInput = this._instantiationService.createInstance(TableDesignerComponentInput, this._provider, tableInfo, telemetryInfo);
|
||||
this._register(this._designerComponentInput.onStateChange((e) => {
|
||||
if (e.previousState.pendingAction === 'publish') {
|
||||
this.setEditorLabel();
|
||||
this._onDidChangeLabel.fire();
|
||||
}
|
||||
if (e.currentState.dirty !== e.previousState.dirty) {
|
||||
this._onDidChangeDirty.fire();
|
||||
}
|
||||
}));
|
||||
this._register(this._designerComponentInput.onInitialized(() => {
|
||||
this.setEditorLabel();
|
||||
}));
|
||||
|
||||
// default to basic if icon is null (new table) or no sub type
|
||||
this._tableIcon = tableInfo.tableIcon ? tableInfo.tableIcon as TableIcon : TableIcon.Basic;
|
||||
@@ -97,7 +95,7 @@ export class TableDesignerInput extends EditorInput {
|
||||
if (this._designerComponentInput.pendingAction) {
|
||||
this._notificationService.warn(localize('tableDesigner.OperationInProgressWarning', "The operation cannot be performed while another operation is in progress."));
|
||||
} else {
|
||||
await this._designerComponentInput.openPublishDialog();
|
||||
await this._designerComponentInput.save();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -118,18 +116,8 @@ export class TableDesignerInput extends EditorInput {
|
||||
}
|
||||
|
||||
private setEditorLabel(): void {
|
||||
const tableInfo = this._designerComponentInput.tableInfo;
|
||||
if (tableInfo.isNewTable) {
|
||||
const existingNames = this._editorService.editors.map(editor => editor.getName());
|
||||
// Find the next available unique name for the new table designer
|
||||
let idx = 1;
|
||||
do {
|
||||
this._name = `${NewTable} ${idx}`;
|
||||
idx++;
|
||||
} while (existingNames.indexOf(this._name) !== -1);
|
||||
} else {
|
||||
this._name = `${tableInfo.schema}.${tableInfo.name}`;
|
||||
}
|
||||
this._title = `${tableInfo.server}.${tableInfo.database} - ${this._name}`;
|
||||
this._name = this._designerComponentInput.tableInfo.title;
|
||||
this._title = this._designerComponentInput.tableInfo.tooltip;
|
||||
this._onDidChangeLabel.fire();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,57 +6,56 @@
|
||||
import { TableDesignerComponentInput } from 'sql/workbench/services/tableDesigner/browser/tableDesignerComponentInput';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export abstract class TableChangesActionBase extends Action {
|
||||
protected _input: TableDesignerComponentInput;
|
||||
private _onStateChangeDisposable: IDisposable;
|
||||
const PublishChangesLabel = localize('tableDesigner.publishTableChanges', "Publish Changes...");
|
||||
const SaveChangesLabel = localize('tableDesigner.saveTableChanges', "Save");
|
||||
|
||||
constructor(id: string, label: string, iconClassNames: string) {
|
||||
super(id, label, iconClassNames);
|
||||
export class SaveTableChangesAction extends Action {
|
||||
public static ID = 'tableDesigner.publishTableChanges';
|
||||
protected _input: TableDesignerComponentInput;
|
||||
protected _inputDisposableStore: DisposableStore;
|
||||
|
||||
constructor() {
|
||||
super(SaveTableChangesAction.ID);
|
||||
this._inputDisposableStore = new DisposableStore();
|
||||
}
|
||||
|
||||
public setContext(input: TableDesignerComponentInput): void {
|
||||
this._input = input;
|
||||
this.updateState();
|
||||
this._onStateChangeDisposable?.dispose();
|
||||
this._onStateChangeDisposable = input.onStateChange((e) => {
|
||||
this.updateLabelAndIcon();
|
||||
this._inputDisposableStore?.dispose();
|
||||
this._inputDisposableStore = new DisposableStore();
|
||||
this._inputDisposableStore.add(input.onStateChange((e) => {
|
||||
this.updateState();
|
||||
});
|
||||
}));
|
||||
this._inputDisposableStore.add(input.onInitialized(() => {
|
||||
this.updateLabelAndIcon();
|
||||
}));
|
||||
}
|
||||
|
||||
private updateState(): void {
|
||||
this.enabled = this._input.dirty && this._input.valid && this._input.pendingAction === undefined;
|
||||
}
|
||||
|
||||
private updateLabelAndIcon(): void {
|
||||
if (this._input?.tableDesignerView?.useAdvancedSaveMode) {
|
||||
this.label = PublishChangesLabel;
|
||||
this.class = Codicon.repoPush.classNames;
|
||||
} else {
|
||||
this.label = SaveChangesLabel;
|
||||
this.class = Codicon.save.classNames;
|
||||
}
|
||||
}
|
||||
|
||||
public override async run(): Promise<void> {
|
||||
await this._input.save();
|
||||
}
|
||||
|
||||
override dispose() {
|
||||
super.dispose();
|
||||
this._onStateChangeDisposable?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class PublishTableChangesAction extends TableChangesActionBase {
|
||||
public static ID = 'tableDesigner.publishTableChanges';
|
||||
public static LABEL = localize('tableDesigner.publishTableChanges', "Publish Changes...");
|
||||
constructor() {
|
||||
super(PublishTableChangesAction.ID, PublishTableChangesAction.LABEL, Codicon.repoPush.classNames);
|
||||
}
|
||||
|
||||
public override async run(): Promise<void> {
|
||||
await this._input.openPublishDialog();
|
||||
}
|
||||
}
|
||||
|
||||
export class GenerateTableChangeScriptAction extends TableChangesActionBase {
|
||||
public static ID = 'tableDesigner.generateScript';
|
||||
public static LABEL = localize('tableDesigner.generateScript', "Generate Script");
|
||||
|
||||
constructor() {
|
||||
super(GenerateTableChangeScriptAction.ID, GenerateTableChangeScriptAction.LABEL, Codicon.output.classNames);
|
||||
}
|
||||
|
||||
public override async run(): Promise<void> {
|
||||
await this._input.generateScript();
|
||||
this._inputDisposableStore?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
|
||||
import { IEditorOpenContext } from 'vs/workbench/common/editor';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { PublishTableChangesAction } from 'sql/workbench/contrib/tableDesigner/browser/actions';
|
||||
import { SaveTableChangesAction } from 'sql/workbench/contrib/tableDesigner/browser/actions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IColorTheme, ICssStyleCollector, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { DesignerPaneSeparator } from 'sql/platform/theme/common/colorRegistry';
|
||||
@@ -26,7 +26,7 @@ export class TableDesignerEditor extends EditorPane {
|
||||
public static readonly ID: string = 'workbench.editor.tableDesigner';
|
||||
|
||||
private _designer: Designer;
|
||||
private _publishChangesAction: PublishTableChangesAction;
|
||||
private _saveChangesAction: SaveTableChangesAction;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@@ -45,7 +45,7 @@ export class TableDesignerEditor extends EditorPane {
|
||||
await super.setInput(input, options, context, token);
|
||||
const designerInput = input.getComponentInput();
|
||||
this._designer.setInput(designerInput);
|
||||
this._publishChangesAction.setContext(designerInput);
|
||||
this._saveChangesAction.setContext(designerInput);
|
||||
}
|
||||
|
||||
protected createEditor(parent: HTMLElement): void {
|
||||
@@ -58,9 +58,9 @@ export class TableDesignerEditor extends EditorPane {
|
||||
const designerContainer = container.appendChild(DOM.$('.designer-container'));
|
||||
const actionbar = new ActionBar(actionbarContainer);
|
||||
this._register(actionbar);
|
||||
this._publishChangesAction = this._instantiationService.createInstance(PublishTableChangesAction);
|
||||
this._publishChangesAction.enabled = false;
|
||||
actionbar.push([this._publishChangesAction], { icon: true, label: false });
|
||||
this._saveChangesAction = this._instantiationService.createInstance(SaveTableChangesAction);
|
||||
this._saveChangesAction.enabled = false;
|
||||
actionbar.push([this._saveChangesAction], { icon: true, label: false });
|
||||
|
||||
this._designer = this._instantiationService.createInstance(Designer, designerContainer);
|
||||
this._register(attachDesignerStyler(this._designer, this.themeService));
|
||||
|
||||
@@ -33,6 +33,7 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
private _onEditProcessed = new Emitter<DesignerEditProcessedEventArgs>();
|
||||
private _onRefreshRequested = new Emitter<void>();
|
||||
private _originalViewModel: DesignerViewModel;
|
||||
private _tableDesignerView: azdata.designers.TableDesignerView;
|
||||
|
||||
public readonly onInitialized: Event<void> = this._onInitialized.event;
|
||||
public readonly onEditProcessed: Event<DesignerEditProcessedEventArgs> = this._onEditProcessed.event;
|
||||
@@ -81,6 +82,10 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
return this._issues;
|
||||
}
|
||||
|
||||
get tableDesignerView(): azdata.designers.TableDesignerView {
|
||||
return this._tableDesignerView;
|
||||
}
|
||||
|
||||
processEdit(edit: DesignerEdit): void {
|
||||
const telemetryInfo = this.createTelemetryInfo();
|
||||
telemetryInfo.tableObjectType = this.getObjectTypeFromPath(edit.path);
|
||||
@@ -177,6 +182,14 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
}
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
if (this.tableDesignerView?.useAdvancedSaveMode) {
|
||||
await this.openPublishDialog();
|
||||
} else {
|
||||
await this.publishChanges();
|
||||
}
|
||||
}
|
||||
|
||||
async openPublishDialog(): Promise<void> {
|
||||
const reportNotificationHandle = this._notificationService.notify({
|
||||
severity: Severity.Info,
|
||||
@@ -243,24 +256,28 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
}
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
async initialize(): Promise<void> {
|
||||
if (this._view !== undefined || this.pendingAction === 'initialize') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateState(this.valid, this.dirty, 'initialize');
|
||||
this._provider.initializeTableDesigner(this.tableInfo).then(result => {
|
||||
try {
|
||||
const result = await this._provider.initializeTableDesigner(this.tableInfo);
|
||||
this.doInitialization(result);
|
||||
this._onInitialized.fire();
|
||||
}, error => {
|
||||
} catch (error) {
|
||||
this._errorMessageService.showDialog(Severity.Error, ErrorDialogTitle, localize('tableDesigner.errorInitializingTableDesigner', "An error occurred while initializing the table designer: {0}", error?.message ?? error));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private doInitialization(designerInfo: azdata.designers.TableDesignerInfo): void {
|
||||
this.tableInfo = designerInfo.tableInfo;
|
||||
this.updateState(true, this.tableInfo.isNewTable);
|
||||
this._viewModel = designerInfo.viewModel;
|
||||
this._originalViewModel = this.tableInfo.isNewTable ? undefined : deepClone(this._viewModel);
|
||||
this._tableDesignerView = designerInfo.view;
|
||||
this._issues = designerInfo.issues;
|
||||
this.setDesignerView(designerInfo.view);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user