mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Make new workspace inputbox editable in Create project from database dialog (#13842)
* update create project from database dialog to have editable new workspace * add validation * add test * add error message * Remove test for now * cleanup * add periods * throw errors * change return type to void
This commit is contained in:
@@ -38,9 +38,9 @@ export const NewWorkspaceWillBeCreated = localize('dataworkspace.NewWorkspaceWil
|
||||
export const WorkspaceLocationTitle = localize('dataworkspace.workspaceLocationTitle', "Workspace location");
|
||||
export const ProjectParentDirectoryNotExistError = (location: string): string => { return localize('dataworkspace.projectParentDirectoryNotExistError', "The selected project location '{0}' does not exist or is not a directory.", location); };
|
||||
export const ProjectDirectoryAlreadyExistError = (projectName: string, location: string): string => { return localize('dataworkspace.projectDirectoryAlreadyExistError', "There is already a directory named '{0}' in the selected location: '{1}'.", projectName, location); };
|
||||
export const WorkspaceFileInvalidError = (workspace: string): string => { return localize('dataworkspace.workspaceFileInvalidError', "The selected workspace file path '{0}' does not have the required file extension {1}", workspace, WorkspaceFileExtension); };
|
||||
export const WorkspaceParentDirectoryNotExistError = (location: string): string => { return localize('dataworkspace.workspaceParentDirectoryNotExistError', "The selected workspace location '{0}' does not exist or is not a directory", location); };
|
||||
export const WorkspaceFileAlreadyExistsError = (file: string): string => { return localize('dataworkspace.workspaceFileAlreadyExistsError', "The selected workspace file '{0}' already exists. To add the project to an existing workspace, use the Open Existing dialog to first open the workspace", file); };
|
||||
export const WorkspaceFileInvalidError = (workspace: string): string => { return localize('dataworkspace.workspaceFileInvalidError', "The selected workspace file path '{0}' does not have the required file extension {1}.", workspace, WorkspaceFileExtension); };
|
||||
export const WorkspaceParentDirectoryNotExistError = (location: string): string => { return localize('dataworkspace.workspaceParentDirectoryNotExistError', "The selected workspace location '{0}' does not exist or is not a directory.", location); };
|
||||
export const WorkspaceFileAlreadyExistsError = (file: string): string => { return localize('dataworkspace.workspaceFileAlreadyExistsError', "The selected workspace file '{0}' already exists. To add the project to an existing workspace, use the Open Existing dialog to first open the workspace.", file); };
|
||||
|
||||
//Open Existing Dialog
|
||||
export const OpenExistingDialogTitle = localize('dataworkspace.openExistingDialogTitle', "Open existing");
|
||||
|
||||
@@ -16,8 +16,8 @@ export class DataWorkspaceExtension implements IExtension {
|
||||
return this.workspaceService.getProjectsInWorkspace();
|
||||
}
|
||||
|
||||
addProjectsToWorkspace(projectFiles: vscode.Uri[]): Promise<void> {
|
||||
return this.workspaceService.addProjectsToWorkspace(projectFiles);
|
||||
addProjectsToWorkspace(projectFiles: vscode.Uri[], workspaceFilePath?: vscode.Uri): Promise<void> {
|
||||
return this.workspaceService.addProjectsToWorkspace(projectFiles, workspaceFilePath);
|
||||
}
|
||||
|
||||
showProjectsView(): void {
|
||||
|
||||
@@ -20,9 +20,10 @@ declare module 'dataworkspace' {
|
||||
|
||||
/**
|
||||
* Add projects to the workspace
|
||||
* @param projectFiles Uris of project files to add
|
||||
* @param projectFiles Uris of project files to add,
|
||||
* @param workspaceFilePath workspace file to create if no workspace is open
|
||||
*/
|
||||
addProjectsToWorkspace(projectFiles: vscode.Uri[]): Promise<void>;
|
||||
addProjectsToWorkspace(projectFiles: vscode.Uri[], workspaceFilePath?: vscode.Uri): Promise<void>;
|
||||
|
||||
/**
|
||||
* Change focus to Projects view
|
||||
|
||||
@@ -137,6 +137,12 @@ export const addProjectToCurrentWorkspace = localize('addProjectToCurrentWorkspa
|
||||
export const newWorkspaceWillBeCreated = localize('newWorkspaceWillBeCreated', "A new workspace will be created for this project.");
|
||||
export const workspaceLocationTitle = localize('workspaceLocationTitle', "Workspace location");
|
||||
export const workspace = localize('workspace', "Workspace");
|
||||
export const WorkspaceFileExtension = '.code-workspace';
|
||||
export const ProjectParentDirectoryNotExistError = (location: string): string => { return localize('dataworkspace.projectParentDirectoryNotExistError', "The selected project location '{0}' does not exist or is not a directory.", location); };
|
||||
export const ProjectDirectoryAlreadyExistError = (projectName: string, location: string): string => { return localize('dataworkspace.projectDirectoryAlreadyExistError', "There is already a directory named '{0}' in the selected location: '{1}'.", projectName, location); };
|
||||
export const WorkspaceFileInvalidError = (workspace: string): string => { return localize('dataworkspace.workspaceFileInvalidError', "The selected workspace file path '{0}' does not have the required file extension {1}.", workspace, WorkspaceFileExtension); };
|
||||
export const WorkspaceParentDirectoryNotExistError = (location: string): string => { return localize('dataworkspace.workspaceParentDirectoryNotExistError', "The selected workspace location '{0}' does not exist or is not a directory.", location); };
|
||||
export const WorkspaceFileAlreadyExistsError = (file: string): string => { return localize('dataworkspace.workspaceFileAlreadyExistsError', "The selected workspace file '{0}' already exists. To add the project to an existing workspace, use the Open Existing dialog to first open the workspace.", file); };
|
||||
|
||||
|
||||
// Error messages
|
||||
|
||||
@@ -658,7 +658,7 @@ export class ProjectsController {
|
||||
|
||||
// add project to workspace
|
||||
workspaceApi.showProjectsView();
|
||||
await workspaceApi.addProjectsToWorkspace([vscode.Uri.file(newProjFilePath)]);
|
||||
await workspaceApi.addProjectsToWorkspace([vscode.Uri.file(newProjFilePath)], model.newWorkspaceFilePath);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { cssStyles } from '../common/uiConstants';
|
||||
import { ImportDataModel } from '../models/api/import';
|
||||
import { Deferred } from '../common/promise';
|
||||
import { getConnectionName } from './utils';
|
||||
import { exists } from '../common/utils';
|
||||
|
||||
export class CreateProjectFromDatabaseDialog {
|
||||
public dialog: azdata.window.Dialog;
|
||||
@@ -37,6 +38,9 @@ export class CreateProjectFromDatabaseDialog {
|
||||
constructor(private profile: azdata.IConnectionProfile | undefined) {
|
||||
this.dialog = azdata.window.createModelViewDialog(constants.createProjectFromDatabaseDialogName);
|
||||
this.createProjectFromDatabaseTab = azdata.window.createTab(constants.createProjectFromDatabaseDialogName);
|
||||
this.dialog.registerCloseValidator(async () => {
|
||||
return this.validate();
|
||||
});
|
||||
}
|
||||
|
||||
public async openDialog(): Promise<void> {
|
||||
@@ -164,7 +168,7 @@ export class CreateProjectFromDatabaseDialog {
|
||||
|
||||
this.sourceDatabaseDropDown.onValueChanged(() => {
|
||||
this.setProjectName();
|
||||
this.updateWorkspaceInputbox(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!);
|
||||
this.updateWorkspaceInputbox(path.join(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!), this.projectNameTextBox!.value!);
|
||||
this.tryEnableCreateButton();
|
||||
});
|
||||
|
||||
@@ -252,7 +256,7 @@ export class CreateProjectFromDatabaseDialog {
|
||||
this.projectNameTextBox.onTextChanged(() => {
|
||||
this.projectNameTextBox!.value = this.projectNameTextBox!.value?.trim();
|
||||
this.projectNameTextBox!.updateProperty('title', this.projectNameTextBox!.value);
|
||||
this.updateWorkspaceInputbox(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!);
|
||||
this.updateWorkspaceInputbox(path.join(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!), this.projectNameTextBox!.value!);
|
||||
this.tryEnableCreateButton();
|
||||
});
|
||||
|
||||
@@ -280,7 +284,7 @@ export class CreateProjectFromDatabaseDialog {
|
||||
|
||||
this.projectLocationTextBox.onTextChanged(() => {
|
||||
this.projectLocationTextBox!.updateProperty('title', this.projectLocationTextBox!.value);
|
||||
this.updateWorkspaceInputbox(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!);
|
||||
this.updateWorkspaceInputbox(path.join(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!), this.projectNameTextBox!.value!);
|
||||
this.tryEnableCreateButton();
|
||||
});
|
||||
|
||||
@@ -318,7 +322,7 @@ export class CreateProjectFromDatabaseDialog {
|
||||
|
||||
this.projectLocationTextBox!.value = folderUris[0].fsPath;
|
||||
this.projectLocationTextBox!.updateProperty('title', folderUris[0].fsPath);
|
||||
this.updateWorkspaceInputbox(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!);
|
||||
this.updateWorkspaceInputbox(path.join(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!), this.projectNameTextBox!.value!);
|
||||
});
|
||||
|
||||
return browseFolderButton;
|
||||
@@ -356,17 +360,54 @@ export class CreateProjectFromDatabaseDialog {
|
||||
private createWorkspaceContainerRow(view: azdata.ModelView): azdata.FlexContainer {
|
||||
this.workspaceInputBox = view.modelBuilder.inputBox().withProperties({
|
||||
ariaLabel: constants.workspaceLocationTitle,
|
||||
enabled: false,
|
||||
enabled: !vscode.workspace.workspaceFile, // want it editable if no workspace is open
|
||||
value: vscode.workspace.workspaceFile?.fsPath ?? '',
|
||||
title: vscode.workspace.workspaceFile?.fsPath ?? '' // hovertext for if file path is too long to be seen in textbox
|
||||
title: vscode.workspace.workspaceFile?.fsPath ?? '', // hovertext for if file path is too long to be seen in textbox
|
||||
width: '100%'
|
||||
}).component();
|
||||
|
||||
const browseFolderButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
ariaLabel: constants.browseButtonText,
|
||||
iconPath: IconPathHelper.folder_blue,
|
||||
height: '16px',
|
||||
width: '18px'
|
||||
}).component();
|
||||
|
||||
this.toDispose.push(browseFolderButton.onDidClick(async () => {
|
||||
const currentFileName = path.parse(this.workspaceInputBox!.value!).base;
|
||||
|
||||
// let user select folder for workspace file to be created in
|
||||
const folderUris = await vscode.window.showOpenDialog({
|
||||
canSelectFiles: false,
|
||||
canSelectFolders: true,
|
||||
canSelectMany: false,
|
||||
defaultUri: vscode.Uri.file(path.parse(this.workspaceInputBox!.value!).dir)
|
||||
});
|
||||
if (!folderUris || folderUris.length === 0) {
|
||||
return;
|
||||
}
|
||||
const selectedFolder = folderUris[0].fsPath;
|
||||
|
||||
const selectedFile = path.join(selectedFolder, currentFileName);
|
||||
this.workspaceInputBox!.value = selectedFile;
|
||||
this.workspaceInputBox!.title = selectedFile;
|
||||
}));
|
||||
|
||||
const workspaceLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: vscode.workspace.workspaceFile ? constants.addProjectToCurrentWorkspace : constants.newWorkspaceWillBeCreated,
|
||||
CSSStyles: { 'margin-top': '-10px', 'margin-bottom': '5px' }
|
||||
}).component();
|
||||
|
||||
const workspaceContainerRow = view.modelBuilder.flexContainer().withItems([workspaceLabel, this.workspaceInputBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px', 'margin-top': '0px' } }).withLayout({ flexFlow: 'column' }).component();
|
||||
let workspaceContainerRow;
|
||||
if (vscode.workspace.workspaceFile) {
|
||||
workspaceContainerRow = view.modelBuilder.flexContainer().withItems([workspaceLabel, this.workspaceInputBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px', 'margin-top': '0px' } }).withLayout({ flexFlow: 'column' }).component();
|
||||
|
||||
} else {
|
||||
// have browse button to help select where the workspace file should be created
|
||||
const workspaceInput = view.modelBuilder.flexContainer().withItems([this.workspaceInputBox], { CSSStyles: { 'margin-right': '10px', 'margin-bottom': '10px', 'width': '100%' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
workspaceInput.addItem(browseFolderButton, { CSSStyles: { 'margin-top': '-10px' } });
|
||||
workspaceContainerRow = view.modelBuilder.flexContainer().withItems([workspaceLabel, workspaceInput], { flex: '0 0 auto', CSSStyles: { 'margin-top': '0px' } }).withLayout({ flexFlow: 'column' }).component();
|
||||
}
|
||||
|
||||
return workspaceContainerRow;
|
||||
}
|
||||
@@ -401,7 +442,8 @@ export class CreateProjectFromDatabaseDialog {
|
||||
projName: this.projectNameTextBox!.value!,
|
||||
filePath: this.projectLocationTextBox!.value!,
|
||||
version: '1.0.0.0',
|
||||
extractTarget: this.mapExtractTargetEnum(<string>this.folderStructureDropDown!.value)
|
||||
extractTarget: this.mapExtractTargetEnum(<string>this.folderStructureDropDown!.value),
|
||||
newWorkspaceFilePath: this.workspaceInputBox!.enabled ? vscode.Uri.file(this.workspaceInputBox!.value!) : undefined
|
||||
};
|
||||
|
||||
azdata.window.closeDialog(this.dialog);
|
||||
@@ -424,4 +466,62 @@ export class CreateProjectFromDatabaseDialog {
|
||||
throw new Error(constants.extractTargetRequired);
|
||||
}
|
||||
}
|
||||
|
||||
async validate(): Promise<boolean> {
|
||||
try {
|
||||
// the selected location should be an existing directory
|
||||
const parentDirectoryExists = await exists(this.projectLocationTextBox!.value!);
|
||||
if (!parentDirectoryExists) {
|
||||
this.showErrorMessage(constants.ProjectParentDirectoryNotExistError(this.projectLocationTextBox!.value!));
|
||||
return false;
|
||||
}
|
||||
|
||||
// there shouldn't be an existing sub directory with the same name as the project in the selected location
|
||||
const projectDirectoryExists = await exists(path.join(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!));
|
||||
if (projectDirectoryExists) {
|
||||
this.showErrorMessage(constants.ProjectDirectoryAlreadyExistError(this.projectNameTextBox!.value!, this.projectLocationTextBox!.value!));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.workspaceInputBox!.enabled) {
|
||||
await this.validateNewWorkspace();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.showErrorMessage(err?.message ? err.message : err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected async validateNewWorkspace(): Promise<void> {
|
||||
const sameFolderAsNewProject = path.join(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!) === path.dirname(this.workspaceInputBox!.value!);
|
||||
|
||||
// workspace file should end in .code-workspace
|
||||
const workspaceValid = this.workspaceInputBox!.value!.endsWith(constants.WorkspaceFileExtension);
|
||||
if (!workspaceValid) {
|
||||
throw new Error(constants.WorkspaceFileInvalidError(this.workspaceInputBox!.value!));
|
||||
}
|
||||
|
||||
// if the workspace file is not going to be in the same folder as the newly created project, then check that it's a valid folder
|
||||
if (!sameFolderAsNewProject) {
|
||||
const workspaceParentDirectoryExists = await exists(path.dirname(this.workspaceInputBox!.value!));
|
||||
if (!workspaceParentDirectoryExists) {
|
||||
throw new Error(constants.WorkspaceParentDirectoryNotExistError(path.dirname(this.workspaceInputBox!.value!)));
|
||||
}
|
||||
}
|
||||
|
||||
// workspace file should not be an existing workspace file
|
||||
const workspaceFileExists = await exists(this.workspaceInputBox!.value!);
|
||||
if (workspaceFileExists) {
|
||||
throw new Error(constants.WorkspaceFileAlreadyExistsError(this.workspaceInputBox!.value!));
|
||||
}
|
||||
}
|
||||
|
||||
protected showErrorMessage(message: string): void {
|
||||
this.dialog.message = {
|
||||
text: message,
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Uri } from 'vscode';
|
||||
import { ExtractTarget } from '../../../../mssql';
|
||||
|
||||
/**
|
||||
@@ -15,4 +16,5 @@ export interface ImportDataModel {
|
||||
filePath: string;
|
||||
version: string;
|
||||
extractTarget: ExtractTarget;
|
||||
newWorkspaceFilePath?: Uri;
|
||||
}
|
||||
|
||||
@@ -97,6 +97,8 @@ describe('Create Project From Database Dialog', () => {
|
||||
sinon.stub(azdata.connection, 'listDatabases').resolves(['My Database']);
|
||||
await dialog.openDialog();
|
||||
|
||||
dialog.workspaceInputBox!.enabled = false;
|
||||
|
||||
dialog.projectNameTextBox!.value = 'testProject';
|
||||
dialog.projectLocationTextBox!.value = 'testLocation';
|
||||
|
||||
@@ -108,7 +110,8 @@ describe('Create Project From Database Dialog', () => {
|
||||
projName: 'testProject',
|
||||
filePath: 'testLocation',
|
||||
version: '1.0.0.0',
|
||||
extractTarget: mssql.ExtractTarget['schemaObjectType']
|
||||
extractTarget: mssql.ExtractTarget['schemaObjectType'],
|
||||
newWorkspaceFilePath: undefined
|
||||
};
|
||||
|
||||
dialog.createProjectFromDatabaseCallback = (m) => { model = m; };
|
||||
|
||||
Reference in New Issue
Block a user