mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Add git clone option for opening existing workspace (#14828)
* added git option to dialog * Add validation for cloning workspace and hide radio buttons for project * add test * cleanup
This commit is contained in:
@@ -53,12 +53,19 @@ export const WorkspaceFileAlreadyExistsError = (file: string): string => { retur
|
|||||||
//Open Existing Dialog
|
//Open Existing Dialog
|
||||||
export const OpenExistingDialogTitle = localize('dataworkspace.openExistingDialogTitle', "Open existing");
|
export const OpenExistingDialogTitle = localize('dataworkspace.openExistingDialogTitle', "Open existing");
|
||||||
export const FileNotExistError = (fileType: string, filePath: string): string => { return localize('dataworkspace.fileNotExistError', "The selected {0} file '{1}' does not exist or is not a file.", fileType, filePath); };
|
export const FileNotExistError = (fileType: string, filePath: string): string => { return localize('dataworkspace.fileNotExistError', "The selected {0} file '{1}' does not exist or is not a file.", fileType, filePath); };
|
||||||
|
export const CloneParentDirectoryNotExistError = (location: string): string => { return localize('dataworkspace.cloneParentDirectoryNotExistError', "The selected clone path '{0}' does not exist or is not a directory.", location); };
|
||||||
export const Project = localize('dataworkspace.project', "Project");
|
export const Project = localize('dataworkspace.project', "Project");
|
||||||
export const Workspace = localize('dataworkspace.workspace', "Workspace");
|
export const Workspace = localize('dataworkspace.workspace', "Workspace");
|
||||||
export const LocationSelectorTitle = localize('dataworkspace.locationSelectorTitle', "Location");
|
export const LocationSelectorTitle = localize('dataworkspace.locationSelectorTitle', "Location");
|
||||||
export const ProjectFilePlaceholder = localize('dataworkspace.projectFilePlaceholder', "Select project (.sqlproj) file");
|
export const ProjectFilePlaceholder = localize('dataworkspace.projectFilePlaceholder', "Select project (.sqlproj) file");
|
||||||
export const WorkspacePlaceholder = localize('dataworkspace.workspacePlaceholder', "Select workspace ({0}) file", WorkspaceFileExtension);
|
export const WorkspacePlaceholder = localize('dataworkspace.workspacePlaceholder', "Select workspace ({0}) file", WorkspaceFileExtension);
|
||||||
export const ProjectAlreadyOpened = (path: string): string => { return localize('dataworkspace.projectAlreadyOpened', "Project '{0}' is already opened.", path); };
|
export const ProjectAlreadyOpened = (path: string): string => { return localize('dataworkspace.projectAlreadyOpened', "Project '{0}' is already opened.", path); };
|
||||||
|
export const Local = localize('dataworksapce.local', 'Local');
|
||||||
|
export const RemoteGitRepo = localize('dataworkspace.remoteGitRepo', "Remote git repository");
|
||||||
|
export const GitRepoUrlTitle = localize('dataworkspace.gitRepoUrlTitle', "Git repository URL");
|
||||||
|
export const GitRepoUrlPlaceholder = localize('dataworkspace.gitRepoUrlPlaceholder', "Enter remote git repository URL");
|
||||||
|
export const LocalClonePathTitle = localize('dataworkspace.localClonePathTitle', "Local clone path");
|
||||||
|
export const LocalClonePathPlaceholder = localize('dataworkspace.localClonePathPlaceholder', "Select location to clone repository locally");
|
||||||
|
|
||||||
// Workspace settings for saving new projects
|
// Workspace settings for saving new projects
|
||||||
export const ProjectConfigurationKey = 'projects';
|
export const ProjectConfigurationKey = 'projects';
|
||||||
|
|||||||
@@ -52,5 +52,6 @@ export enum TelemetryActions {
|
|||||||
NewProjectDialogLaunched = 'NewProjectDialogLaunched',
|
NewProjectDialogLaunched = 'NewProjectDialogLaunched',
|
||||||
OpeningWorkspace = 'OpeningWorkspace',
|
OpeningWorkspace = 'OpeningWorkspace',
|
||||||
OpenExistingDialogLaunched = 'OpenExistingDialogLaunched',
|
OpenExistingDialogLaunched = 'OpenExistingDialogLaunched',
|
||||||
NewProjectDialogCompleted = 'NewProjectDialogCompleted'
|
NewProjectDialogCompleted = 'NewProjectDialogCompleted',
|
||||||
|
GitClone = 'GitClone'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,21 @@ import * as path from 'path';
|
|||||||
import { DialogBase } from './dialogBase';
|
import { DialogBase } from './dialogBase';
|
||||||
import * as constants from '../common/constants';
|
import * as constants from '../common/constants';
|
||||||
import { IWorkspaceService } from '../common/interfaces';
|
import { IWorkspaceService } from '../common/interfaces';
|
||||||
import { fileExist } from '../common/utils';
|
import { directoryExist, fileExist } from '../common/utils';
|
||||||
import { IconPathHelper } from '../common/iconHelper';
|
import { IconPathHelper } from '../common/iconHelper';
|
||||||
import { calculateRelativity, TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry';
|
import { calculateRelativity, TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry';
|
||||||
|
import { defaultProjectSaveLocation } from '../common/projectLocationHelper';
|
||||||
|
|
||||||
export class OpenExistingDialog extends DialogBase {
|
export class OpenExistingDialog extends DialogBase {
|
||||||
public _targetTypeRadioCardGroup: azdata.RadioCardGroupComponent | undefined;
|
public targetTypeRadioCardGroup: azdata.RadioCardGroupComponent | undefined;
|
||||||
public _filePathTextBox: azdata.InputBoxComponent | undefined;
|
public filePathTextBox: azdata.InputBoxComponent | undefined;
|
||||||
|
public filePathAndButtonComponent: azdata.FormComponent | undefined;
|
||||||
|
public gitRepoTextBoxComponent: azdata.FormComponent | undefined;
|
||||||
|
public localClonePathComponent: azdata.FormComponent | undefined;
|
||||||
|
public localClonePathTextBox: azdata.InputBoxComponent | undefined;
|
||||||
|
public localRadioButton: azdata.RadioButtonComponent | undefined;
|
||||||
|
public remoteGitRepoRadioButton: azdata.RadioButtonComponent | undefined;
|
||||||
|
public locationRadioButtonFormComponent: azdata.FormComponent | undefined;
|
||||||
public formBuilder: azdata.FormBuilder | undefined;
|
public formBuilder: azdata.FormBuilder | undefined;
|
||||||
|
|
||||||
private _targetTypes = [
|
private _targetTypes = [
|
||||||
@@ -40,14 +48,20 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
async validate(): Promise<boolean> {
|
async validate(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
// the selected location should be an existing directory
|
// the selected location should be an existing directory
|
||||||
if (this._targetTypeRadioCardGroup?.selectedCardId === constants.Project) {
|
if (this.targetTypeRadioCardGroup?.selectedCardId === constants.Project) {
|
||||||
await this.validateFile(this._filePathTextBox!.value!, constants.Project.toLowerCase());
|
await this.validateFile(this.filePathTextBox!.value!, constants.Project.toLowerCase());
|
||||||
|
|
||||||
if (this.workspaceInputBox!.enabled) {
|
if (this.workspaceInputBox!.enabled) {
|
||||||
await this.validateNewWorkspace(false);
|
await this.validateNewWorkspace(false);
|
||||||
}
|
}
|
||||||
} else if (this._targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) {
|
} else if (this.targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) {
|
||||||
await this.validateFile(this._filePathTextBox!.value!, constants.Workspace.toLowerCase());
|
if (this.localRadioButton?.checked) {
|
||||||
|
await this.validateFile(this.filePathTextBox!.value!, constants.Workspace.toLowerCase());
|
||||||
|
} else {
|
||||||
|
// validate clone location
|
||||||
|
// check if parent folder exists
|
||||||
|
await this.validateClonePath(<string>this.localClonePathTextBox!.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -65,15 +79,33 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async validateClonePath(location: string): Promise<void> {
|
||||||
|
// only need to check if parent directory exists
|
||||||
|
// if the same repo has been cloned before, the git clone will append the next number to the folder
|
||||||
|
const parentDirectoryExists = await directoryExist(location);
|
||||||
|
if (!parentDirectoryExists) {
|
||||||
|
throw new Error(constants.CloneParentDirectoryNotExistError(location));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async onComplete(): Promise<void> {
|
async onComplete(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (this._targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) {
|
if (this.targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) {
|
||||||
// capture that workspace was selected, also if there's already an open workspace that's being replaced
|
// capture that workspace was selected, also if there's already an open workspace that's being replaced
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.OpeningWorkspace)
|
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.OpeningWorkspace)
|
||||||
.withAdditionalProperties({ hasWorkspaceOpen: (vscode.workspace.workspaceFile !== undefined).toString() })
|
.withAdditionalProperties({ hasWorkspaceOpen: (vscode.workspace.workspaceFile !== undefined).toString() })
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
await this.workspaceService.enterWorkspace(vscode.Uri.file(this._filePathTextBox!.value!));
|
if (this.remoteGitRepoRadioButton!.checked) {
|
||||||
|
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.GitClone)
|
||||||
|
.withAdditionalProperties({ selectedTarget: 'workspace' })
|
||||||
|
.send();
|
||||||
|
|
||||||
|
// after this executes, the git extension will show a popup asking if you want to enter the workspace
|
||||||
|
await vscode.commands.executeCommand('git.clone', (<azdata.InputBoxComponent>this.gitRepoTextBoxComponent?.component).value, this.localClonePathTextBox!.value);
|
||||||
|
} else {
|
||||||
|
await this.workspaceService.enterWorkspace(vscode.Uri.file(this.filePathTextBox!.value!));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// save datapoint now because it'll get set to new value during validateWorkspace()
|
// save datapoint now because it'll get set to new value during validateWorkspace()
|
||||||
const telemetryProps: any = { hasWorkspaceOpen: (vscode.workspace.workspaceFile !== undefined).toString() };
|
const telemetryProps: any = { hasWorkspaceOpen: (vscode.workspace.workspaceFile !== undefined).toString() };
|
||||||
@@ -82,13 +114,13 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
let addProjectsPromise: Promise<void>;
|
let addProjectsPromise: Promise<void>;
|
||||||
|
|
||||||
if (validateWorkspace) {
|
if (validateWorkspace) {
|
||||||
telemetryProps.workspaceProjectRelativity = calculateRelativity(this._filePathTextBox!.value!, this.workspaceInputBox!.value!);
|
telemetryProps.workspaceProjectRelativity = calculateRelativity(this.filePathTextBox!.value!, this.workspaceInputBox!.value!);
|
||||||
telemetryProps.cancelled = 'false';
|
telemetryProps.cancelled = 'false';
|
||||||
addProjectsPromise = this.workspaceService.addProjectsToWorkspace([vscode.Uri.file(this._filePathTextBox!.value!)], vscode.Uri.file(this.workspaceInputBox!.value!));
|
addProjectsPromise = this.workspaceService.addProjectsToWorkspace([vscode.Uri.file(this.filePathTextBox!.value!)], vscode.Uri.file(this.workspaceInputBox!.value!));
|
||||||
} else {
|
} else {
|
||||||
telemetryProps.workspaceProjectRelativity = 'none';
|
telemetryProps.workspaceProjectRelativity = 'none';
|
||||||
telemetryProps.cancelled = 'true';
|
telemetryProps.cancelled = 'true';
|
||||||
addProjectsPromise = this.workspaceService.addProjectsToWorkspace([vscode.Uri.file(this._filePathTextBox!.value!)], vscode.Uri.file(this.workspaceInputBox!.value!));
|
addProjectsPromise = this.workspaceService.addProjectsToWorkspace([vscode.Uri.file(this.filePathTextBox!.value!)], vscode.Uri.file(this.workspaceInputBox!.value!));
|
||||||
}
|
}
|
||||||
|
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.OpeningProject)
|
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.OpeningProject)
|
||||||
@@ -104,7 +136,7 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async initialize(view: azdata.ModelView): Promise<void> {
|
protected async initialize(view: azdata.ModelView): Promise<void> {
|
||||||
this._targetTypeRadioCardGroup = view.modelBuilder.radioCardGroup().withProperties<azdata.RadioCardGroupComponentProperties>({
|
this.targetTypeRadioCardGroup = view.modelBuilder.radioCardGroup().withProperties<azdata.RadioCardGroupComponentProperties>({
|
||||||
cards: this._targetTypes.map((targetType) => {
|
cards: this._targetTypes.map((targetType) => {
|
||||||
return <azdata.RadioCard>{
|
return <azdata.RadioCard>{
|
||||||
id: targetType.name,
|
id: targetType.name,
|
||||||
@@ -130,44 +162,148 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
selectedCardId: constants.Project
|
selectedCardId: constants.Project
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._filePathTextBox = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
this.localRadioButton = view.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({
|
||||||
ariaLabel: constants.LocationSelectorTitle,
|
name: 'location',
|
||||||
placeHolder: constants.ProjectFilePlaceholder,
|
label: constants.Local,
|
||||||
|
checked: true
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.register(this.localRadioButton.onDidChangeCheckedState(checked => {
|
||||||
|
if (checked) {
|
||||||
|
this.formBuilder?.removeFormItem(<azdata.FormComponent>this.gitRepoTextBoxComponent);
|
||||||
|
this.formBuilder?.removeFormItem(<azdata.FormComponent>this.localClonePathComponent);
|
||||||
|
this.formBuilder?.insertFormItem(<azdata.FormComponent>this.filePathAndButtonComponent, 2);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.remoteGitRepoRadioButton = view.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({
|
||||||
|
name: 'location',
|
||||||
|
label: constants.RemoteGitRepo
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.locationRadioButtonFormComponent = {
|
||||||
|
title: constants.LocationSelectorTitle,
|
||||||
|
required: true,
|
||||||
|
component: view.modelBuilder.flexContainer()
|
||||||
|
.withItems([this.localRadioButton, this.remoteGitRepoRadioButton], { flex: '0 0 auto', CSSStyles: { 'margin-right': '15px' } })
|
||||||
|
.withProperties({ ariaRole: 'radiogroup' })
|
||||||
|
.component()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.register(this.remoteGitRepoRadioButton.onDidChangeCheckedState(checked => {
|
||||||
|
if (checked) {
|
||||||
|
this.formBuilder?.removeFormItem(<azdata.FormComponent>this.filePathAndButtonComponent);
|
||||||
|
this.formBuilder?.insertFormItem(<azdata.FormComponent>this.gitRepoTextBoxComponent, 2);
|
||||||
|
this.formBuilder?.insertFormItem(<azdata.FormComponent>this.localClonePathComponent, 3);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.gitRepoTextBoxComponent = {
|
||||||
|
title: constants.GitRepoUrlTitle,
|
||||||
|
component: view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||||
|
ariaLabel: constants.GitRepoUrlTitle,
|
||||||
|
placeHolder: constants.GitRepoUrlPlaceholder,
|
||||||
|
required: true,
|
||||||
|
width: constants.DefaultInputWidth
|
||||||
|
}).component()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.localClonePathTextBox = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||||
|
ariaLabel: constants.LocalClonePathTitle,
|
||||||
|
placeHolder: constants.LocalClonePathPlaceholder,
|
||||||
required: true,
|
required: true,
|
||||||
width: constants.DefaultInputWidth
|
width: constants.DefaultInputWidth
|
||||||
}).component();
|
}).component();
|
||||||
this.register(this._filePathTextBox.onTextChanged(() => {
|
|
||||||
this._filePathTextBox!.updateProperty('title', this._filePathTextBox!.value!);
|
|
||||||
this.updateWorkspaceInputbox(path.dirname(this._filePathTextBox!.value!), path.basename(this._filePathTextBox!.value!, path.extname(this._filePathTextBox!.value!)));
|
|
||||||
}));
|
|
||||||
|
|
||||||
const browseFolderButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
const localClonePathBrowseFolderButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||||
ariaLabel: constants.BrowseButtonText,
|
ariaLabel: constants.BrowseButtonText,
|
||||||
iconPath: IconPathHelper.folder,
|
iconPath: IconPathHelper.folder,
|
||||||
width: '18px',
|
width: '18px',
|
||||||
height: '16px',
|
height: '16px',
|
||||||
}).component();
|
}).component();
|
||||||
this.register(browseFolderButton.onDidClick(async () => {
|
|
||||||
if (this._targetTypeRadioCardGroup?.selectedCardId === constants.Project) {
|
this.register(localClonePathBrowseFolderButton.onDidClick(async () => {
|
||||||
|
const folderUris = await vscode.window.showOpenDialog({
|
||||||
|
canSelectFiles: false,
|
||||||
|
canSelectFolders: true,
|
||||||
|
canSelectMany: false,
|
||||||
|
defaultUri: defaultProjectSaveLocation()
|
||||||
|
});
|
||||||
|
if (!folderUris || folderUris.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedFolder = folderUris[0].fsPath;
|
||||||
|
this.localClonePathTextBox!.value = selectedFolder;
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.localClonePathComponent = {
|
||||||
|
title: constants.LocalClonePathTitle,
|
||||||
|
component: this.createHorizontalContainer(view, [this.localClonePathTextBox, localClonePathBrowseFolderButton]),
|
||||||
|
required: true
|
||||||
|
};
|
||||||
|
|
||||||
|
this.filePathTextBox = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||||
|
ariaLabel: constants.LocationSelectorTitle,
|
||||||
|
placeHolder: constants.ProjectFilePlaceholder,
|
||||||
|
required: true,
|
||||||
|
width: constants.DefaultInputWidth
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.register(this.filePathTextBox.onTextChanged(() => {
|
||||||
|
this.filePathTextBox!.updateProperty('title', this.filePathTextBox!.value!);
|
||||||
|
this.updateWorkspaceInputbox(path.dirname(this.filePathTextBox!.value!), path.basename(this.filePathTextBox!.value!, path.extname(this.filePathTextBox!.value!)));
|
||||||
|
}));
|
||||||
|
|
||||||
|
const localProjectBrowseFolderButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||||
|
ariaLabel: constants.BrowseButtonText,
|
||||||
|
iconPath: IconPathHelper.folder,
|
||||||
|
width: '18px',
|
||||||
|
height: '16px'
|
||||||
|
}).component();
|
||||||
|
this.register(localProjectBrowseFolderButton.onDidClick(async () => {
|
||||||
|
if (this.targetTypeRadioCardGroup?.selectedCardId === constants.Project) {
|
||||||
await this.projectBrowse();
|
await this.projectBrowse();
|
||||||
} else if (this._targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) {
|
} else if (this.targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) {
|
||||||
await this.workspaceBrowse();
|
await this.workspaceBrowse();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.register(this._targetTypeRadioCardGroup.onSelectionChanged(({ cardId }) => {
|
const flexContainer = this.createHorizontalContainer(view, [this.filePathTextBox, localProjectBrowseFolderButton]);
|
||||||
|
flexContainer.updateCssStyles({ 'margin-top': '-10px' });
|
||||||
|
this.filePathAndButtonComponent = {
|
||||||
|
component: flexContainer
|
||||||
|
};
|
||||||
|
|
||||||
|
this.register(this.targetTypeRadioCardGroup.onSelectionChanged(({ cardId }) => {
|
||||||
if (cardId === constants.Project) {
|
if (cardId === constants.Project) {
|
||||||
this._filePathTextBox!.placeHolder = constants.ProjectFilePlaceholder;
|
this.filePathTextBox!.placeHolder = constants.ProjectFilePlaceholder;
|
||||||
|
// hide these two radio buttons for now since git clone is just for workspaces
|
||||||
|
this.localRadioButton?.updateCssStyles({ 'display': 'none' });
|
||||||
|
this.remoteGitRepoRadioButton?.updateCssStyles({ 'display': 'none' });
|
||||||
|
this.formBuilder?.removeFormItem(<azdata.FormComponent>this.gitRepoTextBoxComponent);
|
||||||
|
this.formBuilder?.removeFormItem(<azdata.FormComponent>this.localClonePathComponent);
|
||||||
|
|
||||||
|
this.formBuilder?.addFormItem(this.filePathAndButtonComponent!);
|
||||||
this.formBuilder?.addFormItem(this.workspaceDescriptionFormComponent!);
|
this.formBuilder?.addFormItem(this.workspaceDescriptionFormComponent!);
|
||||||
this.formBuilder?.addFormItem(this.workspaceInputFormComponent!);
|
this.formBuilder?.addFormItem(this.workspaceInputFormComponent!);
|
||||||
} else if (cardId === constants.Workspace) {
|
} else if (cardId === constants.Workspace) {
|
||||||
this._filePathTextBox!.placeHolder = constants.WorkspacePlaceholder;
|
this.filePathTextBox!.placeHolder = constants.WorkspacePlaceholder;
|
||||||
|
this.localRadioButton?.updateCssStyles({ 'display': 'block' });
|
||||||
|
this.remoteGitRepoRadioButton?.updateCssStyles({ 'display': 'block' });
|
||||||
|
|
||||||
this.formBuilder?.removeFormItem(this.workspaceDescriptionFormComponent!);
|
this.formBuilder?.removeFormItem(this.workspaceDescriptionFormComponent!);
|
||||||
this.formBuilder?.removeFormItem(this.workspaceInputFormComponent!);
|
this.formBuilder?.removeFormItem(this.workspaceInputFormComponent!);
|
||||||
|
|
||||||
|
if (this.remoteGitRepoRadioButton!.checked) {
|
||||||
|
this.formBuilder?.removeFormItem(<azdata.FormComponent>this.filePathAndButtonComponent);
|
||||||
|
this.formBuilder?.insertFormItem(<azdata.FormComponent>this.gitRepoTextBoxComponent, 2);
|
||||||
|
this.formBuilder?.insertFormItem(<azdata.FormComponent>this.localClonePathComponent, 3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear selected file textbox
|
// clear selected file textbox
|
||||||
this._filePathTextBox!.value = '';
|
this.filePathTextBox!.value = '';
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.createWorkspaceContainer(view);
|
this.createWorkspaceContainer(view);
|
||||||
@@ -176,12 +312,10 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
{
|
{
|
||||||
title: constants.TypeTitle,
|
title: constants.TypeTitle,
|
||||||
required: true,
|
required: true,
|
||||||
component: this._targetTypeRadioCardGroup,
|
component: this.targetTypeRadioCardGroup,
|
||||||
}, {
|
|
||||||
title: constants.LocationSelectorTitle,
|
|
||||||
required: true,
|
|
||||||
component: this.createHorizontalContainer(view, [this._filePathTextBox, browseFolderButton])
|
|
||||||
},
|
},
|
||||||
|
this.locationRadioButtonFormComponent,
|
||||||
|
this.filePathAndButtonComponent,
|
||||||
this.workspaceDescriptionFormComponent!,
|
this.workspaceDescriptionFormComponent!,
|
||||||
this.workspaceInputFormComponent!
|
this.workspaceInputFormComponent!
|
||||||
]);
|
]);
|
||||||
@@ -204,7 +338,7 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const workspaceFilePath = fileUris[0].fsPath;
|
const workspaceFilePath = fileUris[0].fsPath;
|
||||||
this._filePathTextBox!.value = workspaceFilePath;
|
this.filePathTextBox!.value = workspaceFilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async projectBrowse(): Promise<void> {
|
public async projectBrowse(): Promise<void> {
|
||||||
@@ -228,6 +362,6 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const projectFilePath = fileUris[0].fsPath;
|
const projectFilePath = fileUris[0].fsPath;
|
||||||
this._filePathTextBox!.value = projectFilePath;
|
this.filePathTextBox!.value = projectFilePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import * as should from 'should';
|
|||||||
import * as TypeMoq from 'typemoq';
|
import * as TypeMoq from 'typemoq';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import * as os from 'os';
|
||||||
import * as constants from '../../common/constants';
|
import * as constants from '../../common/constants';
|
||||||
import * as utils from '../../common/utils';
|
import * as utils from '../../common/utils';
|
||||||
import { WorkspaceService } from '../../services/workspaceService';
|
import { WorkspaceService } from '../../services/workspaceService';
|
||||||
@@ -25,8 +26,8 @@ suite('Open Existing Dialog', function (): void {
|
|||||||
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
||||||
await dialog.open();
|
await dialog.open();
|
||||||
|
|
||||||
dialog._targetTypeRadioCardGroup?.updateProperty( 'selectedCardId', constants.Project);
|
dialog.targetTypeRadioCardGroup?.updateProperty( 'selectedCardId', constants.Project);
|
||||||
dialog._filePathTextBox!.value = 'nonExistentProjectFile';
|
dialog.filePathTextBox!.value = 'nonExistentProjectFile';
|
||||||
dialog.workspaceInputBox!.value = 'test.code-workspace';
|
dialog.workspaceInputBox!.value = 'test.code-workspace';
|
||||||
|
|
||||||
const validateResult = await dialog.validate();
|
const validateResult = await dialog.validate();
|
||||||
@@ -36,7 +37,7 @@ suite('Open Existing Dialog', function (): void {
|
|||||||
should.equal(validateResult, false, 'Validation should fail because project file does not exist, but passed');
|
should.equal(validateResult, false, 'Validation should fail because project file does not exist, but passed');
|
||||||
|
|
||||||
// create a project file
|
// create a project file
|
||||||
dialog._filePathTextBox!.value = await createProjectFile('testproj');
|
dialog.filePathTextBox!.value = await createProjectFile('testproj');
|
||||||
should.equal(await dialog.validate(), true, `Validation should pass because project file exists, but failed with: ${dialog.dialogObject.message.text}`);
|
should.equal(await dialog.validate(), true, `Validation should pass because project file exists, but failed with: ${dialog.dialogObject.message.text}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -45,8 +46,8 @@ suite('Open Existing Dialog', function (): void {
|
|||||||
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
||||||
await dialog.open();
|
await dialog.open();
|
||||||
|
|
||||||
dialog._targetTypeRadioCardGroup?.updateProperty( 'selectedCardId', constants.Workspace);
|
dialog.targetTypeRadioCardGroup?.updateProperty( 'selectedCardId', constants.Workspace);
|
||||||
dialog._filePathTextBox!.value = 'nonExistentWorkspaceFile';
|
dialog.filePathTextBox!.value = 'nonExistentWorkspaceFile';
|
||||||
const fileExistStub = sinon.stub(utils, 'fileExist').resolves(false);
|
const fileExistStub = sinon.stub(utils, 'fileExist').resolves(false);
|
||||||
|
|
||||||
const validateResult = await dialog.validate();
|
const validateResult = await dialog.validate();
|
||||||
@@ -55,11 +56,32 @@ suite('Open Existing Dialog', function (): void {
|
|||||||
should.equal(validateResult, false, 'Validation should fail because workspace file does not exist, but passed');
|
should.equal(validateResult, false, 'Validation should fail because workspace file does not exist, but passed');
|
||||||
|
|
||||||
// validation should pass if workspace file exists
|
// validation should pass if workspace file exists
|
||||||
dialog._filePathTextBox!.value = generateUniqueWorkspaceFilePath();
|
dialog.filePathTextBox!.value = generateUniqueWorkspaceFilePath();
|
||||||
fileExistStub.resolves(true);
|
fileExistStub.resolves(true);
|
||||||
should.equal(await dialog.validate(), true, `Validation should pass because workspace file exists, but failed with: ${dialog.dialogObject.message.text}`);
|
should.equal(await dialog.validate(), true, `Validation should pass because workspace file exists, but failed with: ${dialog.dialogObject.message.text}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Should validate workspace git clone location', async function (): Promise<void> {
|
||||||
|
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
||||||
|
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
||||||
|
await dialog.open();
|
||||||
|
|
||||||
|
dialog.targetTypeRadioCardGroup?.updateProperty( 'selectedCardId', constants.Workspace);
|
||||||
|
dialog.localRadioButton!.checked = false;
|
||||||
|
dialog.remoteGitRepoRadioButton!.checked = true;
|
||||||
|
dialog.localClonePathTextBox!.value = 'invalidLocation';
|
||||||
|
const folderExistStub = sinon.stub(utils, 'directoryExist').resolves(false);
|
||||||
|
|
||||||
|
const validateResult = await dialog.validate();
|
||||||
|
const msg = constants.CloneParentDirectoryNotExistError(dialog.localClonePathTextBox!.value);
|
||||||
|
should.equal(dialog.dialogObject.message.text, msg);
|
||||||
|
should.equal(validateResult, false, 'Validation should fail because clone directory does not exist, but passed');
|
||||||
|
|
||||||
|
// validation should pass if directory exists
|
||||||
|
dialog.localClonePathTextBox!.value = os.tmpdir();
|
||||||
|
folderExistStub.resolves(true);
|
||||||
|
should.equal(await dialog.validate(), true, `Validation should pass because clone directory exists, but failed with: ${dialog.dialogObject.message.text}`);
|
||||||
|
});
|
||||||
|
|
||||||
test('Should validate workspace in onComplete when opening project', async function (): Promise<void> {
|
test('Should validate workspace in onComplete when opening project', async function (): Promise<void> {
|
||||||
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
||||||
@@ -69,7 +91,7 @@ suite('Open Existing Dialog', function (): void {
|
|||||||
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
||||||
await dialog.open();
|
await dialog.open();
|
||||||
|
|
||||||
dialog._filePathTextBox!.value = generateUniqueProjectFilePath('testproj');
|
dialog.filePathTextBox!.value = generateUniqueProjectFilePath('testproj');
|
||||||
should.doesNotThrow(async () => await dialog.onComplete());
|
should.doesNotThrow(async () => await dialog.onComplete());
|
||||||
|
|
||||||
workspaceServiceMock.setup(x => x.validateWorkspace()).throws(new Error('test error'));
|
workspaceServiceMock.setup(x => x.validateWorkspace()).throws(new Error('test error'));
|
||||||
@@ -84,16 +106,16 @@ suite('Open Existing Dialog', function (): void {
|
|||||||
|
|
||||||
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
||||||
await dialog.open();
|
await dialog.open();
|
||||||
should.equal(dialog._filePathTextBox!.value, '');
|
should.equal(dialog.filePathTextBox!.value, '');
|
||||||
await dialog.workspaceBrowse();
|
await dialog.workspaceBrowse();
|
||||||
should.equal(dialog._filePathTextBox!.value, '', 'Workspace file should not be set when no file is selected');
|
should.equal(dialog.filePathTextBox!.value, '', 'Workspace file should not be set when no file is selected');
|
||||||
|
|
||||||
sinon.restore();
|
sinon.restore();
|
||||||
const workspaceFile = vscode.Uri.file(generateUniqueWorkspaceFilePath());
|
const workspaceFile = vscode.Uri.file(generateUniqueWorkspaceFilePath());
|
||||||
sinon.stub(vscode.window, 'showOpenDialog').returns(Promise.resolve([workspaceFile]));
|
sinon.stub(vscode.window, 'showOpenDialog').returns(Promise.resolve([workspaceFile]));
|
||||||
await dialog.workspaceBrowse();
|
await dialog.workspaceBrowse();
|
||||||
should.equal(dialog._filePathTextBox!.value, workspaceFile.fsPath, 'Workspace file should get set');
|
should.equal(dialog.filePathTextBox!.value, workspaceFile.fsPath, 'Workspace file should get set');
|
||||||
should.equal(dialog._filePathTextBox?.value, workspaceFile.fsPath);
|
should.equal(dialog.filePathTextBox?.value, workspaceFile.fsPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('project browse', async function (): Promise<void> {
|
test('project browse', async function (): Promise<void> {
|
||||||
@@ -103,16 +125,16 @@ suite('Open Existing Dialog', function (): void {
|
|||||||
|
|
||||||
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
||||||
await dialog.open();
|
await dialog.open();
|
||||||
should.equal(dialog._filePathTextBox!.value, '');
|
should.equal(dialog.filePathTextBox!.value, '');
|
||||||
await dialog.projectBrowse();
|
await dialog.projectBrowse();
|
||||||
should.equal(dialog._filePathTextBox!.value, '', 'Project file should not be set when no file is selected');
|
should.equal(dialog.filePathTextBox!.value, '', 'Project file should not be set when no file is selected');
|
||||||
|
|
||||||
sinon.restore();
|
sinon.restore();
|
||||||
const projectFile = vscode.Uri.file(generateUniqueProjectFilePath('testproj'));
|
const projectFile = vscode.Uri.file(generateUniqueProjectFilePath('testproj'));
|
||||||
sinon.stub(vscode.window, 'showOpenDialog').returns(Promise.resolve([projectFile]));
|
sinon.stub(vscode.window, 'showOpenDialog').returns(Promise.resolve([projectFile]));
|
||||||
await dialog.projectBrowse();
|
await dialog.projectBrowse();
|
||||||
should.equal(dialog._filePathTextBox!.value, projectFile.fsPath, 'Project file should be set');
|
should.equal(dialog.filePathTextBox!.value, projectFile.fsPath, 'Project file should be set');
|
||||||
should.equal(dialog._filePathTextBox?.value, projectFile.fsPath);
|
should.equal(dialog.filePathTextBox?.value, projectFile.fsPath);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user