Auto increment new db project name (#11882)

* auto increment db proj name

* auto increment on import project from db

* adding separate message if workspace setting is invalid

* updating based on feedback

* adding do not ask again functionality

* moving constants

* making newprojecttool only top level functions

* adding tests

* updating to address merge conflicts

* fixing tests

* fixing tests
This commit is contained in:
anjalia
2020-08-26 12:14:51 -07:00
committed by GitHub
parent a3121c0b2d
commit f7279cb1f5
7 changed files with 210 additions and 7 deletions

View File

@@ -35,6 +35,15 @@
"sqlDatabaseProjects.netCoreSDKLocation": {
"type": "string",
"description": "%sqlDatabaseProjects.netCoreInstallLocation%"
},
"sqlDatabaseProjects.defaultProjectSaveLocation": {
"type": "string",
"description": "%sqlDatabaseProjects.defaultProjectSaveLocation%"
},
"sqlDatabaseProjects.showUpdateSaveLocationPrompt": {
"type": "boolean",
"description": "%sqlDatabaseProjects.showUpdateSaveLocationPrompt%",
"default": true
}
}
}

View File

@@ -26,6 +26,8 @@
"sqlDatabaseProjects.openContainingFolder": "Open Containing Folder",
"sqlDatabaseProjects.Settings": "Database Projects",
"sqlDatabaseProjects.netCoreInstallLocation": "Full Path to .Net Core SDK on the machine.",
"sqlDatabaseProjects.netCoreInstallLocation": "Full path to .Net Core SDK on the machine.",
"sqlDatabaseProjects.defaultProjectSaveLocation": "Full path to folder where new database projects are saved by default.",
"sqlDatabaseProjects.showUpdateSaveLocationPrompt": "After creating a new database project, always prompt to update the location where new projects are saved by default.",
"sqlDatabaseProjects.welcome": "No database projects currently open.\n[New Project](command:sqlDatabaseProjects.new)\n[Open Project](command:sqlDatabaseProjects.open)\n[Create Project From Database](command:sqlDatabaseProjects.importDatabase)"
}

View File

@@ -56,6 +56,11 @@ export const flat = localize('flat', "Flat");
export const objectType = localize('objectType', "Object Type");
export const schema = localize('schema', "Schema");
export const schemaObjectType = localize('schemaObjectType', "Schema/Object Type");
export const defaultProjectNameStarter = localize('defaultProjectNameStarter', "DatabaseProject");
export const newDefaultProjectSaveLocation = localize('newDefaultProjectSaveLocation', "Would you like to update the default location to save new database projects?");
export const invalidDefaultProjectSaveLocation = localize('invalidDefaultProjectSaveLocation', "Default location to save new database projects is invalid. Would you like to update it?");
export const openWorkspaceSettings = localize('openWorkspaceSettings', "Yes, open Settings");
export const doNotPromptAgain = localize('doNotPromptAgain', "Don't ask again");
export function newObjectNamePrompt(objectType: string) { return localize('newObjectNamePrompt', 'New {0} name:', objectType); }
export function deleteConfirmation(toDelete: string) { return localize('deleteConfirmation', "Are you sure you want to delete {0}?", toDelete); }
export function deleteConfirmationContents(toDelete: string) { return localize('deleteConfirmationContents', "Are you sure you want to delete {0} and all of its contents?", toDelete); }
@@ -207,6 +212,11 @@ export const activeDirectoryInteractive = 'active directory interactive';
export const userIdSetting = 'User ID';
export const passwordSetting = 'Password';
// Workspace settings for saving new database projects
export const dbProjectConfigurationKey = 'sqlDatabaseProjects';
export const projectSaveLocationKey = 'defaultProjectSaveLocation';
export const showUpdatePromptKey = 'showUpdateSaveLocationPrompt';
// Authentication types
export const integratedAuth = 'Integrated';
export const azureMfaAuth = 'AzureMFA';

View File

@@ -9,6 +9,7 @@ import * as templates from '../templates/templates';
import * as constants from '../common/constants';
import * as path from 'path';
import * as glob from 'fast-glob';
import * as newProjectTool from '../tools/newProjectTool';
import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider';
import { getErrorMessage } from '../common/utils';
@@ -89,6 +90,9 @@ export default class MainController implements vscode.Disposable {
// ensure .net core is installed
await this.netcoreTool.findOrInstallNetCore();
// set the user settings around saving new projects to default value
await newProjectTool.initializeSaveLocationSetting();
// load any sql projects that are open in workspace folder
await this.loadProjectsInWorkspace();
}
@@ -144,8 +148,7 @@ export default class MainController implements vscode.Disposable {
try {
let newProjName = await vscode.window.showInputBox({
prompt: constants.newDatabaseProjectName,
value: `DatabaseProject${this.projectsController.projects.length + 1}`
// TODO: Smarter way to suggest a name. Easy if we prompt for location first, but that feels odd...
value: newProjectTool.defaultProjectNameNewProj()
});
newProjName = newProjName?.trim();
@@ -160,7 +163,7 @@ export default class MainController implements vscode.Disposable {
canSelectFiles: false,
canSelectFolders: true,
canSelectMany: false,
defaultUri: vscode.workspace.workspaceFolders ? (vscode.workspace.workspaceFolders as vscode.WorkspaceFolder[])[0].uri : undefined
defaultUri: newProjectTool.defaultProjectSaveLocation()
});
if (!selectionResult) {
@@ -174,6 +177,8 @@ export default class MainController implements vscode.Disposable {
const newProjFilePath = await this.projectsController.createNewProject(<string>newProjName, newProjFolderUri, true);
const proj = await this.projectsController.openProject(vscode.Uri.file(newProjFilePath));
newProjectTool.updateSaveLocationSetting();
return proj;
}
catch (err) {

View File

@@ -11,9 +11,10 @@ import * as path from 'path';
import * as utils from '../common/utils';
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
import * as templates from '../templates/templates';
import * as newProjectTool from '../tools/newProjectTool';
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import { promises as fs } from 'fs';
import { PublishDatabaseDialog } from '../dialogs/publishDatabaseDialog';
import { Project, DatabaseReferenceLocation, SystemDatabase, TargetPlatform, ProjectEntry, reservedProjectFolders, SqlProjectReferenceProjectEntry } from '../models/project';
@@ -661,6 +662,8 @@ export class ProjectsController {
model.extractTarget = await this.getExtractTarget();
model.version = '1.0.0.0';
newProjectTool.updateSaveLocationSetting();
const newProjFilePath = await this.createNewProject(model.projName, vscode.Uri.file(newProjFolderUri), true);
model.filePath = path.dirname(newProjFilePath);
@@ -739,7 +742,7 @@ export class ProjectsController {
private async getProjectName(dbName: string): Promise<string> {
let projName = await vscode.window.showInputBox({
prompt: constants.newDatabaseProjectName,
value: `DatabaseProject${dbName}`
value: newProjectTool.defaultProjectNameFromDb(dbName)
});
projName = projName?.trim();
@@ -797,7 +800,7 @@ export class ProjectsController {
canSelectFolders: true,
canSelectMany: false,
openLabel: constants.selectString,
defaultUri: vscode.workspace.workspaceFolders ? (vscode.workspace.workspaceFolders as vscode.WorkspaceFolder[])[0].uri : undefined
defaultUri: newProjectTool.defaultProjectSaveLocation()
});
if (selectionResult) {

View File

@@ -0,0 +1,52 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as should from 'should';
import * as newProjectTool from '../tools/newProjectTool';
import * as constants from '../common/constants';
import { generateTestFolderPath, createTestFile } from './testUtils';
let previousSetting : string;
let testFolderPath : string;
describe('NewProjectTool: New project tool tests', function (): void {
beforeEach(async function () {
previousSetting = await vscode.workspace.getConfiguration(constants.dbProjectConfigurationKey)[constants.projectSaveLocationKey];
testFolderPath = await generateTestFolderPath();
});
afterEach(async function () {
await vscode.workspace.getConfiguration(constants.dbProjectConfigurationKey).update(constants.projectSaveLocationKey, previousSetting, true);
});
it('Should generate correct default project names', async function (): Promise<void> {
await vscode.workspace.getConfiguration(constants.dbProjectConfigurationKey).update(constants.projectSaveLocationKey, testFolderPath, true);
should(newProjectTool.defaultProjectNameNewProj()).equal('DatabaseProject1');
should(newProjectTool.defaultProjectNameFromDb('master')).equal('DatabaseProjectmaster');
});
it('Should auto-increment default project names for new projects', async function (): Promise<void> {
await vscode.workspace.getConfiguration(constants.dbProjectConfigurationKey).update(constants.projectSaveLocationKey, testFolderPath, true);
should(newProjectTool.defaultProjectNameNewProj()).equal('DatabaseProject1');
await createTestFile('', 'DatabaseProject1', testFolderPath);
should(newProjectTool.defaultProjectNameNewProj()).equal('DatabaseProject2');
await createTestFile('', 'DatabaseProject2', testFolderPath);
should(newProjectTool.defaultProjectNameNewProj()).equal('DatabaseProject3');
});
it('Should auto-increment default project names for import projects', async function (): Promise<void> {
await vscode.workspace.getConfiguration(constants.dbProjectConfigurationKey).update(constants.projectSaveLocationKey, testFolderPath, true);
should(newProjectTool.defaultProjectNameFromDb("master")).equal('DatabaseProjectmaster');
await createTestFile('', 'DatabaseProjectmaster', testFolderPath);
should(newProjectTool.defaultProjectNameFromDb("master")).equal('DatabaseProjectmaster2');
await createTestFile('', 'DatabaseProjectmaster2', testFolderPath);
should(newProjectTool.defaultProjectNameFromDb("master")).equal('DatabaseProjectmaster3');
});
});

View File

@@ -0,0 +1,122 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as constants from '../common/constants';
/**
* Sets workspace setting on the default save location to the user's home directory
*/
export async function initializeSaveLocationSetting() {
if (!projectSaveLocationSettingExists()) {
await config().update(constants.projectSaveLocationKey, os.homedir(), true);
}
}
/**
* Returns the default location to save a new database project
*/
export function defaultProjectSaveLocation(): vscode.Uri {
return projectSaveLocationSettingIsValid() ? vscode.Uri.file(projectSaveLocationSetting()) : vscode.Uri.file(os.homedir());
}
/**
* Returns default project name for a fresh new project, such as 'DatabaseProject1'. Auto-increments
* the suggestion if a project of that name already exists in the default save location
*/
export function defaultProjectNameNewProj(): string {
return defaultProjectName(constants.defaultProjectNameStarter, 1);
}
/**
* Returns default project name for a new project based on given dbName. Auto-increments
* the suggestion if a project of that name already exists in the default save location
*
* @param dbName the database name to base the default project name off of
*/
export function defaultProjectNameFromDb(dbName: string): string {
const projectNameStarter = constants.defaultProjectNameStarter + dbName;
const projectPath: string = path.join(defaultProjectSaveLocation().fsPath, projectNameStarter);
if (!fs.existsSync(projectPath)) {
return projectNameStarter;
}
return defaultProjectName(projectNameStarter, 2);
}
/**
* Prompts user to update workspace settings
*/
export async function updateSaveLocationSetting(): Promise<void> {
const showPrompt: boolean = config()[constants.showUpdatePromptKey];
if (showPrompt) {
const openSettingsMessage = projectSaveLocationSettingIsValid() ?
constants.newDefaultProjectSaveLocation : constants.invalidDefaultProjectSaveLocation;
const result = await vscode.window.showInformationMessage(openSettingsMessage, constants.openWorkspaceSettings,
constants.doNotPromptAgain);
if (result === constants.openWorkspaceSettings || result === constants.doNotPromptAgain) {
// if user either opens settings or clicks "don't ask again", do not prompt for save location again
await config().update(constants.showUpdatePromptKey, false, true);
if (result === constants.openWorkspaceSettings) {
await vscode.commands.executeCommand('workbench.action.openGlobalSettings'); //open settings
}
}
}
}
/**
* Get workspace configurations for this extension
*/
function config(): vscode.WorkspaceConfiguration {
return vscode.workspace.getConfiguration(constants.dbProjectConfigurationKey);
}
/**
* Returns the workspace setting on the default location to save new database projects
*/
function projectSaveLocationSetting(): string {
return config()[constants.projectSaveLocationKey];
}
/**
* Returns if the default save location for new database projects workspace setting exists and is
* a valid path
*/
function projectSaveLocationSettingIsValid(): boolean {
return projectSaveLocationSettingExists() && fs.existsSync(projectSaveLocationSetting());
}
/**
* Returns if a value for the default save location for new database projects exists
*/
function projectSaveLocationSettingExists(): boolean {
return projectSaveLocationSetting() !== undefined && projectSaveLocationSetting() !== null
&& projectSaveLocationSetting().trim() !== '';
}
/**
* Returns a project name that begins with the given nameStarter, and ends in a number, such as
* 'DatabaseProject1'. Number begins at the given counter, but auto-increments if a project of
* that name already exists in the default save location.
*
* @param nameStarter the beginning of the default project name, such as 'DatabaseProject'
* @param counter the starting value of of the number appended to the nameStarter
*/
function defaultProjectName(nameStarter: string, counter: number): string {
while (counter < Number.MAX_SAFE_INTEGER) {
const name: string = nameStarter + counter;
const projectPath: string = path.join(defaultProjectSaveLocation().fsPath, name);
if (!fs.existsSync(projectPath)) {
return name;
}
counter++;
}
return constants.defaultProjectNameStarter + counter;
}