Remove references to root in sql projects (#21911)

* update getFileProjectEntry and getRelativePath

* remove root and fix tests
This commit is contained in:
Kim Santiago
2023-02-14 10:34:46 -08:00
committed by GitHub
parent d5384cad0e
commit 71c12883fe
4 changed files with 78 additions and 61 deletions

View File

@@ -18,8 +18,8 @@ import * as constants from '../common/constants';
import { SqlDatabaseProjectProvider } from '../projectProvider/projectProvider'; import { SqlDatabaseProjectProvider } from '../projectProvider/projectProvider';
import { EntryType, GenerateProjectFromOpenApiSpecOptions, ItemType } from 'sqldbproj'; import { EntryType, GenerateProjectFromOpenApiSpecOptions, ItemType } from 'sqldbproj';
import { FileNode, TableFileNode } from '../models/tree/fileFolderTreeItem'; import { FileNode, TableFileNode } from '../models/tree/fileFolderTreeItem';
import { ProjectRootTreeItem } from '../models/tree/projectTreeItem';
import { getAzdataApi } from '../common/utils'; import { getAzdataApi } from '../common/utils';
import { Project } from '../models/project';
/** /**
* The main controller class that initializes the extension * The main controller class that initializes the extension
@@ -94,10 +94,12 @@ export default class MainController implements vscode.Disposable {
this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.openInDesigner', async (node: WorkspaceTreeItem) => { this.context.subscriptions.push(vscode.commands.registerCommand('sqlDatabaseProjects.openInDesigner', async (node: WorkspaceTreeItem) => {
if (node?.element instanceof TableFileNode) { if (node?.element instanceof TableFileNode) {
const tableFileNode = node.element as TableFileNode; const tableFileNode = node.element as TableFileNode;
const projectNode = tableFileNode.root as ProjectRootTreeItem;
const projectPath = tableFileNode.sqlprojUri.fsPath;
const project = await Project.openProject(projectPath);
const targetVersion = project.getProjectTargetVersion();
const filePath = tableFileNode.fileSystemUri.fsPath; const filePath = tableFileNode.fileSystemUri.fsPath;
const projectPath = projectNode.project.projectFilePath;
const targetVersion = projectNode.project.getProjectTargetVersion();
await getAzdataApi()!.designers.openTableDesigner('MSSQL', { await getAzdataApi()!.designers.openTableDesigner('MSSQL', {
title: tableFileNode.friendlyName, title: tableFileNode.friendlyName,
tooltip: `${projectPath} - ${tableFileNode.friendlyName}`, tooltip: `${projectPath} - ${tableFileNode.friendlyName}`,
@@ -105,7 +107,7 @@ export default class MainController implements vscode.Disposable {
isNewTable: false, isNewTable: false,
tableScriptPath: filePath, tableScriptPath: filePath,
projectFilePath: projectPath, projectFilePath: projectPath,
allScripts: projectNode.project.files.filter(entry => entry.type === EntryType.File && path.extname(entry.fsUri.fsPath).toLowerCase() === constants.sqlFileExtension) allScripts: project.files.filter(entry => entry.type === EntryType.File && path.extname(entry.fsUri.fsPath).toLowerCase() === constants.sqlFileExtension)
.map(entry => entry.fsUri.fsPath), .map(entry => entry.fsUri.fsPath),
targetVersion: targetVersion targetVersion: targetVersion
}, { }, {

View File

@@ -21,7 +21,6 @@ import { Project, reservedProjectFolders } from '../models/project';
import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider'; import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider';
import { FolderNode, FileNode } from '../models/tree/fileFolderTreeItem'; import { FolderNode, FileNode } from '../models/tree/fileFolderTreeItem';
import { BaseProjectTreeItem } from '../models/tree/baseTreeItem'; import { BaseProjectTreeItem } from '../models/tree/baseTreeItem';
import { ProjectRootTreeItem } from '../models/tree/projectTreeItem';
import { ImportDataModel } from '../models/api/import'; import { ImportDataModel } from '../models/api/import';
import { NetCoreTool, DotNetError } from '../tools/netcoreTool'; import { NetCoreTool, DotNetError } from '../tools/netcoreTool';
import { ShellCommandOptions } from '../tools/shellExecutionHelper'; import { ShellCommandOptions } from '../tools/shellExecutionHelper';
@@ -257,7 +256,7 @@ export class ProjectsController {
*/ */
public async buildProject(project: Project): Promise<string>; public async buildProject(project: Project): Promise<string>;
public async buildProject(context: Project | dataworkspace.WorkspaceTreeItem): Promise<string> { public async buildProject(context: Project | dataworkspace.WorkspaceTreeItem): Promise<string> {
const project: Project = this.getProjectFromContext(context); const project: Project = await this.getProjectFromContext(context);
const startTime = new Date(); const startTime = new Date();
const currentBuildTimeInfo = `${startTime.toLocaleDateString()} ${constants.at} ${startTime.toLocaleTimeString()}`; const currentBuildTimeInfo = `${startTime.toLocaleDateString()} ${constants.at} ${startTime.toLocaleTimeString()}`;
@@ -332,7 +331,7 @@ export class ProjectsController {
public async publishToNewAzureServer(context: Project | dataworkspace.WorkspaceTreeItem, deployProfile: ISqlDbDeployProfile): Promise<void> { public async publishToNewAzureServer(context: Project | dataworkspace.WorkspaceTreeItem, deployProfile: ISqlDbDeployProfile): Promise<void> {
try { try {
TelemetryReporter.sendActionEvent(TelemetryViews.ProjectController, TelemetryActions.publishToNewAzureServer); TelemetryReporter.sendActionEvent(TelemetryViews.ProjectController, TelemetryActions.publishToNewAzureServer);
const project: Project = this.getProjectFromContext(context); const project: Project = await this.getProjectFromContext(context);
if (deployProfile?.deploySettings && deployProfile?.sqlDbSetting) { if (deployProfile?.deploySettings && deployProfile?.sqlDbSetting) {
void utils.showInfoMessageWithOutputChannel(constants.creatingAzureSqlServer(deployProfile?.sqlDbSetting?.serverName), this._outputChannel); void utils.showInfoMessageWithOutputChannel(constants.creatingAzureSqlServer(deployProfile?.sqlDbSetting?.serverName), this._outputChannel);
const connectionUri = await this.deployService.createNewAzureSqlServer(deployProfile); const connectionUri = await this.deployService.createNewAzureSqlServer(deployProfile);
@@ -365,7 +364,7 @@ export class ProjectsController {
* @param deployProfile * @param deployProfile
*/ */
public async publishToDockerContainer(context: Project | dataworkspace.WorkspaceTreeItem, deployProfile: IPublishToDockerSettings): Promise<void> { public async publishToDockerContainer(context: Project | dataworkspace.WorkspaceTreeItem, deployProfile: IPublishToDockerSettings): Promise<void> {
const project: Project = this.getProjectFromContext(context); const project: Project = await this.getProjectFromContext(context);
// Removing the path separator from the image base name to be able to add that in the telemetry. With the separator the name is flagged as user path which is not true // Removing the path separator from the image base name to be able to add that in the telemetry. With the separator the name is flagged as user path which is not true
// We only need to know the image base parts so it's ok to use a different separator when adding to telemetry // We only need to know the image base parts so it's ok to use a different separator when adding to telemetry
const dockerImageNameForTelemetry = deployProfile.dockerSettings.dockerBaseImage.replace(/\//gi, '_'); const dockerImageNameForTelemetry = deployProfile.dockerSettings.dockerBaseImage.replace(/\//gi, '_');
@@ -411,7 +410,7 @@ export class ProjectsController {
*/ */
public async publishProject(project: Project): Promise<void>; public async publishProject(project: Project): Promise<void>;
public async publishProject(context: Project | dataworkspace.WorkspaceTreeItem): Promise<void> { public async publishProject(context: Project | dataworkspace.WorkspaceTreeItem): Promise<void> {
const project: Project = this.getProjectFromContext(context); const project: Project = await this.getProjectFromContext(context);
if (utils.getAzdataApi()) { if (utils.getAzdataApi()) {
let publishDatabaseDialog = this.getPublishDialog(project); let publishDatabaseDialog = this.getPublishDialog(project);
@@ -589,7 +588,7 @@ export class ProjectsController {
let sourceParam; let sourceParam;
if (source as dataworkspace.WorkspaceTreeItem) { if (source as dataworkspace.WorkspaceTreeItem) {
sourceParam = this.getProjectFromContext(source as dataworkspace.WorkspaceTreeItem).projectFilePath; sourceParam = (await this.getProjectFromContext(source as dataworkspace.WorkspaceTreeItem)).projectFilePath;
} else { } else {
sourceParam = source as azdataType.IConnectionProfile; sourceParam = source as azdataType.IConnectionProfile;
} }
@@ -623,8 +622,9 @@ export class ProjectsController {
//#region Add/Exclude/Delete Item //#region Add/Exclude/Delete Item
public async addFolderPrompt(treeNode: dataworkspace.WorkspaceTreeItem): Promise<void> { public async addFolderPrompt(treeNode: dataworkspace.WorkspaceTreeItem): Promise<void> {
const project = this.getProjectFromContext(treeNode); const project = await this.getProjectFromContext(treeNode);
const relativePathToParent = this.getRelativePath(treeNode.element); const projectRelativeUri = vscode.Uri.file(path.basename((treeNode.element as BaseProjectTreeItem).sqlprojUri.fsPath, constants.sqlprojExtension));
const relativePathToParent = this.getRelativePath(projectRelativeUri, treeNode.element);
const absolutePathToParent = path.join(project.projectFolderPath, relativePathToParent); const absolutePathToParent = path.join(project.projectFolderPath, relativePathToParent);
const newFolderName = await this.promptForNewObjectName(new templates.ProjectScriptType(ItemType.folder, constants.folderFriendlyName, ''), const newFolderName = await this.promptForNewObjectName(new templates.ProjectScriptType(ItemType.folder, constants.folderFriendlyName, ''),
project, absolutePathToParent); project, absolutePathToParent);
@@ -679,7 +679,8 @@ export class ProjectsController {
} }
public async addItemPromptFromNode(treeNode: dataworkspace.WorkspaceTreeItem, itemTypeName?: string): Promise<void> { public async addItemPromptFromNode(treeNode: dataworkspace.WorkspaceTreeItem, itemTypeName?: string): Promise<void> {
await this.addItemPrompt(this.getProjectFromContext(treeNode), this.getRelativePath(treeNode.element), { itemType: itemTypeName }, treeNode.treeDataProvider as SqlDatabaseProjectTreeViewProvider); const projectRelativeUri = vscode.Uri.file(path.basename((treeNode.element as BaseProjectTreeItem).sqlprojUri.fsPath, constants.sqlprojExtension));
await this.addItemPrompt(await this.getProjectFromContext(treeNode), this.getRelativePath(projectRelativeUri, treeNode.element), { itemType: itemTypeName }, treeNode.treeDataProvider as SqlDatabaseProjectTreeViewProvider);
} }
public async addItemPrompt(project: ISqlProject, relativePath: string, options?: AddItemOptions, treeDataProvider?: SqlDatabaseProjectTreeViewProvider): Promise<void> { public async addItemPrompt(project: ISqlProject, relativePath: string, options?: AddItemOptions, treeDataProvider?: SqlDatabaseProjectTreeViewProvider): Promise<void> {
@@ -743,7 +744,7 @@ export class ProjectsController {
} }
public async addExistingItemPrompt(treeNode: dataworkspace.WorkspaceTreeItem): Promise<void> { public async addExistingItemPrompt(treeNode: dataworkspace.WorkspaceTreeItem): Promise<void> {
const project = this.getProjectFromContext(treeNode); const project = await this.getProjectFromContext(treeNode);
const uris = await vscode.window.showOpenDialog({ const uris = await vscode.window.showOpenDialog({
canSelectFiles: true, canSelectFiles: true,
@@ -769,7 +770,7 @@ export class ProjectsController {
public async exclude(context: dataworkspace.WorkspaceTreeItem): Promise<void> { public async exclude(context: dataworkspace.WorkspaceTreeItem): Promise<void> {
const node = context.element as BaseProjectTreeItem; const node = context.element as BaseProjectTreeItem;
const project = this.getProjectFromContext(node); const project = await this.getProjectFromContext(node);
const fileEntry = this.getFileProjectEntry(project, node); const fileEntry = this.getFileProjectEntry(project, node);
@@ -786,7 +787,7 @@ export class ProjectsController {
public async delete(context: dataworkspace.WorkspaceTreeItem): Promise<void> { public async delete(context: dataworkspace.WorkspaceTreeItem): Promise<void> {
const node = context.element as BaseProjectTreeItem; const node = context.element as BaseProjectTreeItem;
const project = this.getProjectFromContext(node); const project = await this.getProjectFromContext(node);
let confirmationPrompt; let confirmationPrompt;
if (node instanceof DatabaseReferenceTreeItem) { if (node instanceof DatabaseReferenceTreeItem) {
@@ -842,7 +843,7 @@ 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 = this.getProjectFromContext(node); const project = await this.getProjectFromContext(node);
const file = this.getFileProjectEntry(project, node); const file = this.getFileProjectEntry(project, node);
// need to use quickpick because input box isn't supported in treeviews // need to use quickpick because input box isn't supported in treeviews
@@ -881,7 +882,7 @@ export class ProjectsController {
*/ */
public async editSqlCmdVariable(context: dataworkspace.WorkspaceTreeItem): Promise<void> { public async editSqlCmdVariable(context: dataworkspace.WorkspaceTreeItem): Promise<void> {
const node = context.element as SqlCmdVariableTreeItem; const node = context.element as SqlCmdVariableTreeItem;
const project = this.getProjectFromContext(node); const project = await this.getProjectFromContext(node);
const originalValue = project.sqlCmdVariables[node.friendlyName]; // TODO: update to hookup with however sqlcmd vars work after swap const originalValue = project.sqlCmdVariables[node.friendlyName]; // TODO: update to hookup with however sqlcmd vars work after swap
const newValue = await vscode.window.showInputBox( const newValue = await vscode.window.showInputBox(
@@ -903,7 +904,7 @@ export class ProjectsController {
* @param context * @param context
*/ */
public async addSqlCmdVariable(context: dataworkspace.WorkspaceTreeItem): Promise<void> { public async addSqlCmdVariable(context: dataworkspace.WorkspaceTreeItem): Promise<void> {
const project = this.getProjectFromContext(context); const project = await this.getProjectFromContext(context);
const variableName = await vscode.window.showInputBox( const variableName = await vscode.window.showInputBox(
{ {
@@ -939,10 +940,9 @@ export class ProjectsController {
} }
private getDatabaseReference(project: Project, context: BaseProjectTreeItem): IDatabaseReferenceProjectEntry | undefined { private getDatabaseReference(project: Project, context: BaseProjectTreeItem): IDatabaseReferenceProjectEntry | undefined {
const root = context.root as ProjectRootTreeItem;
const databaseReference = context as DatabaseReferenceTreeItem; const databaseReference = context as DatabaseReferenceTreeItem;
if (root && databaseReference) { if (databaseReference) {
return project.databaseReferences.find(r => r.databaseName === databaseReference.treeItem.label); return project.databaseReferences.find(r => r.databaseName === databaseReference.treeItem.label);
} }
@@ -956,7 +956,7 @@ export class ProjectsController {
* @param context a treeItem in a project's hierarchy, to be used to obtain a Project * @param context a treeItem in a project's hierarchy, to be used to obtain a Project
*/ */
public async openContainingFolder(context: dataworkspace.WorkspaceTreeItem): Promise<void> { public async openContainingFolder(context: dataworkspace.WorkspaceTreeItem): Promise<void> {
const project = this.getProjectFromContext(context); const project = await this.getProjectFromContext(context);
await vscode.commands.executeCommand(constants.revealFileInOsCommand, vscode.Uri.file(project.projectFilePath)); await vscode.commands.executeCommand(constants.revealFileInOsCommand, vscode.Uri.file(project.projectFilePath));
} }
@@ -966,7 +966,7 @@ export class ProjectsController {
* @param context a treeItem in a project's hierarchy, to be used to obtain a Project * @param context a treeItem in a project's hierarchy, to be used to obtain a Project
*/ */
public async editProjectFile(context: dataworkspace.WorkspaceTreeItem): Promise<void> { public async editProjectFile(context: dataworkspace.WorkspaceTreeItem): Promise<void> {
const project = this.getProjectFromContext(context); const project = await this.getProjectFromContext(context);
try { try {
await vscode.commands.executeCommand(constants.vscodeOpenCommand, vscode.Uri.file(project.projectFilePath)); await vscode.commands.executeCommand(constants.vscodeOpenCommand, vscode.Uri.file(project.projectFilePath));
@@ -1004,7 +1004,8 @@ export class ProjectsController {
*/ */
public async openFileWithWatcher(fileSystemUri: vscode.Uri, node: FileNode): Promise<void> { public async openFileWithWatcher(fileSystemUri: vscode.Uri, node: FileNode): Promise<void> {
await vscode.commands.executeCommand(constants.vscodeOpenCommand, fileSystemUri); await vscode.commands.executeCommand(constants.vscodeOpenCommand, fileSystemUri);
const projectTargetVersion = (node.root as ProjectRootTreeItem).project.getProjectTargetVersion(); const project = await Project.openProject(node.sqlprojUri.fsPath);
const projectTargetVersion = project.getProjectTargetVersion();
const initiallyContainsCreateTableStatement = await utils.fileContainsCreateTableStatement(fileSystemUri.fsPath, projectTargetVersion); const initiallyContainsCreateTableStatement = await utils.fileContainsCreateTableStatement(fileSystemUri.fsPath, projectTargetVersion);
const fileWatcher: vscode.FileSystemWatcher = vscode.workspace.createFileSystemWatcher(fileSystemUri.fsPath); const fileWatcher: vscode.FileSystemWatcher = vscode.workspace.createFileSystemWatcher(fileSystemUri.fsPath);
@@ -1037,7 +1038,7 @@ export class ProjectsController {
* @param context * @param context
*/ */
public async reloadProject(context: dataworkspace.WorkspaceTreeItem): Promise<void> { public async reloadProject(context: dataworkspace.WorkspaceTreeItem): Promise<void> {
const project = this.getProjectFromContext(context); const project = await this.getProjectFromContext(context);
if (project) { if (project) {
// won't open any newly referenced projects, but otherwise matches the behavior of reopening the project // won't open any newly referenced projects, but otherwise matches the behavior of reopening the project
await project.readProjFile(); await project.readProjFile();
@@ -1052,7 +1053,7 @@ export class ProjectsController {
* @param context a treeItem in a project's hierarchy, to be used to obtain a Project * @param context a treeItem in a project's hierarchy, to be used to obtain a Project
*/ */
public async changeTargetPlatform(context: Project | dataworkspace.WorkspaceTreeItem): Promise<void> { public async changeTargetPlatform(context: Project | dataworkspace.WorkspaceTreeItem): Promise<void> {
const project = this.getProjectFromContext(context); const project = await this.getProjectFromContext(context);
const selectedTargetPlatform = (await vscode.window.showQuickPick((Array.from(constants.targetPlatformToVersion.keys())).map(version => { return { label: version }; }), const selectedTargetPlatform = (await vscode.window.showQuickPick((Array.from(constants.targetPlatformToVersion.keys())).map(version => { return { label: version }; }),
{ {
canPickMany: false, canPickMany: false,
@@ -1070,7 +1071,7 @@ export class ProjectsController {
* @param context a treeItem in a project's hierarchy, to be used to obtain a Project * @param context a treeItem in a project's hierarchy, to be used to obtain a Project
*/ */
public async convertToSdkStyleProject(context: dataworkspace.WorkspaceTreeItem): Promise<void> { public async convertToSdkStyleProject(context: dataworkspace.WorkspaceTreeItem): Promise<void> {
const project = this.getProjectFromContext(context); const project = await this.getProjectFromContext(context);
// confirm that user wants to update the project and knows the SSDT doesn't have support for displaying glob files yet // confirm that user wants to update the project and knows the SSDT doesn't have support for displaying glob files yet
await vscode.window.showWarningMessage(constants.convertToSdkStyleConfirmation(project.projectFileName), { modal: true }, constants.yesString).then(async (result) => { await vscode.window.showWarningMessage(constants.convertToSdkStyleConfirmation(project.projectFileName), { modal: true }, constants.yesString).then(async (result) => {
@@ -1101,7 +1102,7 @@ export class ProjectsController {
* @param context a treeItem in a project's hierarchy, to be used to obtain a Project * @param context a treeItem in a project's hierarchy, to be used to obtain a Project
*/ */
public async addDatabaseReference(context: Project | dataworkspace.WorkspaceTreeItem): Promise<AddDatabaseReferenceDialog | undefined> { public async addDatabaseReference(context: Project | dataworkspace.WorkspaceTreeItem): Promise<AddDatabaseReferenceDialog | undefined> {
const project = this.getProjectFromContext(context); const project = await this.getProjectFromContext(context);
if (utils.getAzdataApi()) { if (utils.getAzdataApi()) {
const addDatabaseReferenceDialog = this.getAddDatabaseReferenceDialog(project); const addDatabaseReferenceDialog = this.getAddDatabaseReferenceDialog(project);
@@ -1173,7 +1174,7 @@ export class ProjectsController {
* @param node a treeItem in a project's hierarchy, to be used to obtain a Project * @param node a treeItem in a project's hierarchy, to be used to obtain a Project
*/ */
public async validateExternalStreamingJob(node: dataworkspace.WorkspaceTreeItem): Promise<mssql.ValidateStreamingJobResult> { public async validateExternalStreamingJob(node: dataworkspace.WorkspaceTreeItem): Promise<mssql.ValidateStreamingJobResult> {
const project: Project = this.getProjectFromContext(node); const project: Project = await this.getProjectFromContext(node);
let dacpacPath: string = project.dacpacOutputPath; let dacpacPath: string = project.dacpacOutputPath;
const preExistingDacpac = await utils.exists(dacpacPath); const preExistingDacpac = await utils.exists(dacpacPath);
@@ -1444,38 +1445,38 @@ export class ProjectsController {
//#region Helper methods //#region Helper methods
private getFileProjectEntry(project: Project, context: BaseProjectTreeItem): FileProjectEntry | undefined { private getFileProjectEntry(project: Project, context: BaseProjectTreeItem): FileProjectEntry | undefined {
const root = context.root as ProjectRootTreeItem;
const fileOrFolder = context as FileNode ? context as FileNode : context as FolderNode; const fileOrFolder = context as FileNode ? context as FileNode : context as FolderNode;
if (root && fileOrFolder) { if (fileOrFolder) {
// use relative path and not tree paths for files and folder // use relative path and not tree paths for files and folder
const allFileEntries = project.files.concat(project.preDeployScripts).concat(project.postDeployScripts).concat(project.noneDeployScripts); const allFileEntries = project.files.concat(project.preDeployScripts).concat(project.postDeployScripts).concat(project.noneDeployScripts);
// trim trailing slash since folders with and without a trailing slash are allowed in a sqlproj // trim trailing slash since folders with and without a trailing slash are allowed in a sqlproj
const trimmedUri = utils.trimChars(utils.getPlatformSafeFileEntryPath(utils.trimUri(root.fileSystemUri, fileOrFolder.fileSystemUri)), '/'); const trimmedUri = utils.trimChars(utils.getPlatformSafeFileEntryPath(utils.trimUri(fileOrFolder.sqlprojUri, fileOrFolder.fileSystemUri)), '/');
return allFileEntries.find(x => utils.trimChars(utils.getPlatformSafeFileEntryPath(x.relativePath), '/') === trimmedUri); return allFileEntries.find(x => utils.trimChars(utils.getPlatformSafeFileEntryPath(x.relativePath), '/') === trimmedUri);
} }
return project.files.find(x => utils.getPlatformSafeFileEntryPath(x.relativePath) === utils.getPlatformSafeFileEntryPath(utils.trimUri(context.root.relativeProjectUri, context.relativeProjectUri))); const projectRelativeUri = vscode.Uri.file(path.basename(context.sqlprojUri.fsPath, constants.sqlprojExtension));
return project.files.find(x => utils.getPlatformSafeFileEntryPath(x.relativePath) === utils.getPlatformSafeFileEntryPath(utils.trimUri(projectRelativeUri, context.relativeProjectUri)));
} }
private getProjectFromContext(context: Project | BaseProjectTreeItem | dataworkspace.WorkspaceTreeItem): Project { private async getProjectFromContext(context: Project | BaseProjectTreeItem | dataworkspace.WorkspaceTreeItem): Promise<Project> {
if ('element' in context) { if ('element' in context) {
return context.element.root.project; context = context.element;
} }
if (context instanceof Project) { if (context instanceof Project) {
return context; return context;
} }
if (context.root instanceof ProjectRootTreeItem) { if (context instanceof BaseProjectTreeItem) {
return (<ProjectRootTreeItem>context.root).project; return Project.openProject(context.sqlprojUri.fsPath);
} else { } else {
throw new Error(constants.unexpectedProjectContext(context.relativeProjectUri.path)); throw new Error(constants.unexpectedProjectContext(JSON.stringify(context)));
} }
} }
private getRelativePath(treeNode: BaseProjectTreeItem): string { private getRelativePath(rootProjectUri: vscode.Uri, treeNode: BaseProjectTreeItem): string {
return treeNode instanceof FolderNode ? utils.trimUri(treeNode.root.relativeProjectUri, treeNode.relativeProjectUri) : ''; return treeNode instanceof FolderNode ? utils.trimUri(rootProjectUri, treeNode.relativeProjectUri) : '';
} }
private getConnectionProfileFromContext(context: azdataType.IConnectionProfile | mssqlVscode.ITreeNodeInfo | undefined): azdataType.IConnectionProfile | mssqlVscode.IConnectionInfo | undefined { private getConnectionProfileFromContext(context: azdataType.IConnectionProfile | mssqlVscode.ITreeNodeInfo | undefined): azdataType.IConnectionProfile | mssqlVscode.IConnectionInfo | undefined {
@@ -1666,7 +1667,7 @@ export class ProjectsController {
try { try {
if ('treeDataProvider' in context) { if ('treeDataProvider' in context) {
project = this.getProjectFromContext(context as dataworkspace.WorkspaceTreeItem); project = await this.getProjectFromContext(context as dataworkspace.WorkspaceTreeItem);
} }
} catch { } } catch { }

View File

@@ -25,14 +25,4 @@ export abstract class BaseProjectTreeItem {
public get friendlyName(): string { public get friendlyName(): string {
return path.parse(this.relativeProjectUri.path).base; return path.parse(this.relativeProjectUri.path).base;
} }
public get root() {
let node: BaseProjectTreeItem = this;
while (node.parent !== undefined) {
node = node.parent;
}
return node;
}
} }

View File

@@ -127,10 +127,11 @@ describe('ProjectsController', function (): void {
sinon.stub(vscode.window, 'showInputBox').resolves(tableName); sinon.stub(vscode.window, 'showInputBox').resolves(tableName);
const spy = sinon.spy(vscode.window, 'showErrorMessage'); const spy = sinon.spy(vscode.window, 'showErrorMessage');
const projController = new ProjectsController(testContext.outputChannel); const projController = new ProjectsController(testContext.outputChannel);
const project = await testUtils.createTestProject(baselines.newProjectFileBaseline); let project = await testUtils.createTestProject(baselines.newProjectFileBaseline);
should(project.files.length).equal(0, 'There should be no files'); should(project.files.length).equal(0, 'There should be no files');
await projController.addItemPrompt(project, '', { itemType: ItemType.script }); await projController.addItemPrompt(project, '', { itemType: ItemType.script });
should(project.files.length).equal(1, 'File should be successfully added'); should(project.files.length).equal(1, 'File should be successfully added');
await projController.addItemPrompt(project, '', { itemType: ItemType.script }); await projController.addItemPrompt(project, '', { itemType: ItemType.script });
const msg = constants.fileAlreadyExists(tableName); const msg = constants.fileAlreadyExists(tableName);
@@ -155,7 +156,7 @@ describe('ProjectsController', function (): void {
sinon.stub(vscode.window, 'showInputBox').resolves(tableName); sinon.stub(vscode.window, 'showInputBox').resolves(tableName);
const spy = sinon.spy(vscode.window, 'showErrorMessage'); const spy = sinon.spy(vscode.window, 'showErrorMessage');
const projController = new ProjectsController(testContext.outputChannel); const projController = new ProjectsController(testContext.outputChannel);
const project = await testUtils.createTestProject(baselines.newProjectFileBaseline); let project = await testUtils.createTestProject(baselines.newProjectFileBaseline);
should(project.files.length).equal(0, 'There should be no files'); should(project.files.length).equal(0, 'There should be no files');
await projController.addItemPrompt(project, '', { itemType: ItemType.script }); await projController.addItemPrompt(project, '', { itemType: ItemType.script });
@@ -164,12 +165,18 @@ describe('ProjectsController', function (): void {
// exclude item // exclude item
const projTreeRoot = new ProjectRootTreeItem(project); const projTreeRoot = new ProjectRootTreeItem(project);
await projController.exclude(createWorkspaceTreeItem(<FileNode>projTreeRoot.children.find(x => x.friendlyName === 'table1.sql')!)); await projController.exclude(createWorkspaceTreeItem(<FileNode>projTreeRoot.children.find(x => x.friendlyName === 'table1.sql')!));
// reload project
project = await Project.openProject(project.projectFilePath);
should(project.files.length).equal(0, 'File should be successfully excluded'); should(project.files.length).equal(0, 'File should be successfully excluded');
should(spy.called).be.false(`showErrorMessage not called with expected message. Actual '${spy.getCall(0)?.args[0]}'`); should(spy.called).be.false(`showErrorMessage not called with expected message. Actual '${spy.getCall(0)?.args[0]}'`);
// add item back // add item back
sinon.stub(vscode.window, 'showOpenDialog').resolves([vscode.Uri.file(path.join(project.projectFolderPath, 'table1.sql'))]); sinon.stub(vscode.window, 'showOpenDialog').resolves([vscode.Uri.file(path.join(project.projectFolderPath, 'table1.sql'))]);
await projController.addExistingItemPrompt(createWorkspaceTreeItem(projTreeRoot)); await projController.addExistingItemPrompt(createWorkspaceTreeItem(projTreeRoot));
// reload project
project = await Project.openProject(project.projectFilePath);
should(project.files.length).equal(1, 'File should be successfully re-added'); should(project.files.length).equal(1, 'File should be successfully re-added');
}); });
@@ -178,11 +185,15 @@ describe('ProjectsController', function (): void {
const stub = sinon.stub(vscode.window, 'showInputBox').resolves(folderName); const stub = sinon.stub(vscode.window, 'showInputBox').resolves(folderName);
const projController = new ProjectsController(testContext.outputChannel); const projController = new ProjectsController(testContext.outputChannel);
const project = await testUtils.createTestProject(baselines.newProjectFileBaseline); let project = await testUtils.createTestProject(baselines.newProjectFileBaseline);
const projectRoot = new ProjectRootTreeItem(project); const projectRoot = new ProjectRootTreeItem(project);
should(project.files.length).equal(0, 'There should be no other folders'); should(project.files.length).equal(0, 'There should be no other folders');
await projController.addFolderPrompt(createWorkspaceTreeItem(projectRoot)); await projController.addFolderPrompt(createWorkspaceTreeItem(projectRoot));
// reload project
project = await Project.openProject(project.projectFilePath);
should(project.files.length).equal(1, 'Folder should be successfully added'); should(project.files.length).equal(1, 'Folder should be successfully added');
stub.restore(); stub.restore();
await verifyFolderNotAdded(folderName, projController, project, projectRoot); await verifyFolderNotAdded(folderName, projController, project, projectRoot);
@@ -198,22 +209,28 @@ describe('ProjectsController', function (): void {
const stub = sinon.stub(vscode.window, 'showInputBox').resolves(folderName); const stub = sinon.stub(vscode.window, 'showInputBox').resolves(folderName);
const projController = new ProjectsController(testContext.outputChannel); const projController = new ProjectsController(testContext.outputChannel);
const project = await testUtils.createTestProject(baselines.openProjectFileBaseline); let project = await testUtils.createTestProject(baselines.openProjectFileBaseline);
const projectRoot = new ProjectRootTreeItem(project); const projectRoot = new ProjectRootTreeItem(project);
// make sure it's ok to add these folders if they aren't where the reserved folders are at the root of the project // make sure it's ok to add these folders if they aren't where the reserved folders are at the root of the project
let node = projectRoot.children.find(c => c.friendlyName === 'Tables'); let node = projectRoot.children.find(c => c.friendlyName === 'Tables');
stub.restore(); stub.restore();
for (let i in reservedProjectFolders) { for (let i in reservedProjectFolders) {
// reload project
project = await Project.openProject(project.projectFilePath);
await verifyFolderAdded(reservedProjectFolders[i], projController, project, <BaseProjectTreeItem>node); await verifyFolderAdded(reservedProjectFolders[i], projController, project, <BaseProjectTreeItem>node);
} }
}); });
async function verifyFolderAdded(folderName: string, projController: ProjectsController, project: Project, node: BaseProjectTreeItem): Promise<void> { async function verifyFolderAdded(folderName: string, projController: ProjectsController, project: Project, node: BaseProjectTreeItem): Promise<void> {
const beforeFileCount = project.files.length; const beforeFileCount = project.files.length;
let beforeFiles = project.files.map(f => f.relativePath);
const stub = sinon.stub(vscode.window, 'showInputBox').resolves(folderName); const stub = sinon.stub(vscode.window, 'showInputBox').resolves(folderName);
await projController.addFolderPrompt(createWorkspaceTreeItem(node)); await projController.addFolderPrompt(createWorkspaceTreeItem(node));
should(project.files.length).equal(beforeFileCount + 1, `File count should be increased by one after adding the folder ${folderName}`);
// reload project
project = await Project.openProject(project.projectFilePath);
should(project.files.length).equal(beforeFileCount + 1, `File count should be increased by one after adding the folder ${folderName}. before files: ${JSON.stringify(beforeFiles)}/n after files: ${JSON.stringify(project.files.map(f => f.relativePath))}`);
stub.restore(); stub.restore();
} }
@@ -261,7 +278,7 @@ describe('ProjectsController', function (): void {
it('Should delete database references', async function (): Promise<void> { it('Should delete database references', async function (): Promise<void> {
// setup - openProject baseline has a system db reference to master // setup - openProject baseline has a system db reference to master
const proj = await testUtils.createTestProject(baselines.openProjectFileBaseline); let proj = await testUtils.createTestProject(baselines.openProjectFileBaseline);
const projController = new ProjectsController(testContext.outputChannel); const projController = new ProjectsController(testContext.outputChannel);
sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve(constants.yesString)); sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve(constants.yesString));
@@ -281,6 +298,8 @@ describe('ProjectsController', function (): void {
}); });
const projTreeRoot = new ProjectRootTreeItem(proj); const projTreeRoot = new ProjectRootTreeItem(proj);
// reload project
proj = await Project.openProject(proj.projectFilePath);
should(proj.databaseReferences.length).equal(3, 'Should start with 3 database references'); should(proj.databaseReferences.length).equal(3, 'Should start with 3 database references');
const databaseReferenceNodeChildren = projTreeRoot.children.find(x => x.friendlyName === constants.databaseReferencesNodeName)?.children; const databaseReferenceNodeChildren = projTreeRoot.children.find(x => x.friendlyName === constants.databaseReferencesNodeName)?.children;
@@ -289,6 +308,8 @@ describe('ProjectsController', function (): void {
await projController.delete(createWorkspaceTreeItem(databaseReferenceNodeChildren?.find(x => x.friendlyName === 'project1')!)); // project reference await projController.delete(createWorkspaceTreeItem(databaseReferenceNodeChildren?.find(x => x.friendlyName === 'project1')!)); // project reference
// confirm result // confirm result
// reload project
proj = await Project.openProject(proj.projectFilePath);
should(proj.databaseReferences.length).equal(0, 'All database references should have been deleted'); should(proj.databaseReferences.length).equal(0, 'All database references should have been deleted');
}); });
@@ -353,7 +374,7 @@ describe('ProjectsController', function (): void {
const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline, folderPath); const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline, folderPath);
const treeProvider = new SqlDatabaseProjectTreeViewProvider(); const treeProvider = new SqlDatabaseProjectTreeViewProvider();
const projController = new ProjectsController(testContext.outputChannel); const projController = new ProjectsController(testContext.outputChannel);
const project = await Project.openProject(vscode.Uri.file(sqlProjPath).fsPath); let project = await Project.openProject(vscode.Uri.file(sqlProjPath).fsPath);
treeProvider.load([project]); treeProvider.load([project]);
// change the sql project file // change the sql project file
@@ -361,9 +382,12 @@ describe('ProjectsController', function (): void {
should(project.files.length).equal(0); should(project.files.length).equal(0);
// call reload project // call reload project
await projController.reloadProject({ treeDataProvider: treeProvider, element: { root: { project: project } } }); const projTreeRoot = new ProjectRootTreeItem(project);
await projController.reloadProject(createWorkspaceTreeItem(projTreeRoot));
// calling this because this gets called in the projectProvider.getProjectTreeDataProvider(), which is called by workspaceTreeDataProvider // calling this because this gets called in the projectProvider.getProjectTreeDataProvider(), which is called by workspaceTreeDataProvider
// when notifyTreeDataChanged() happens // when notifyTreeDataChanged() happens
// reload project
project = await Project.openProject(sqlProjPath);
treeProvider.load([project]); treeProvider.load([project]);
// check that the new project is in the tree // check that the new project is in the tree
@@ -421,7 +445,7 @@ describe('ProjectsController', function (): void {
projController.setup(x => x.getPublishDialog(TypeMoq.It.isAny())).returns(() => publishDialog.object); projController.setup(x => x.getPublishDialog(TypeMoq.It.isAny())).returns(() => publishDialog.object);
const proj = new Project('FakePath'); const proj = new Project('FakePath');
sinon.stub(proj, 'getProjectTargetVersion').returns('150'); sinon.stub(proj, 'getProjectTargetVersion').returns('150');
void projController.object.publishProject(proj); await projController.object.publishProject(proj);
should(opened).equal(true); should(opened).equal(true);
}); });