Adding Move, Exclude, and Rename support for folders (#22867)

* Adding exclude folder and base for move folder

* checkpoint

* rename

* Fixing up tests

* Adding exclude test to projController

* Adding tests

* fixing order of service.moveX() calls

* Updating move() order in sqlproj service

* PR feedback

* unskipping

* reskipping test

* Fixing folder move conditional

* updating comments
This commit is contained in:
Benjin Dubishar
2023-04-28 16:05:38 -07:00
committed by GitHub
parent 934d8ff8fa
commit 29ff6ca16c
9 changed files with 175 additions and 161 deletions

View File

@@ -396,10 +396,10 @@ declare module 'mssql' {
/** /**
* Move a folder and its contents within a project * Move a folder and its contents within a project
* @param projectUri Absolute path of the project, including .sqlproj * @param projectUri Absolute path of the project, including .sqlproj
* @param destinationPath Path of the folder, typically relative to the .sqlproj file * @param sourcePath Source path of the folder, typically relative to the .sqlproj file
* @param path Path of the folder, typically relative to the .sqlproj file * @param destinationPath Destination path of the folder, typically relative to the .sqlproj file
*/ */
moveFolder(projectUri: string, destinationPath: string, path: string): Promise<azdata.ResultStatus>; moveFolder(projectUri: string, sourcePath: string, destinationPath: string): Promise<azdata.ResultStatus>;
/** /**
* Add a post-deployment script to a project * Add a post-deployment script to a project
@@ -446,18 +446,18 @@ declare module 'mssql' {
/** /**
* Move a post-deployment script in a project * Move a post-deployment script in a project
* @param projectUri Absolute path of the project, including .sqlproj * @param projectUri Absolute path of the project, including .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
* @param path Path of the script, including .sql, relative to the .sqlproj * @param path Path of the script, including .sql, relative to the .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
*/ */
movePostDeploymentScript(projectUri: string, destinationPath: string, path: string): Promise<azdata.ResultStatus>; movePostDeploymentScript(projectUri: string, path: string, destinationPath: string): Promise<azdata.ResultStatus>;
/** /**
* Move a pre-deployment script in a project * Move a pre-deployment script in a project
* @param projectUri Absolute path of the project, including .sqlproj * @param projectUri Absolute path of the project, including .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
* @param path Path of the script, including .sql, relative to the .sqlproj * @param path Path of the script, including .sql, relative to the .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
*/ */
movePreDeploymentScript(projectUri: string, destinationPath: string, path: string): Promise<azdata.ResultStatus>; movePreDeploymentScript(projectUri: string, path: string, destinationPath: string): Promise<azdata.ResultStatus>;
/** /**
* Close a SQL project * Close a SQL project
@@ -561,10 +561,10 @@ declare module 'mssql' {
/** /**
* Move a SQL object script in a project * Move a SQL object script in a project
* @param projectUri Absolute path of the project, including .sqlproj * @param projectUri Absolute path of the project, including .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
* @param path Path of the script, including .sql, relative to the .sqlproj * @param path Path of the script, including .sql, relative to the .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
*/ */
moveSqlObjectScript(projectUri: string, destinationPath: string, path: string): Promise<azdata.ResultStatus>; moveSqlObjectScript(projectUri: string, path: string, destinationPath: string): Promise<azdata.ResultStatus>;
/** /**
* Get all the database references in a project * Get all the database references in a project
@@ -632,10 +632,10 @@ declare module 'mssql' {
/** /**
* Move a None item in a project * Move a None item in a project
* @param projectUri Absolute path of the project, including .sqlproj * @param projectUri Absolute path of the project, including .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
* @param path Path of the item, including extension, relative to the .sqlproj * @param path Path of the item, including extension, relative to the .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
*/ */
moveNoneItem(projectUri: string, destinationPath: string, path: string): Promise<azdata.ResultStatus>; moveNoneItem(projectUri: string, path: string, destinationPath: string): Promise<azdata.ResultStatus>;
} }

View File

@@ -185,10 +185,10 @@ export class SqlProjectsService extends BaseService implements mssql.ISqlProject
/** /**
* Move a post-deployment script in a project * Move a post-deployment script in a project
* @param projectUri Absolute path of the project, including .sqlproj * @param projectUri Absolute path of the project, including .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
* @param path Path of the script, including .sql, relative to the .sqlproj * @param path Path of the script, including .sql, relative to the .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
*/ */
public async movePostDeploymentScript(projectUri: string, destinationPath: string, path: string): Promise<azdata.ResultStatus> { public async movePostDeploymentScript(projectUri: string, path: string, destinationPath: string): Promise<azdata.ResultStatus> {
const params: contracts.MoveItemParams = { projectUri: projectUri, destinationPath: destinationPath, path: path }; const params: contracts.MoveItemParams = { projectUri: projectUri, destinationPath: destinationPath, path: path };
return await this.runWithErrorHandling(contracts.MovePostDeploymentScriptRequest.type, params); return await this.runWithErrorHandling(contracts.MovePostDeploymentScriptRequest.type, params);
} }
@@ -196,10 +196,10 @@ export class SqlProjectsService extends BaseService implements mssql.ISqlProject
/** /**
* Move a pre-deployment script in a project * Move a pre-deployment script in a project
* @param projectUri Absolute path of the project, including .sqlproj * @param projectUri Absolute path of the project, including .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
* @param path Path of the script, including .sql, relative to the .sqlproj * @param path Path of the script, including .sql, relative to the .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
*/ */
public async movePreDeploymentScript(projectUri: string, destinationPath: string, path: string): Promise<azdata.ResultStatus> { public async movePreDeploymentScript(projectUri: string, path: string, destinationPath: string): Promise<azdata.ResultStatus> {
const params: contracts.MoveItemParams = { projectUri: projectUri, destinationPath: destinationPath, path: path }; const params: contracts.MoveItemParams = { projectUri: projectUri, destinationPath: destinationPath, path: path };
return await this.runWithErrorHandling(contracts.MovePreDeploymentScriptRequest.type, params); return await this.runWithErrorHandling(contracts.MovePreDeploymentScriptRequest.type, params);
} }
@@ -350,10 +350,10 @@ export class SqlProjectsService extends BaseService implements mssql.ISqlProject
/** /**
* Move a SQL object script in a project * Move a SQL object script in a project
* @param projectUri Absolute path of the project, including .sqlproj * @param projectUri Absolute path of the project, including .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
* @param path Path of the script, including .sql, relative to the .sqlproj * @param path Path of the script, including .sql, relative to the .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
*/ */
public async moveSqlObjectScript(projectUri: string, destinationPath: string, path: string): Promise<azdata.ResultStatus> { public async moveSqlObjectScript(projectUri: string, path: string, destinationPath: string): Promise<azdata.ResultStatus> {
const params: contracts.MoveItemParams = { projectUri: projectUri, destinationPath: destinationPath, path: path }; const params: contracts.MoveItemParams = { projectUri: projectUri, destinationPath: destinationPath, path: path };
return await this.runWithErrorHandling(contracts.MoveSqlObjectScriptRequest.type, params); return await this.runWithErrorHandling(contracts.MoveSqlObjectScriptRequest.type, params);
} }
@@ -454,10 +454,10 @@ export class SqlProjectsService extends BaseService implements mssql.ISqlProject
/** /**
* Move a SQL object script in a project * Move a SQL object script in a project
* @param projectUri Absolute path of the project, including .sqlproj * @param projectUri Absolute path of the project, including .sqlproj
* @param path Path of the script, including .sql, relative to the .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj * @param destinationPath Destination path of the file or folder, relative to the .sqlproj
* @param path Path of the file, including extension, relative to the .sqlproj
*/ */
public async moveNoneItem(projectUri: string, destinationPath: string, path: string): Promise<azdata.ResultStatus> { public async moveNoneItem(projectUri: string, path: string, destinationPath: string): Promise<azdata.ResultStatus> {
const params: contracts.MoveItemParams = { projectUri: projectUri, destinationPath: destinationPath, path: path }; const params: contracts.MoveItemParams = { projectUri: projectUri, destinationPath: destinationPath, path: path };
return await this.runWithErrorHandling(contracts.MoveNoneItemRequest.type, params); return await this.runWithErrorHandling(contracts.MoveNoneItemRequest.type, params);
} }
@@ -475,11 +475,11 @@ export class SqlProjectsService extends BaseService implements mssql.ISqlProject
/** /**
* Move a folder and its contents within a project * Move a folder and its contents within a project
* @param projectUri Absolute path of the project, including .sqlproj * @param projectUri Absolute path of the project, including .sqlproj
* @param destinationPath Path of the folder, typically relative to the .sqlproj file * @param sourcePath Source path of the folder, typically relative to the .sqlproj file
* @param path Path of the folder, typically relative to the .sqlproj file * @param destinationPath Destination path of the folder, typically relative to the .sqlproj file
*/ */
public async moveFolder(projectUri: string, destinationPath: string, path: string): Promise<azdata.ResultStatus> { public async moveFolder(projectUri: string, sourcePath: string, destinationPath: string): Promise<azdata.ResultStatus> {
const params: contracts.MoveFolderParams = { projectUri: projectUri, destinationPath: destinationPath, path: path }; const params: contracts.MoveFolderParams = { projectUri: projectUri, path: sourcePath, destinationPath: destinationPath };
return await this.runWithErrorHandling(contracts.MoveFolderRequest.type, params); return await this.runWithErrorHandling(contracts.MoveFolderRequest.type, params);
} }
} }

View File

@@ -419,7 +419,7 @@
}, },
{ {
"command": "sqlDatabaseProjects.exclude", "command": "sqlDatabaseProjects.exclude",
"when": "view == dataworkspace.views.main && viewItem =~ /^databaseProject.itemType.file/", "when": "view == dataworkspace.views.main && viewItem == databaseProject.itemType.folder || viewItem =~ /^databaseProject.itemType.file/",
"group": "9_dbProjectsLast@1" "group": "9_dbProjectsLast@1"
}, },
{ {
@@ -429,7 +429,7 @@
}, },
{ {
"command": "sqlDatabaseProjects.rename", "command": "sqlDatabaseProjects.rename",
"when": "view == dataworkspace.views.main && viewItem =~ /^databaseProject.itemType.file/", "when": "view == dataworkspace.views.main && viewItem == databaseProject.itemType.folder || viewItem =~ /^databaseProject.itemType.file/",
"group": "9_dbProjectsLast@3" "group": "9_dbProjectsLast@3"
}, },
{ {

View File

@@ -679,7 +679,7 @@ export function errorExtracting(path: string, error: string) { return localize('
//#endregion //#endregion
//#region move //#region move
export const onlyMoveSqlFilesSupported = localize('onlyMoveSqlFilesSupported', "Only moving .sql files is supported"); export const onlyMoveFilesFoldersSupported = localize('onlyMoveFilesFoldersSupported', "Only moving files and folders are supported");
export const movingFilesBetweenProjectsNotSupported = localize('movingFilesBetweenProjectsNotSupported', "Moving files between projects is not supported"); export const movingFilesBetweenProjectsNotSupported = localize('movingFilesBetweenProjectsNotSupported', "Moving files between projects is not supported");
export function errorMovingFile(source: string, destination: string, error: string) { return localize('errorMovingFile', "Error when moving file from {0} to {1}. Error: {2}", source, destination, error); } export function errorMovingFile(source: string, destination: string, error: string) { return localize('errorMovingFile', "Error when moving file from {0} to {1}. Error: {2}", source, destination, error); }
export function moveConfirmationPrompt(source: string, destination: string) { return localize('moveConfirmationPrompt', "Are you sure you want to move {0} to {1}?", source, destination); } export function moveConfirmationPrompt(source: string, destination: string) { return localize('moveConfirmationPrompt', "Are you sure you want to move {0} to {1}?", source, destination); }

View File

@@ -797,31 +797,27 @@ export class ProjectsController {
const node = context.element as BaseProjectTreeItem; const node = context.element as BaseProjectTreeItem;
const project = await this.getProjectFromContext(node); const project = await this.getProjectFromContext(node);
const fileEntry = this.getFileProjectEntry(project, node); if (node.entryKey) {
if (fileEntry) {
TelemetryReporter.sendActionEvent(TelemetryViews.ProjectTree, TelemetryActions.excludeFromProject); TelemetryReporter.sendActionEvent(TelemetryViews.ProjectTree, TelemetryActions.excludeFromProject);
switch (node.type) { switch (node.type) {
case constants.DatabaseProjectItemType.sqlObjectScript: case constants.DatabaseProjectItemType.sqlObjectScript:
case constants.DatabaseProjectItemType.table: case constants.DatabaseProjectItemType.table:
case constants.DatabaseProjectItemType.externalStreamingJob: case constants.DatabaseProjectItemType.externalStreamingJob:
await project.excludeSqlObjectScript(fileEntry.relativePath); await project.excludeSqlObjectScript(node.entryKey);
break; break;
case constants.DatabaseProjectItemType.folder: case constants.DatabaseProjectItemType.folder:
// TODO: not yet supported in DacFx await project.excludeFolder(node.entryKey);
//await project.excludeFolder(fileEntry.relativePath);
void vscode.window.showErrorMessage(constants.excludeFolderNotSupported);
break; break;
case constants.DatabaseProjectItemType.preDeploymentScript: case constants.DatabaseProjectItemType.preDeploymentScript:
await project.excludePreDeploymentScript(fileEntry.relativePath); await project.excludePreDeploymentScript(node.entryKey);
break; break;
case constants.DatabaseProjectItemType.postDeploymentScript: case constants.DatabaseProjectItemType.postDeploymentScript:
await project.excludePostDeploymentScript(fileEntry.relativePath); await project.excludePostDeploymentScript(node.entryKey);
break; break;
case constants.DatabaseProjectItemType.noneFile: case constants.DatabaseProjectItemType.noneFile:
case constants.DatabaseProjectItemType.publishProfile: case constants.DatabaseProjectItemType.publishProfile:
await project.excludeNoneItem(fileEntry.relativePath); await project.excludeNoneItem(node.entryKey);
break; break;
default: default:
throw new Error(constants.unhandledExcludeType(node.type)); throw new Error(constants.unhandledExcludeType(node.type));
@@ -904,25 +900,21 @@ export class ProjectsController {
public async rename(context: dataworkspace.WorkspaceTreeItem): Promise<void> { public async rename(context: dataworkspace.WorkspaceTreeItem): Promise<void> {
const node = context.element as BaseProjectTreeItem; const node = context.element as BaseProjectTreeItem;
const project = await this.getProjectFromContext(node); const project = await this.getProjectFromContext(node);
const file = this.getFileProjectEntry(project, node);
const baseName = path.basename(node.friendlyName);
let fileExtension: string;
if (utils.isPublishProfile(baseName)) { const originalAbsolutePath = utils.getPlatformSafeFileEntryPath(node.projectFileUri.fsPath);
fileExtension = constants.publishProfileExtension; const originalName = path.basename(node.friendlyName);
} else { const originalExt = path.extname(node.friendlyName);
fileExtension = constants.sqlFileExtension;
}
// need to use quickpick because input box isn't supported in treeviews // need to use quickpick because input box isn't supported in treeviews
// https://github.com/microsoft/vscode/issues/117502 and https://github.com/microsoft/vscode/issues/97190 // https://github.com/microsoft/vscode/issues/117502 and https://github.com/microsoft/vscode/issues/97190
const newFileName = await vscode.window.showInputBox( const newFileName = await vscode.window.showInputBox(
{ {
title: constants.enterNewName, title: constants.enterNewName,
value: path.basename(baseName, fileExtension), value: originalName,
valueSelection: [0, path.basename(originalName, originalExt).length],
ignoreFocusOut: true, ignoreFocusOut: true,
validateInput: async (value) => { validateInput: async (newName) => {
return await this.fileAlreadyExists(value, file?.fsUri.fsPath!) ? constants.fileAlreadyExists(value) : undefined; return await this.fileAlreadyExists(newName, originalAbsolutePath) ? constants.fileAlreadyExists(newName) : undefined;
} }
}); });
@@ -930,22 +922,21 @@ export class ProjectsController {
return; return;
} }
const newFilePath = path.join(path.dirname(utils.getPlatformSafeFileEntryPath(file?.relativePath!)), `${newFileName}${fileExtension}`); const newFilePath = path.join(path.dirname(utils.getPlatformSafeFileEntryPath(node.relativeProjectUri.fsPath!)), newFileName);
const result = await project.move(node, newFilePath);
const renameResult = await project.move(node, newFilePath); if (result?.success) {
if (renameResult?.success) {
TelemetryReporter.sendActionEvent(TelemetryViews.ProjectTree, TelemetryActions.rename); TelemetryReporter.sendActionEvent(TelemetryViews.ProjectTree, TelemetryActions.rename);
} else { } else {
TelemetryReporter.sendErrorEvent2(TelemetryViews.ProjectTree, TelemetryActions.rename); TelemetryReporter.sendErrorEvent2(TelemetryViews.ProjectTree, TelemetryActions.rename);
void vscode.window.showErrorMessage(constants.errorRenamingFile(file?.relativePath!, newFilePath, utils.getErrorMessage(renameResult?.errorMessage))); void vscode.window.showErrorMessage(constants.errorRenamingFile(node.entryKey!, newFilePath, result?.errorMessage));
} }
this.refreshProjectsTree(context); this.refreshProjectsTree(context);
} }
private fileAlreadyExists(newFileName: string, previousFilePath: string): Promise<boolean> { private fileAlreadyExists(newFileName: string, previousFilePath: string): Promise<boolean> {
return utils.exists(path.join(path.dirname(previousFilePath), `${newFileName}.sql`)); return utils.exists(path.join(path.dirname(previousFilePath), newFileName));
} }
/** /**
@@ -1488,21 +1479,6 @@ export class ProjectsController {
//#region Helper methods //#region Helper methods
private getFileProjectEntry(project: Project, context: BaseProjectTreeItem): FileProjectEntry | undefined {
const fileOrFolder = context as FileNode ? context as FileNode : context as FolderNode;
if (fileOrFolder) {
// use relative path and not tree paths for files and folder
const allFileEntries = project.files.concat(project.preDeployScripts).concat(project.postDeployScripts).concat(project.noneDeployScripts).concat(project.publishProfiles);
// trim trailing slash since folders with and without a trailing slash are allowed in a sqlproj
const trimmedUri = utils.trimChars(utils.getPlatformSafeFileEntryPath(utils.trimUri(fileOrFolder.projectFileUri, fileOrFolder.fileSystemUri)), '/');
return allFileEntries.find(x => utils.trimChars(utils.getPlatformSafeFileEntryPath(x.relativePath), '/') === trimmedUri);
}
const projectRelativeUri = vscode.Uri.file(path.basename(context.projectFileUri.fsPath, constants.sqlprojExtension));
return project.files.find(x => utils.getPlatformSafeFileEntryPath(x.relativePath) === utils.getPlatformSafeFileEntryPath(utils.trimUri(projectRelativeUri, context.relativeProjectUri)));
}
private async getProjectFromContext(context: Project | BaseProjectTreeItem | dataworkspace.WorkspaceTreeItem): Promise<Project> { private async getProjectFromContext(context: Project | BaseProjectTreeItem | dataworkspace.WorkspaceTreeItem): Promise<Project> {
if ('element' in context) { if ('element' in context) {
context = context.element; context = context.element;
@@ -1881,23 +1857,22 @@ export class ProjectsController {
//#endregion //#endregion
/** /**
* Move a file in the project tree * Move a file or folder in the project tree
* @param projectUri URI of the project * @param projectUri URI of the project
* @param source * @param source
* @param target * @param target
*/ */
public async moveFile(projectUri: vscode.Uri, source: any, target: dataworkspace.WorkspaceTreeItem): Promise<void> { public async moveFile(projectUri: vscode.Uri, source: any, target: dataworkspace.WorkspaceTreeItem): Promise<void> {
const sourceFileNode = source as FileNode; const sourceFileNode = source as FileNode | FolderNode;
const project = await this.getProjectFromContext(sourceFileNode); const project = await this.getProjectFromContext(sourceFileNode);
// only moving files is supported // only moving files and folders are supported
if (!sourceFileNode || !(sourceFileNode instanceof FileNode)) { if (!sourceFileNode || !(sourceFileNode instanceof FileNode || sourceFileNode instanceof FolderNode)) {
void vscode.window.showErrorMessage(constants.onlyMoveSqlFilesSupported); void vscode.window.showErrorMessage(constants.onlyMoveFilesFoldersSupported);
return; return;
} }
// Moving files to the SQLCMD variables and Database references folders isn't allowed // Moving files/folders to the SQLCMD variables and Database references folders isn't allowed
// TODO: should there be an error displayed if a file attempting to move a file to sqlcmd variables or database references? Or just silently fail and do nothing?
if (!target.element.fileSystemUri) { if (!target.element.fileSystemUri) {
return; return;
} }
@@ -1930,7 +1905,7 @@ export class ProjectsController {
return; return;
} }
// Move the file // Move the file/folder
const moveResult = await project.move(sourceFileNode, newPath); const moveResult = await project.move(sourceFileNode, newPath);
if (moveResult?.success) { if (moveResult?.success) {

View File

@@ -20,7 +20,7 @@ import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/t
import { DacpacReferenceProjectEntry, FileProjectEntry, NugetPackageReferenceProjectEntry, SqlProjectReferenceProjectEntry, SystemDatabaseReferenceProjectEntry } from './projectEntry'; import { DacpacReferenceProjectEntry, FileProjectEntry, NugetPackageReferenceProjectEntry, SqlProjectReferenceProjectEntry, SystemDatabaseReferenceProjectEntry } from './projectEntry';
import { ResultStatus } from 'azdata'; import { ResultStatus } from 'azdata';
import { BaseProjectTreeItem } from './tree/baseTreeItem'; import { BaseProjectTreeItem } from './tree/baseTreeItem';
import { NoneNode, PostDeployNode, PreDeployNode, PublishProfileNode, SqlObjectFileNode } from './tree/fileFolderTreeItem'; import { FolderNode, NoneNode, PostDeployNode, PreDeployNode, PublishProfileNode, SqlObjectFileNode } from './tree/fileFolderTreeItem';
import { ProjectType, GetScriptsResult, GetFoldersResult } from '../common/typeHelper'; import { ProjectType, GetScriptsResult, GetFoldersResult } from '../common/typeHelper';
@@ -531,6 +531,9 @@ export class Project implements ISqlProject {
const result = await this.sqlProjService.addFolder(this.projectFilePath, relativeFolderPath); const result = await this.sqlProjService.addFolder(this.projectFilePath, relativeFolderPath);
this.throwIfFailed(result); this.throwIfFailed(result);
// Note: adding a folder does not mean adding the contents of the folder.
// SDK projects may still need to adjust their include/exclude globs, and Legacy projects must still include each file
// in order for the contents of the folders to be added.
await this.readFolders(); await this.readFolders();
} }
@@ -538,6 +541,32 @@ export class Project implements ISqlProject {
const result = await this.sqlProjService.deleteFolder(this.projectFilePath, relativeFolderPath); const result = await this.sqlProjService.deleteFolder(this.projectFilePath, relativeFolderPath);
this.throwIfFailed(result); this.throwIfFailed(result);
await this.readFilesInProject();
await this.readPreDeployScripts();
await this.readPostDeployScripts();
await this.readNoneItems();
await this.readFolders();
}
public async excludeFolder(relativeFolderPath: string): Promise<void> {
const result = await this.sqlProjService.excludeFolder(this.projectFilePath, relativeFolderPath);
this.throwIfFailed(result);
await this.readFilesInProject();
await this.readPreDeployScripts();
await this.readPostDeployScripts();
await this.readNoneItems();
await this.readFolders();
}
public async moveFolder(relativeSourcePath: string, relativeDestinationPath: string): Promise<void> {
const result = await this.sqlProjService.moveFolder(this.projectFilePath, relativeSourcePath, relativeDestinationPath);
this.throwIfFailed(result);
await this.readFilesInProject();
await this.readPreDeployScripts();
await this.readPostDeployScripts();
await this.readNoneItems();
await this.readFolders(); await this.readFolders();
} }
@@ -861,9 +890,8 @@ export class Project implements ISqlProject {
} }
const databaseLiteral = settings.databaseVariable ? undefined : settings.databaseName; const databaseLiteral = settings.databaseVariable ? undefined : settings.databaseName;
let result, referenceName;
let result;
let referenceName;
if (reference instanceof SqlProjectReferenceProjectEntry) { if (reference instanceof SqlProjectReferenceProjectEntry) {
referenceName = (<IProjectReferenceSettings>settings).projectName; referenceName = (<IProjectReferenceSettings>settings).projectName;
result = await this.sqlProjService.addSqlProjectReference(this.projectFilePath, reference.pathForSqlProj(), reference.projectGuid, settings.suppressMissingDependenciesErrors, settings.databaseVariable, settings.serverVariable, databaseLiteral) result = await this.sqlProjService.addSqlProjectReference(this.projectFilePath, reference.pathForSqlProj(), reference.projectGuid, settings.suppressMissingDependenciesErrors, settings.databaseVariable, settings.serverVariable, databaseLiteral)
@@ -1026,13 +1054,15 @@ export class Project implements ISqlProject {
let result; let result;
if (node instanceof SqlObjectFileNode) { if (node instanceof SqlObjectFileNode) {
result = await this.sqlProjService.moveSqlObjectScript(this.projectFilePath, destinationRelativePath, originalRelativePath) result = await this.sqlProjService.moveSqlObjectScript(this.projectFilePath, originalRelativePath, destinationRelativePath)
} else if (node instanceof PreDeployNode) { } else if (node instanceof PreDeployNode) {
result = await this.sqlProjService.movePreDeploymentScript(this.projectFilePath, destinationRelativePath, originalRelativePath) result = await this.sqlProjService.movePreDeploymentScript(this.projectFilePath, originalRelativePath, destinationRelativePath)
} else if (node instanceof PostDeployNode) { } else if (node instanceof PostDeployNode) {
result = await this.sqlProjService.movePostDeploymentScript(this.projectFilePath, destinationRelativePath, originalRelativePath) result = await this.sqlProjService.movePostDeploymentScript(this.projectFilePath, originalRelativePath, destinationRelativePath)
} else if (node instanceof NoneNode || node instanceof PublishProfileNode) { } else if (node instanceof NoneNode || node instanceof PublishProfileNode) {
result = await this.sqlProjService.moveNoneItem(this.projectFilePath, destinationRelativePath, originalRelativePath); result = await this.sqlProjService.moveNoneItem(this.projectFilePath, originalRelativePath, destinationRelativePath);
} else if (node instanceof FolderNode) {
result = await this.sqlProjService.moveFolder(this.projectFilePath, originalRelativePath, destinationRelativePath);
} else { } else {
result = { success: false, errorMessage: constants.unhandledMoveNode } result = { success: false, errorMessage: constants.unhandledMoveNode }
} }

View File

@@ -349,8 +349,7 @@ describe('Project: sdk style project content operations', function (): void {
should(project.files.length).equal(0, 'There should not be any SQL object scripts after the excludes'); should(project.files.length).equal(0, 'There should not be any SQL object scripts after the excludes');
}); });
// skipped because exclude folder not yet supported it('Should handle excluding glob included folders', async function (): Promise<void> {
it.skip('Should handle excluding glob included folders', async function (): Promise<void> {
const testFolderPath = await testUtils.generateTestFolderPath(this.test); const testFolderPath = await testUtils.generateTestFolderPath(this.test);
const projFilePath = await testUtils.createTestSqlProjFile(this.test, baselines.openSdkStyleSqlProjectBaseline, testFolderPath); const projFilePath = await testUtils.createTestSqlProjFile(this.test, baselines.openSdkStyleSqlProjectBaseline, testFolderPath);
await testUtils.createDummyFileStructureWithPrePostDeployScripts(this.test, false, undefined, path.dirname(projFilePath)); await testUtils.createDummyFileStructureWithPrePostDeployScripts(this.test, false, undefined, path.dirname(projFilePath));
@@ -362,23 +361,16 @@ describe('Project: sdk style project content operations', function (): void {
should(project.noneDeployScripts.length).equal(2); should(project.noneDeployScripts.length).equal(2);
// try to exclude a glob included folder // try to exclude a glob included folder
//await project.excludeFolder('folder1\\'); await project.excludeFolder('folder1');
// verify folder and contents are excluded // verify folder and contents are excluded
should(project.folders.length).equal(1); should(project.folders.length).equal(1);
should(project.files.length).equal(6); should(project.files.length).equal(6);
should(project.noneDeployScripts.length).equal(1, 'Script.PostDeployment2.sql should have been excluded'); should(project.noneDeployScripts.length).equal(1, 'Script.PostDeployment2.sql should have been excluded');
should(project.files.find(f => f.relativePath === 'folder1\\')).equal(undefined); should(project.folders.find(f => f.relativePath === 'folder1')).equal(undefined);
// verify sqlproj has glob exclude for folder, but not for files and inner folder
const projFileText = (await fs.readFile(projFilePath)).toString();
should(projFileText.includes('<Build Remove="folder1\\**" />')).equal(true, projFileText);
should(projFileText.includes('<Build Remove="folder1\\file1.sql" />')).equal(false, projFileText);
should(projFileText.includes('<Build Remove="folder1\\nestedFolder\\**" />')).equal(false, projFileText);
}); });
// skipped because exclude folder not yet supported it('Should handle excluding folders', async function (): Promise<void> {
it.skip('Should handle excluding nested glob included folders', async function (): Promise<void> {
const testFolderPath = await testUtils.generateTestFolderPath(this.test,); const testFolderPath = await testUtils.generateTestFolderPath(this.test,);
const projFilePath = await testUtils.createTestSqlProjFile(this.test, baselines.openSdkStyleSqlProjectBaseline, testFolderPath); const projFilePath = await testUtils.createTestSqlProjFile(this.test, baselines.openSdkStyleSqlProjectBaseline, testFolderPath);
await testUtils.createDummyFileStructureWithPrePostDeployScripts(this.test, false, undefined, path.dirname(projFilePath)); await testUtils.createDummyFileStructureWithPrePostDeployScripts(this.test, false, undefined, path.dirname(projFilePath));
@@ -389,21 +381,16 @@ describe('Project: sdk style project content operations', function (): void {
should(project.folders.length).equal(3); should(project.folders.length).equal(3);
// try to exclude a glob included folder // try to exclude a glob included folder
//await project.excludeFolder('folder1\\nestedFolder\\'); await project.excludeFolder('folder1\\nestedFolder');
// verify folder and contents are excluded // verify folder and contents are excluded
should(project.folders.length).equal(2); should(project.folders.length).equal(2);
should(project.files.length).equal(11); should(project.files.length).equal(11);
should(project.files.find(f => f.relativePath === 'folder1\\nestedFolder\\')).equal(undefined); should(project.folders.find(f => f.relativePath === 'folder1\\nestedFolder')).equal(undefined);
// verify sqlproj has glob exclude for folder, but not for files
const projFileText = (await fs.readFile(projFilePath)).toString();
should(projFileText.includes('<Build Remove="folder1\\nestedFolder\\**" />')).equal(true, projFileText);
should(projFileText.includes('<Build Remove="folder1\\nestedFolder\\otherFile1.sql" />')).equal(false, projFileText);
}); });
// skipped because exclude folder not yet supported // skipped because exclude folder not yet supported
it.skip('Should handle excluding explicitly included folders', async function (): Promise<void> { it('Should handle excluding explicitly included folders', async function (): Promise<void> {
const testFolderPath = await testUtils.generateTestFolderPath(this.test,); const testFolderPath = await testUtils.generateTestFolderPath(this.test,);
const projFilePath = await testUtils.createTestSqlProjFile(this.test, baselines.openSdkStyleSqlProjectWithFilesSpecifiedBaseline, testFolderPath); const projFilePath = await testUtils.createTestSqlProjFile(this.test, baselines.openSdkStyleSqlProjectWithFilesSpecifiedBaseline, testFolderPath);
await testUtils.createDummyFileStructure(this.test, false, undefined, path.dirname(projFilePath)); await testUtils.createDummyFileStructure(this.test, false, undefined, path.dirname(projFilePath));
@@ -412,36 +399,27 @@ describe('Project: sdk style project content operations', function (): void {
should(project.files.length).equal(11); should(project.files.length).equal(11);
should(project.folders.length).equal(2); should(project.folders.length).equal(2);
should(project.files.find(f => f.relativePath === 'folder1\\')!).not.equal(undefined); should(project.folders.find(f => f.relativePath === 'folder1')!).not.equal(undefined);
should(project.files.find(f => f.relativePath === 'folder2\\')!).not.equal(undefined); should(project.folders.find(f => f.relativePath === 'folder2')!).not.equal(undefined);
// try to exclude an explicitly included folder without trailing \ in sqlproj // try to exclude an explicitly included folder without trailing \ in sqlproj
//await project.excludeFolder('folder1\\'); await project.excludeFolder('folder1');
// verify folder and contents are excluded // verify folder and contents are excluded
should(project.folders.length).equal(1); should(project.folders.length).equal(1);
should(project.files.length).equal(6); should(project.files.length).equal(6);
should(project.files.find(f => f.relativePath === 'folder1\\')).equal(undefined); should(project.folders.find(f => f.relativePath === 'folder1')).equal(undefined);
// try to exclude an explicitly included folder with trailing \ in sqlproj // try to exclude an explicitly included folder with trailing \ in sqlproj
//await project.excludeFolder('folder2\\'); await project.excludeFolder('folder2');
// verify folder and contents are excluded // verify folder and contents are excluded
should(project.folders.length).equal(0); should(project.folders.length).equal(0);
should(project.files.length).equal(1); should(project.files.length).equal(1);
should(project.files.find(f => f.relativePath === 'folder2\\')).equal(undefined); should(project.folders.find(f => f.relativePath === 'folder2')).equal(undefined);
// make sure both folders are removed from sqlproj and remove entry is added
const projFileText = (await fs.readFile(projFilePath)).toString();
should(projFileText.includes('<Folder Include="folder1" />')).equal(false, projFileText);
should(projFileText.includes('<Folder Include="folder2\\" />')).equal(false, projFileText);
should(projFileText.includes('<Build Remove="folder1\\**" />')).equal(true, projFileText);
should(projFileText.includes('<Build Remove="folder2\\**" />')).equal(true, projFileText);
}); });
// TODO: skipped until fix for folder trailing slashes comes in from DacFx it('Should handle deleting explicitly included folders', async function (): Promise<void> {
it.skip('Should handle deleting explicitly included folders', async function (): Promise<void> {
const testFolderPath = await testUtils.generateTestFolderPath(this.test,); const testFolderPath = await testUtils.generateTestFolderPath(this.test,);
const projFilePath = await testUtils.createTestSqlProjFile(this.test, baselines.openSdkStyleSqlProjectWithFilesSpecifiedBaseline, testFolderPath); const projFilePath = await testUtils.createTestSqlProjFile(this.test, baselines.openSdkStyleSqlProjectWithFilesSpecifiedBaseline, testFolderPath);
await testUtils.createDummyFileStructureWithPrePostDeployScripts(this.test, false, undefined, path.dirname(projFilePath)); await testUtils.createDummyFileStructureWithPrePostDeployScripts(this.test, false, undefined, path.dirname(projFilePath));
@@ -450,32 +428,24 @@ describe('Project: sdk style project content operations', function (): void {
should(project.files.length).equal(13); should(project.files.length).equal(13);
should(project.folders.length).equal(3); should(project.folders.length).equal(3);
should(project.files.find(f => f.relativePath === 'folder1\\')!).not.equal(undefined); should(project.folders.find(f => f.relativePath === 'folder1')!).not.equal(undefined);
should(project.files.find(f => f.relativePath === 'folder2\\')!).not.equal(undefined); should(project.folders.find(f => f.relativePath === 'folder2')!).not.equal(undefined);
// try to delete an explicitly included folder with the trailing \ in sqlproj // try to delete an explicitly included folder with the trailing \ in sqlproj
await project.deleteFolder('folder2\\'); await project.deleteFolder('folder2');
// verify the project not longer has folder2 and its contents // verify the project not longer has folder2 and its contents
should(project.folders.length).equal(2); should(project.folders.length).equal(2);
should(project.files.length).equal(8); should(project.files.length).equal(8);
should(project.files.find(f => f.relativePath === 'folder2\\')).equal(undefined); should(project.folders.find(f => f.relativePath === 'folder2')).equal(undefined);
// try to delete an explicitly included folder without trailing \ in sqlproj // try to delete an explicitly included folder without trailing \ in sqlproj
await project.deleteFolder('folder1\\'); await project.deleteFolder('folder1');
// verify the project not longer has folder1 and its contents // verify the project not longer has folder1 and its contents
should(project.folders.length).equal(0); should(project.folders.length).equal(0);
should(project.files.length).equal(1); should(project.files.length).equal(1);
should(project.files.find(f => f.relativePath === 'folder1\\')).equal(undefined); should(project.folders.find(f => f.relativePath === 'folder1')).equal(undefined);
// make sure both folders are removed from sqlproj and Build Remove entries were not added
const projFileText = (await fs.readFile(projFilePath)).toString();
should(projFileText.includes('<Folder Include="folder1" />')).equal(false, projFileText);
should(projFileText.includes('<Folder Include="folder2\\" />')).equal(false, projFileText);
should(projFileText.includes('<Build Remove="folder1\\**" />')).equal(false, projFileText);
should(projFileText.includes('<Build Remove="folder2\\**" />')).equal(false, projFileText);
}); });
// TODO: remove once DacFx exposes both absolute and relative outputPath // TODO: remove once DacFx exposes both absolute and relative outputPath
@@ -890,7 +860,7 @@ describe('Project: database references', function (): void {
should(project.databaseReferences.length).equal(1, 'There should be one database reference after trying to add a reference to testProject again'); should(project.databaseReferences.length).equal(1, 'There should be one database reference after trying to add a reference to testProject again');
}); });
it.skip('Should update sqlcmd variable values if value changes', async function (): Promise<void> { it('Should update sqlcmd variable values if value changes', async function (): Promise<void> {
const projFilePath = await testUtils.createTestSqlProjFile(this.test, baselines.newProjectFileBaseline); const projFilePath = await testUtils.createTestSqlProjFile(this.test, baselines.newProjectFileBaseline);
const project = await Project.openProject(projFilePath); const project = await Project.openProject(projFilePath);
const databaseVariable = 'test3Db'; const databaseVariable = 'test3Db';
@@ -933,12 +903,6 @@ describe('Project: database references', function (): void {
should(project.databaseReferences.length).equal(1, 'There should be a database reference after adding a reference to test3'); should(project.databaseReferences.length).equal(1, 'There should be a database reference after adding a reference to test3');
should(project.databaseReferences[0].referenceName).equal('test3', 'The database reference should be test3'); should(project.databaseReferences[0].referenceName).equal('test3', 'The database reference should be test3');
should(project.sqlCmdVariables.size).equal(2, 'There should still be 2 sqlcmdvars after adding the dacpac reference again with different sqlcmdvar values'); should(project.sqlCmdVariables.size).equal(2, 'There should still be 2 sqlcmdvars after adding the dacpac reference again with different sqlcmdvar values');
projFileText = (await fs.readFile(projFilePath)).toString();
should(projFileText).containEql('<SqlCmdVariable Include="test3Db">');
should(projFileText).containEql('<DefaultValue>newDbName</DefaultValue>');
should(projFileText).containEql('<SqlCmdVariable Include="otherServer">');
should(projFileText).containEql('<DefaultValue>newServerName</DefaultValue>');
}); });
}); });
@@ -1140,7 +1104,6 @@ describe('Project: round trip updates', function (): void {
await testUpdateInRoundTrip(this.test, baselines.SSDTProjectFileBaseline); await testUpdateInRoundTrip(this.test, baselines.SSDTProjectFileBaseline);
}); });
// skipped until https://mssqltools.visualstudio.com/SQL%20Tools%20Semester%20Work%20Tracking/_workitems/edit/15749 is fixed
it.skip('Should update SSDT project with new system database references', async function (): Promise<void> { it.skip('Should update SSDT project with new system database references', async function (): Promise<void> {
await testUpdateInRoundTrip(this.test, baselines.SSDTUpdatedProjectBaseline); await testUpdateInRoundTrip(this.test, baselines.SSDTUpdatedProjectBaseline);
}); });

View File

@@ -318,7 +318,7 @@ describe('ProjectsController', function (): void {
proj = await Project.openProject(proj.projectFilePath); // reload edited sqlproj from disk proj = await Project.openProject(proj.projectFilePath); // reload edited sqlproj from disk
// confirm result // confirm result
should(proj.files.length).equal(2, 'number of file entries'); // LowerFolder and the contained scripts should be deleted should(proj.files.length).equal(0, 'number of file entries'); // LowerFolder and the contained scripts should be excluded
should(proj.folders.find(f => f.relativePath === 'UpperFolder')).not.equal(undefined, 'UpperFolder should still be there'); should(proj.folders.find(f => f.relativePath === 'UpperFolder')).not.equal(undefined, 'UpperFolder should still be there');
should(proj.preDeployScripts.length).equal(0, 'Pre deployment scripts'); should(proj.preDeployScripts.length).equal(0, 'Pre deployment scripts');
should(proj.postDeployScripts.length).equal(0, 'Post deployment scripts'); should(proj.postDeployScripts.length).equal(0, 'Post deployment scripts');
@@ -330,6 +330,29 @@ describe('ProjectsController', function (): void {
should(await utils.exists(noneEntry.fsUri.fsPath)).equal(true, 'none entry pre-deployment script is supposed to still exist on disk'); should(await utils.exists(noneEntry.fsUri.fsPath)).equal(true, 'none entry pre-deployment script is supposed to still exist on disk');
}); });
it('Should exclude a folder', async function (): Promise<void> {
let proj = await testUtils.createTestSqlProject(this.test);
await proj.addScriptItem('SomeFolder\\MyTable.sql', 'CREATE TABLE [NotARealTable]');
const projController = new ProjectsController(testContext.outputChannel);
const projTreeRoot = new ProjectRootTreeItem(proj);
should(await utils.exists(path.join(proj.projectFolderPath, 'SomeFolder\\MyTable.sql'))).be.true('File should exist in original location');
(proj.files.length).should.equal(1, 'Starting number of files');
(proj.folders.length).should.equal(1, 'Starting number of folders');
// exclude folder
const folderNode = projTreeRoot.children.find(f => f.friendlyName === 'SomeFolder');
await projController.exclude(createWorkspaceTreeItem(folderNode!));
// reload project and verify files were renamed
proj = await Project.openProject(proj.projectFilePath);
should(await utils.exists(path.join(proj.projectFolderPath, 'SomeFolder\\MyTable.sql'))).be.true('File should still exist on disk');
(proj.files.length).should.equal(0, 'Number of files should not have changed');
(proj.folders.length).should.equal(0, 'Number of folders should not have changed');
});
// TODO: move test to DacFx and fix delete // TODO: move test to DacFx and fix delete
it.skip('Should delete folders with excluded items', async function (): Promise<void> { it.skip('Should delete folders with excluded items', async function (): Promise<void> {
let proj = await testUtils.createTestProject(this.test, templates.newSqlProjectTemplate); let proj = await testUtils.createTestProject(this.test, templates.newSqlProjectTemplate);
@@ -879,7 +902,7 @@ describe('ProjectsController', function (): void {
await projController.moveFile(vscode.Uri.file(proj.projectFilePath), sqlcmdVarNode, projectRootWorkspaceTreeItem); await projController.moveFile(vscode.Uri.file(proj.projectFilePath), sqlcmdVarNode, projectRootWorkspaceTreeItem);
should(spy.calledOnce).be.true('showErrorMessage should have been called exactly once when trying to move a sqlcmd variable'); should(spy.calledOnce).be.true('showErrorMessage should have been called exactly once when trying to move a sqlcmd variable');
should(spy.calledWith(constants.onlyMoveSqlFilesSupported)).be.true(`showErrorMessage not called with expected message '${constants.onlyMoveSqlFilesSupported}' Actual '${spy.getCall(0).args[0]}'`); should(spy.calledWith(constants.onlyMoveFilesFoldersSupported)).be.true(`showErrorMessage not called with expected message '${constants.onlyMoveFilesFoldersSupported}' Actual '${spy.getCall(0).args[0]}'`);
spy.restore(); spy.restore();
// try moving a database reference // try moving a database reference
@@ -887,7 +910,7 @@ describe('ProjectsController', function (): void {
await projController.moveFile(vscode.Uri.file(proj.projectFilePath), dbRefNode, projectRootWorkspaceTreeItem); await projController.moveFile(vscode.Uri.file(proj.projectFilePath), dbRefNode, projectRootWorkspaceTreeItem);
should(spy.calledOnce).be.true('showErrorMessage should have been called exactly once when trying to move a database reference'); should(spy.calledOnce).be.true('showErrorMessage should have been called exactly once when trying to move a database reference');
should(spy.calledWith(constants.onlyMoveSqlFilesSupported)).be.true(`showErrorMessage not called with expected message '${constants.onlyMoveSqlFilesSupported}' Actual '${spy.getCall(0).args[0]}'`); should(spy.calledWith(constants.onlyMoveFilesFoldersSupported)).be.true(`showErrorMessage not called with expected message '${constants.onlyMoveFilesFoldersSupported}' Actual '${spy.getCall(0).args[0]}'`);
spy.restore(); spy.restore();
// try moving a folder // try moving a folder
@@ -895,7 +918,7 @@ describe('ProjectsController', function (): void {
await projController.moveFile(vscode.Uri.file(proj.projectFilePath), folderNode, projectRootWorkspaceTreeItem); await projController.moveFile(vscode.Uri.file(proj.projectFilePath), folderNode, projectRootWorkspaceTreeItem);
should(spy.calledOnce).be.true('showErrorMessage should have been called exactly once when trying to move a folder'); should(spy.calledOnce).be.true('showErrorMessage should have been called exactly once when trying to move a folder');
should(spy.calledWith(constants.onlyMoveSqlFilesSupported)).be.true(`showErrorMessage not called with expected message '${constants.onlyMoveSqlFilesSupported}' Actual '${spy.getCall(0).args[0]}'`); should(spy.calledWith(constants.onlyMoveFilesFoldersSupported)).be.true(`showErrorMessage not called with expected message '${constants.onlyMoveFilesFoldersSupported}' Actual '${spy.getCall(0).args[0]}'`);
spy.restore(); spy.restore();
}); });
@@ -942,7 +965,7 @@ describe('ProjectsController', function (): void {
}); });
it('Should rename a sql object file', async function (): Promise<void> { it('Should rename a sql object file', async function (): Promise<void> {
sinon.stub(vscode.window, 'showInputBox').resolves('newName'); sinon.stub(vscode.window, 'showInputBox').resolves('newName.sql');
let proj = await testUtils.createTestProject(this.test, baselines.openSdkStyleSqlProjectBaseline); let proj = await testUtils.createTestProject(this.test, baselines.openSdkStyleSqlProjectBaseline);
const projTreeRoot = await setupMoveTest(proj); const projTreeRoot = await setupMoveTest(proj);
const projController = new ProjectsController(testContext.outputChannel); const projController = new ProjectsController(testContext.outputChannel);
@@ -966,12 +989,12 @@ describe('ProjectsController', function (): void {
const projTreeRoot = new ProjectRootTreeItem(proj); const projTreeRoot = new ProjectRootTreeItem(proj);
// try to rename a file from the root folder // try to rename a file from the root folder
sinon.stub(vscode.window, 'showInputBox').resolves('predeployNewName'); sinon.stub(vscode.window, 'showInputBox').resolves('predeployNewName.sql');
const preDeployScriptNode = projTreeRoot.children.find(x => x.friendlyName === 'Script.PreDeployment1.sql'); const preDeployScriptNode = projTreeRoot.children.find(x => x.friendlyName === 'Script.PreDeployment1.sql');
await projController.rename(createWorkspaceTreeItem(preDeployScriptNode!)); await projController.rename(createWorkspaceTreeItem(preDeployScriptNode!));
sinon.restore(); sinon.restore();
sinon.stub(vscode.window, 'showInputBox').resolves('postdeployNewName'); sinon.stub(vscode.window, 'showInputBox').resolves('postdeployNewName.sql');
const postDeployScriptNode = projTreeRoot.children.find(x => x.friendlyName === 'Script.PostDeployment1.sql'); const postDeployScriptNode = projTreeRoot.children.find(x => x.friendlyName === 'Script.PostDeployment1.sql');
await projController.rename(createWorkspaceTreeItem(postDeployScriptNode!)); await projController.rename(createWorkspaceTreeItem(postDeployScriptNode!));
@@ -984,8 +1007,31 @@ describe('ProjectsController', function (): void {
should(await utils.exists(path.join(proj.projectFolderPath, 'postdeployNewName.sql'))).be.true('The moved post deploy script file should exist'); should(await utils.exists(path.join(proj.projectFolderPath, 'postdeployNewName.sql'))).be.true('The moved post deploy script file should exist');
}); });
it('Should rename a folder', async function (): Promise<void> {
let proj = await testUtils.createTestSqlProject(this.test);
await proj.addScriptItem('SomeFolder\\MyTable.sql', 'CREATE TABLE [NotARealTable]');
// TODO: add test for renaming a file in a folder after fix from DacFx for slashes is brought over const projController = new ProjectsController(testContext.outputChannel);
const projTreeRoot = new ProjectRootTreeItem(proj);
sinon.stub(vscode.window, 'showInputBox').resolves('RenamedFolder');
should(await utils.exists(path.join(proj.projectFolderPath, 'SomeFolder\\MyTable.sql'))).be.true('File should exist in original location');
(proj.files.length).should.equal(1, 'Starting number of files');
(proj.folders.length).should.equal(1, 'Starting number of folders');
// rename folder
const folderNode = projTreeRoot.children.find(f => f.friendlyName === 'SomeFolder');
await projController.rename(createWorkspaceTreeItem(folderNode!));
// reload project and verify files were renamed
proj = await Project.openProject(proj.projectFilePath);
should(await utils.exists(path.join(proj.projectFolderPath, 'RenamedFolder\\MyTable.sql'))).be.true('File should exist in new location');
(proj.files.length).should.equal(1, 'Number of files should not have changed');
(proj.folders.length).should.equal(1, 'Number of folders should not have changed');
should(proj.folders.find(f => f.relativePath === 'RenamedFolder') !== undefined).be.true('The folder path should have been updated');
should(proj.files.find(f => f.relativePath === 'RenamedFolder\\MyTable.sql') !== undefined).be.true('Path of the script in the folder should have been updated');
});
}); });
describe('SqlCmd Variables', function (): void { describe('SqlCmd Variables', function (): void {
@@ -1000,7 +1046,7 @@ describe('ProjectsController', function (): void {
should(project.sqlCmdVariables.size).equal(2, 'The project should start with 2 sqlcmd variables'); should(project.sqlCmdVariables.size).equal(2, 'The project should start with 2 sqlcmd variables');
sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve('Cancel')); sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve('Cancel'));
await projController.delete(createWorkspaceTreeItem(projRoot.children.find(x => x.friendlyName === constants.sqlcmdVariablesNodeName)!.children[0])); await projController.delete(createWorkspaceTreeItem(projRoot.children.find(x => x.friendlyName === constants.sqlcmdVariablesNodeName)!.children[0] /* LowerFolder */));
// reload project // reload project
project = await Project.openProject(project.projectFilePath); project = await Project.openProject(project.projectFilePath);

View File

@@ -530,10 +530,10 @@ declare module 'vscode-mssql' {
/** /**
* Move a folder and its contents within a project * Move a folder and its contents within a project
* @param projectUri Absolute path of the project, including .sqlproj * @param projectUri Absolute path of the project, including .sqlproj
* @param destinationPath Path of the folder, typically relative to the .sqlproj file * @param sourcePath Source path of the folder, typically relative to the .sqlproj file
* @param path Path of the folder, typically relative to the .sqlproj file * @param destinationPath Destination path of the folder, typically relative to the .sqlproj file
*/ */
moveFolder(projectUri: string, destinationPath: string, path: string): Promise<ResultStatus>; moveFolder(projectUri: string, sourcePath: string, destinationPath: string): Promise<ResultStatus>;
/** /**
* Add a post-deployment script to a project * Add a post-deployment script to a project
@@ -580,18 +580,18 @@ declare module 'vscode-mssql' {
/** /**
* Move a post-deployment script in a project * Move a post-deployment script in a project
* @param projectUri Absolute path of the project, including .sqlproj * @param projectUri Absolute path of the project, including .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
* @param path Path of the script, including .sql, relative to the .sqlproj * @param path Path of the script, including .sql, relative to the .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
*/ */
movePostDeploymentScript(projectUri: string, destinationPath: string, path: string): Promise<ResultStatus>; movePostDeploymentScript(projectUri: string, path: string, destinationPath: string): Promise<ResultStatus>;
/** /**
* Move a pre-deployment script in a project * Move a pre-deployment script in a project
* @param projectUri Absolute path of the project, including .sqlproj * @param projectUri Absolute path of the project, including .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
* @param path Path of the script, including .sql, relative to the .sqlproj * @param path Path of the script, including .sql, relative to the .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
*/ */
movePreDeploymentScript(projectUri: string, destinationPath: string, path: string): Promise<ResultStatus>; movePreDeploymentScript(projectUri: string, path: string, destinationPath: string): Promise<ResultStatus>;
/** /**
* Close a SQL project * Close a SQL project
@@ -695,10 +695,10 @@ declare module 'vscode-mssql' {
/** /**
* Move a SQL object script in a project * Move a SQL object script in a project
* @param projectUri Absolute path of the project, including .sqlproj * @param projectUri Absolute path of the project, including .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
* @param path Path of the script, including .sql, relative to the .sqlproj * @param path Path of the script, including .sql, relative to the .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
*/ */
moveSqlObjectScript(projectUri: string, destinationPath: string, path: string): Promise<ResultStatus>; moveSqlObjectScript(projectUri: string, path: string, destinationPath: string): Promise<ResultStatus>;
/** /**
* Get all the database references in a project * Get all the database references in a project
@@ -766,10 +766,10 @@ declare module 'vscode-mssql' {
/** /**
* Move a None item in a project * Move a None item in a project
* @param projectUri Absolute path of the project, including .sqlproj * @param projectUri Absolute path of the project, including .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
* @param path Path of the item, including extension, relative to the .sqlproj * @param path Path of the item, including extension, relative to the .sqlproj
* @param destinationPath Destination path of the file or folder, relative to the .sqlproj
*/ */
moveNoneItem(projectUri: string, destinationPath: string, path: string): Promise<ResultStatus>; moveNoneItem(projectUri: string, path: string, destinationPath: string): Promise<ResultStatus>;
} }
/** /**