mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Load all data workspace projects directly from workspace (#15921)
* Load all projects directly from workspace * fixes * Remove relativity and fix tests * fix compile * PR comments * remove unused * distro
This commit is contained in:
@@ -68,10 +68,6 @@
|
|||||||
"category": "",
|
"category": "",
|
||||||
"icon": "$(close)"
|
"icon": "$(close)"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"command": "projects.removeProject",
|
|
||||||
"title": "%remove-project-command%"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"command": "projects.manageProject",
|
"command": "projects.manageProject",
|
||||||
"title": "%manage-project-command%"
|
"title": "%manage-project-command%"
|
||||||
@@ -112,10 +108,6 @@
|
|||||||
"command": "dataworkspace.close",
|
"command": "dataworkspace.close",
|
||||||
"when": "false"
|
"when": "false"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"command": "projects.removeProject",
|
|
||||||
"when": "false"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"command": "projects.openExisting"
|
"command": "projects.openExisting"
|
||||||
},
|
},
|
||||||
@@ -129,11 +121,6 @@
|
|||||||
"command": "projects.manageProject",
|
"command": "projects.manageProject",
|
||||||
"when": "view == dataworkspace.views.main && viewItem == databaseProject.itemType.project",
|
"when": "view == dataworkspace.views.main && viewItem == databaseProject.itemType.project",
|
||||||
"group": "0_projectsFirst@1"
|
"group": "0_projectsFirst@1"
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "projects.removeProject",
|
|
||||||
"when": "view == dataworkspace.views.main && viewItem == databaseProject.itemType.project",
|
|
||||||
"group": "9_dbProjectsLast@9"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,13 +13,7 @@ export const UnknownProjectsError = (projectFiles: string[]): string => { return
|
|||||||
export const SelectProjectFileActionName = localize('SelectProjectFileActionName', "Select");
|
export const SelectProjectFileActionName = localize('SelectProjectFileActionName', "Select");
|
||||||
export const AllProjectTypes = localize('AllProjectTypes', "All Project Types");
|
export const AllProjectTypes = localize('AllProjectTypes', "All Project Types");
|
||||||
export const ProviderNotFoundForProjectTypeError = (projectType: string): string => { return localize('UnknownProjectTypeError', "No provider was found for project type with id: '{0}'", projectType); };
|
export const ProviderNotFoundForProjectTypeError = (projectType: string): string => { return localize('UnknownProjectTypeError', "No provider was found for project type with id: '{0}'", projectType); };
|
||||||
export const WorkspaceRequiredMessage = localize('dataworkspace.workspaceRequiredMessage', "A workspace is required in order to use the project feature.");
|
export const RestartConfirmation = localize('dataworkspace.restartConfirmation', "Azure Data Studio needs to be restarted for the project to be created and added to the workspace, do this now?");
|
||||||
export const OpenWorkspace = localize('dataworkspace.openWorkspace', "Open Workspace…");
|
|
||||||
export const CreateWorkspaceConfirmation = localize('dataworkspace.createWorkspaceConfirmation', "A workspace will be created and opened in order to open the project. Azure Data Studio will restart and if there is a folder currently open, it will be closed.");
|
|
||||||
export const EnterWorkspaceConfirmation = localize('dataworkspace.enterWorkspaceConfirmation', "To open this workspace, Azure Data Studio will restart. If there is a workspace or folder currently open, it will be closed.");
|
|
||||||
export const WorkspaceContainsNotAddedProjects = localize('dataworkspace.workspaceContainsNotAddedProjects', "The current workspace contains one or more projects that have not been added to the workspace. Use the 'Open existing' dialog to add projects to the projects pane.");
|
|
||||||
export const LaunchOpenExisitingDialog = localize('dataworkspace.launchOpenExistingDialog', "Launch 'Open Existing' Dialog");
|
|
||||||
export const DoNotAskAgain = localize('dataworkspace.doNotAskAgain', "Don't Ask Again");
|
|
||||||
export const ProjectsFailedToLoad = localize('dataworkspace.projectsFailedToLoad', "Some projects failed to load. To view more details, [open the developer console](command:workbench.action.toggleDevTools)");
|
export const ProjectsFailedToLoad = localize('dataworkspace.projectsFailedToLoad', "Some projects failed to load. To view more details, [open the developer console](command:workbench.action.toggleDevTools)");
|
||||||
export const fileDoesNotExist = (name: string): string => { return localize('fileDoesNotExist', "File '{0}' doesn't exist", name); };
|
export const fileDoesNotExist = (name: string): string => { return localize('fileDoesNotExist', "File '{0}' doesn't exist", name); };
|
||||||
export const projectNameNull = localize('projectNameNull', "Project name is null");
|
export const projectNameNull = localize('projectNameNull', "Project name is null");
|
||||||
@@ -27,13 +21,8 @@ export const noPreviousData = (tableName: string): string => { return localize('
|
|||||||
export const gitCloneMessage = (url: string): string => { return localize('gitCloneMessage', "Cloning git repository '{0}'...", url); };
|
export const gitCloneMessage = (url: string): string => { return localize('gitCloneMessage', "Cloning git repository '{0}'...", url); };
|
||||||
export const gitCloneError = localize('gitCloneError', "Error during git clone. View git output for more details");
|
export const gitCloneError = localize('gitCloneError', "Error during git clone. View git output for more details");
|
||||||
|
|
||||||
// config settings
|
|
||||||
export const projectsConfigurationKey = 'projects';
|
|
||||||
export const showNotAddedProjectsMessageKey = 'showNotAddedProjectsInWorkspacePrompt';
|
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
export const OkButtonText = localize('dataworkspace.ok', "OK");
|
export const OkButtonText = localize('dataworkspace.ok', "OK");
|
||||||
export const CancelButtonText = localize('dataworkspace.cancel', "Cancel");
|
|
||||||
export const BrowseButtonText = localize('dataworkspace.browse', "Browse");
|
export const BrowseButtonText = localize('dataworkspace.browse', "Browse");
|
||||||
export const BrowseEllipsis = localize('dataworkspace.browseEllipsis', "Browse...");
|
export const BrowseEllipsis = localize('dataworkspace.browseEllipsis', "Browse...");
|
||||||
export const OpenButtonText = localize('dataworkspace.open', "Open");
|
export const OpenButtonText = localize('dataworkspace.open', "Open");
|
||||||
@@ -51,20 +40,14 @@ export const ProjectNamePlaceholder = localize('dataworkspace.projectNamePlaceho
|
|||||||
export const EnterProjectName = localize('dataworkspace.enterProjectName', "Enter Project Name");
|
export const EnterProjectName = localize('dataworkspace.enterProjectName', "Enter Project Name");
|
||||||
export const ProjectLocationTitle = localize('dataworkspace.projectLocationTitle', "Location");
|
export const ProjectLocationTitle = localize('dataworkspace.projectLocationTitle', "Location");
|
||||||
export const ProjectLocationPlaceholder = localize('dataworkspace.projectLocationPlaceholder', "Select location to create project");
|
export const ProjectLocationPlaceholder = localize('dataworkspace.projectLocationPlaceholder', "Select location to create project");
|
||||||
export const AddProjectToCurrentWorkspace = localize('dataworkspace.AddProjectToCurrentWorkspace', "This project will be added to the current workspace.");
|
|
||||||
export const NewWorkspaceWillBeCreated = localize('dataworkspace.NewWorkspaceWillBeCreated', "A workspace will be created for this project.");
|
|
||||||
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 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 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 ProjectDirectoryAlreadyExistErrorShort = (projectName: string) => { return localize('dataworkspace.projectDirectoryAlreadyExistErrorShort', "Directory '{0}' already exists in the selected location, please choose another", projectName); };
|
export const ProjectDirectoryAlreadyExistErrorShort = (projectName: string) => { return localize('dataworkspace.projectDirectoryAlreadyExistErrorShort', "Directory '{0}' already exists in the selected location, please choose another", projectName); };
|
||||||
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 SelectProjectType = localize('dataworkspace.selectProjectType', "Select Project Type");
|
export const SelectProjectType = localize('dataworkspace.selectProjectType', "Select Project Type");
|
||||||
export const SelectProjectLocation = localize('dataworkspace.selectProjectLocation', "Select Project Location");
|
export const SelectProjectLocation = localize('dataworkspace.selectProjectLocation', "Select Project Location");
|
||||||
export const NameCannotBeEmpty = localize('dataworkspace.nameCannotBeEmpty', "Name cannot be empty");
|
export const NameCannotBeEmpty = localize('dataworkspace.nameCannotBeEmpty', "Name cannot be empty");
|
||||||
//Open Existing Dialog
|
//Open Existing Dialog
|
||||||
export const OpenExistingDialogTitle = localize('dataworkspace.openExistingDialogTitle', "Open existing");
|
export const OpenExistingDialogTitle = localize('dataworkspace.openExistingDialogTitle', "Open Existing Project");
|
||||||
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 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");
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ export class DataWorkspaceExtension implements IExtension {
|
|||||||
constructor(private workspaceService: WorkspaceService) {
|
constructor(private workspaceService: WorkspaceService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
getProjectsInWorkspace(ext?: string): vscode.Uri[] {
|
getProjectsInWorkspace(ext?: string): Promise<vscode.Uri[]> {
|
||||||
return this.workspaceService.getProjectsInWorkspace(ext);
|
return this.workspaceService.getProjectsInWorkspace(ext);
|
||||||
}
|
}
|
||||||
|
|
||||||
addProjectsToWorkspace(projectFiles: vscode.Uri[], workspaceFilePath?: vscode.Uri): Promise<void> {
|
addProjectsToWorkspace(projectFiles: vscode.Uri[]): Promise<void> {
|
||||||
return this.workspaceService.addProjectsToWorkspace(projectFiles, workspaceFilePath);
|
return this.workspaceService.addProjectsToWorkspace(projectFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
showProjectsView(): void {
|
showProjectsView(): void {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export interface IWorkspaceService {
|
|||||||
/**
|
/**
|
||||||
* Gets the project files in current workspace
|
* Gets the project files in current workspace
|
||||||
*/
|
*/
|
||||||
getProjectsInWorkspace(): vscode.Uri[];
|
getProjectsInWorkspace(): Promise<vscode.Uri[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the project provider by project file
|
* Gets the project provider by project file
|
||||||
@@ -66,28 +66,20 @@ export interface IWorkspaceService {
|
|||||||
*/
|
*/
|
||||||
addProjectsToWorkspace(projectFiles: vscode.Uri[], workspaceFilePath?: vscode.Uri): Promise<void>;
|
addProjectsToWorkspace(projectFiles: vscode.Uri[], workspaceFilePath?: vscode.Uri): Promise<void>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the project from workspace
|
|
||||||
* @param projectFile The project file to be removed
|
|
||||||
*/
|
|
||||||
removeProject(projectFile: vscode.Uri): Promise<void>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new project from workspace
|
* Creates a new project from workspace
|
||||||
* @param name The name of the project
|
* @param name The name of the project
|
||||||
* @param location The location of the project
|
* @param location The location of the project
|
||||||
* @param projectTypeId The project type id
|
* @param projectTypeId The project type id
|
||||||
* @param workspaceFile The workspace file to create if a workspace isn't currently open
|
|
||||||
*/
|
*/
|
||||||
createProject(name: string, location: vscode.Uri, projectTypeId: string, workspaceFile?: vscode.Uri): Promise<vscode.Uri>;
|
createProject(name: string, location: vscode.Uri, projectTypeId: string): Promise<vscode.Uri>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clones git repository and adds projects to workspace
|
* Clones git repository and adds projects to workspace
|
||||||
* @param url The url to clone from
|
* @param url The url to clone from
|
||||||
* @param localClonePath local path to clone repository to
|
* @param localClonePath local path to clone repository to
|
||||||
* @param workspaceFile workspace file to add the projects to
|
|
||||||
*/
|
*/
|
||||||
gitCloneProject(url: string, localClonePath: string, workspaceFile: vscode.Uri): Promise<void>;
|
gitCloneProject(url: string, localClonePath: string): Promise<void>;
|
||||||
|
|
||||||
readonly isProjectProviderAvailable: boolean;
|
readonly isProjectProviderAvailable: boolean;
|
||||||
|
|
||||||
@@ -100,9 +92,4 @@ export interface IWorkspaceService {
|
|||||||
* Verify that a workspace is open or if one isn't, ask user to pick whether a workspace should be automatically created
|
* Verify that a workspace is open or if one isn't, ask user to pick whether a workspace should be automatically created
|
||||||
*/
|
*/
|
||||||
validateWorkspace(): Promise<boolean>;
|
validateWorkspace(): Promise<boolean>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows confirmation message that the extension host will be restarted and current workspace/file will be closed. If confirmed, the specified workspace will be entered.
|
|
||||||
*/
|
|
||||||
enterWorkspace(workspaceFile: vscode.Uri): Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,7 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import AdsTelemetryReporter from '@microsoft/ads-extension-telemetry';
|
import AdsTelemetryReporter from '@microsoft/ads-extension-telemetry';
|
||||||
import * as path from 'path';
|
|
||||||
import * as utils from './utils';
|
import * as utils from './utils';
|
||||||
import * as vscode from 'vscode';
|
|
||||||
|
|
||||||
const packageJson = require('../../package.json');
|
const packageJson = require('../../package.json');
|
||||||
|
|
||||||
@@ -14,28 +12,6 @@ let packageInfo = utils.getPackageInfo(packageJson)!;
|
|||||||
|
|
||||||
export const TelemetryReporter = new AdsTelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
|
export const TelemetryReporter = new AdsTelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
|
||||||
|
|
||||||
export function calculateRelativity(projectPath: string, workspacePath?: string): string {
|
|
||||||
workspacePath = workspacePath ?? vscode.workspace.workspaceFile?.fsPath;
|
|
||||||
|
|
||||||
if (!workspacePath) {
|
|
||||||
return 'noWorkspace';
|
|
||||||
}
|
|
||||||
|
|
||||||
const relativePath = path.relative(path.dirname(projectPath), path.dirname(workspacePath));
|
|
||||||
|
|
||||||
if (relativePath.length === 0) { // no path difference
|
|
||||||
return 'sameFolder';
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathParts = relativePath.split(path.sep);
|
|
||||||
|
|
||||||
if (pathParts.every(x => x === '..')) {
|
|
||||||
return 'directAncestor';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'other'; // sibling, cousin, descendant, etc.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export enum TelemetryViews {
|
export enum TelemetryViews {
|
||||||
WorkspaceTreePane = 'WorkspaceTreePane',
|
WorkspaceTreePane = 'WorkspaceTreePane',
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as vscode from 'vscode';
|
|
||||||
import type * as azdataType from 'azdata';
|
import type * as azdataType from 'azdata';
|
||||||
|
|
||||||
export async function directoryExist(directoryPath: string): Promise<boolean> {
|
export async function directoryExist(directoryPath: string): Promise<boolean> {
|
||||||
@@ -32,13 +31,6 @@ async function getFileStatus(path: string): Promise<fs.Stats | undefined> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* if the current workspace is untitled, the returned URI of vscode.workspace.workspaceFile will use the `untitled` scheme
|
|
||||||
*/
|
|
||||||
export function isCurrentWorkspaceUntitled(): boolean {
|
|
||||||
return !!vscode.workspace.workspaceFile && vscode.workspace.workspaceFile.scheme.toLowerCase() === 'untitled';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPackageInfo {
|
export interface IPackageInfo {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
|
|||||||
13
extensions/data-workspace/src/dataworkspace.d.ts
vendored
13
extensions/data-workspace/src/dataworkspace.d.ts
vendored
@@ -18,14 +18,13 @@ declare module 'dataworkspace' {
|
|||||||
* Returns all the projects in the workspace
|
* Returns all the projects in the workspace
|
||||||
* @param ext project extension to filter on. If this is passed in, this will only return projects with this file extension
|
* @param ext project extension to filter on. If this is passed in, this will only return projects with this file extension
|
||||||
*/
|
*/
|
||||||
getProjectsInWorkspace(ext?: string): vscode.Uri[];
|
getProjectsInWorkspace(ext?: string): Promise<vscode.Uri[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add projects to the workspace
|
* 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[], workspaceFilePath?: vscode.Uri): Promise<void>;
|
addProjectsToWorkspace(projectFiles: vscode.Uri[]): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change focus to Projects view
|
* Change focus to Projects view
|
||||||
@@ -53,12 +52,6 @@ declare module 'dataworkspace' {
|
|||||||
*/
|
*/
|
||||||
getProjectTreeDataProvider(projectFile: vscode.Uri): Promise<vscode.TreeDataProvider<any>>;
|
getProjectTreeDataProvider(projectFile: vscode.Uri): Promise<vscode.TreeDataProvider<any>>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify the project provider extension that the specified project file has been removed from the data workspace
|
|
||||||
* @param projectFile The Uri of the project file
|
|
||||||
*/
|
|
||||||
RemoveProject(projectFile: vscode.Uri): Promise<void>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param name Create a project
|
* @param name Create a project
|
||||||
|
|||||||
@@ -5,10 +5,7 @@
|
|||||||
|
|
||||||
import type * as azdataType from 'azdata';
|
import type * as azdataType from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as path from 'path';
|
import { getAzdataApi } from '../common/utils';
|
||||||
import * as constants from '../common/constants';
|
|
||||||
import { IconPathHelper } from '../common/iconHelper';
|
|
||||||
import { directoryExist, fileExist, getAzdataApi, isCurrentWorkspaceUntitled } from '../common/utils';
|
|
||||||
|
|
||||||
interface Deferred<T> {
|
interface Deferred<T> {
|
||||||
resolve: (result: T | Promise<T>) => void;
|
resolve: (result: T | Promise<T>) => void;
|
||||||
@@ -20,9 +17,6 @@ export abstract class DialogBase {
|
|||||||
public dialogObject: azdataType.window.Dialog;
|
public dialogObject: azdataType.window.Dialog;
|
||||||
protected initDialogComplete: Deferred<void> | undefined;
|
protected initDialogComplete: Deferred<void> | undefined;
|
||||||
protected initDialogPromise: Promise<void> = new Promise<void>((resolve, reject) => this.initDialogComplete = { resolve, reject });
|
protected initDialogPromise: Promise<void> = new Promise<void>((resolve, reject) => this.initDialogComplete = { resolve, reject });
|
||||||
protected workspaceDescriptionFormComponent: azdataType.FormComponent | undefined;
|
|
||||||
public workspaceInputBox: azdataType.InputBoxComponent | undefined;
|
|
||||||
protected workspaceInputFormComponent: azdataType.FormComponent | undefined;
|
|
||||||
|
|
||||||
constructor(dialogTitle: string, dialogName: string, okButtonText: string, dialogWidth: azdataType.window.DialogWidth = 600) {
|
constructor(dialogTitle: string, dialogName: string, okButtonText: string, dialogWidth: azdataType.window.DialogWidth = 600) {
|
||||||
this.dialogObject = getAzdataApi()!.window.createModelViewDialog(dialogTitle, dialogName, dialogWidth);
|
this.dialogObject = getAzdataApi()!.window.createModelViewDialog(dialogTitle, dialogName, dialogWidth);
|
||||||
@@ -82,106 +76,4 @@ export abstract class DialogBase {
|
|||||||
protected createHorizontalContainer(view: azdataType.ModelView, items: azdataType.Component[]): azdataType.FlexContainer {
|
protected createHorizontalContainer(view: azdataType.ModelView, items: azdataType.Component[]): azdataType.FlexContainer {
|
||||||
return view.modelBuilder.flexContainer().withItems(items, { CSSStyles: { 'margin-right': '5px', 'margin-bottom': '10px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
return view.modelBuilder.flexContainer().withItems(items, { CSSStyles: { 'margin-right': '5px', 'margin-bottom': '10px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates container with information on which workspace the project will be added to and where the workspace will be
|
|
||||||
* created if no workspace is currently open
|
|
||||||
* @param view
|
|
||||||
*/
|
|
||||||
protected createWorkspaceContainer(view: azdataType.ModelView): void {
|
|
||||||
const workspaceDescription = view.modelBuilder.text().withProperties<azdataType.TextComponentProperties>({
|
|
||||||
value: vscode.workspace.workspaceFile ? constants.AddProjectToCurrentWorkspace : constants.NewWorkspaceWillBeCreated,
|
|
||||||
CSSStyles: { 'margin-top': '3px', 'margin-bottom': '0px' }
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
const initialWorkspaceInputBoxValue = !!vscode.workspace.workspaceFile && !isCurrentWorkspaceUntitled() ? vscode.workspace.workspaceFile.fsPath : '';
|
|
||||||
|
|
||||||
this.workspaceInputBox = view.modelBuilder.inputBox().withProperties<azdataType.InputBoxProperties>({
|
|
||||||
ariaLabel: constants.WorkspaceLocationTitle,
|
|
||||||
width: constants.DefaultInputWidth,
|
|
||||||
enabled: !vscode.workspace.workspaceFile || isCurrentWorkspaceUntitled(), // want it editable if no saved workspace is open
|
|
||||||
value: initialWorkspaceInputBoxValue,
|
|
||||||
title: initialWorkspaceInputBoxValue // hovertext for if file path is too long to be seen in textbox
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
const browseFolderButton = view.modelBuilder.button().withProperties<azdataType.ButtonProperties>({
|
|
||||||
ariaLabel: constants.BrowseButtonText,
|
|
||||||
iconPath: IconPathHelper.folder,
|
|
||||||
height: '16px',
|
|
||||||
width: '18px'
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.register(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;
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (vscode.workspace.workspaceFile && !isCurrentWorkspaceUntitled()) {
|
|
||||||
this.workspaceInputFormComponent = {
|
|
||||||
component: this.workspaceInputBox
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// have browse button to help select where the workspace file should be created
|
|
||||||
const horizontalContainer = this.createHorizontalContainer(view, [this.workspaceInputBox, browseFolderButton]);
|
|
||||||
this.workspaceInputFormComponent = {
|
|
||||||
component: horizontalContainer
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.workspaceDescriptionFormComponent = {
|
|
||||||
title: constants.Workspace,
|
|
||||||
component: workspaceDescription,
|
|
||||||
required: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the workspace inputbox based on the passed in location and name if there isn't a workspace currently open
|
|
||||||
* @param location
|
|
||||||
* @param name
|
|
||||||
*/
|
|
||||||
protected updateWorkspaceInputbox(location: string, name: string): void {
|
|
||||||
if (!vscode.workspace.workspaceFile || isCurrentWorkspaceUntitled()) {
|
|
||||||
const fileLocation = location && name ? path.join(location, `${name}.code-workspace`) : '';
|
|
||||||
this.workspaceInputBox!.value = fileLocation;
|
|
||||||
this.workspaceInputBox!.title = fileLocation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async validateNewWorkspace(sameFolderAsNewProject: boolean): Promise<void> {
|
|
||||||
// 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 directoryExist(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 fileExist(this.workspaceInputBox!.value!);
|
|
||||||
if (workspaceFileExists) {
|
|
||||||
throw new Error(constants.WorkspaceFileAlreadyExistsError(this.workspaceInputBox!.value!));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ export class NewProjectDialog extends DialogBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async validate(): Promise<boolean> {
|
async validate(): Promise<boolean> {
|
||||||
|
if (await this.workspaceService.validateWorkspace() === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// the selected location should be an existing directory
|
// the selected location should be an existing directory
|
||||||
const parentDirectoryExists = await directoryExist(this.model.location);
|
const parentDirectoryExists = await directoryExist(this.model.location);
|
||||||
@@ -49,11 +52,6 @@ export class NewProjectDialog extends DialogBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.workspaceInputBox!.enabled) {
|
|
||||||
const sameFolderAsNewProject = path.join(this.model.location, this.model.name) === path.dirname(this.workspaceInputBox!.value!);
|
|
||||||
await this.validateNewWorkspace(sameFolderAsNewProject);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
@@ -64,15 +62,12 @@ export class NewProjectDialog extends DialogBase {
|
|||||||
|
|
||||||
override async onComplete(): Promise<void> {
|
override async onComplete(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const validateWorkspace = await this.workspaceService.validateWorkspace();
|
|
||||||
|
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.NewProjectDialog, TelemetryActions.NewProjectDialogCompleted)
|
TelemetryReporter.createActionEvent(TelemetryViews.NewProjectDialog, TelemetryActions.NewProjectDialogCompleted)
|
||||||
.withAdditionalProperties({ projectFileExtension: this.model.projectFileExtension, projectTemplateId: this.model.projectTypeId, workspaceValidationPassed: validateWorkspace.toString() })
|
.withAdditionalProperties({ projectFileExtension: this.model.projectFileExtension, projectTemplateId: this.model.projectTypeId })
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
if (validateWorkspace) {
|
await this.workspaceService.createProject(this.model.name, vscode.Uri.file(this.model.location), this.model.projectTypeId);
|
||||||
await this.workspaceService.createProject(this.model.name, vscode.Uri.file(this.model.location), this.model.projectTypeId, vscode.Uri.file(this.workspaceInputBox!.value!));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
|
|
||||||
@@ -129,8 +124,6 @@ export class NewProjectDialog extends DialogBase {
|
|||||||
this.register(projectNameTextBox.onTextChanged(() => {
|
this.register(projectNameTextBox.onTextChanged(() => {
|
||||||
this.model.name = projectNameTextBox.value!;
|
this.model.name = projectNameTextBox.value!;
|
||||||
projectNameTextBox.updateProperty('title', projectNameTextBox.value);
|
projectNameTextBox.updateProperty('title', projectNameTextBox.value);
|
||||||
|
|
||||||
this.updateWorkspaceInputbox(path.join(this.model.location, this.model.name), this.model.name);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const locationTextBox = view.modelBuilder.inputBox().withProperties<azdataType.InputBoxProperties>({
|
const locationTextBox = view.modelBuilder.inputBox().withProperties<azdataType.InputBoxProperties>({
|
||||||
@@ -143,7 +136,6 @@ export class NewProjectDialog extends DialogBase {
|
|||||||
this.register(locationTextBox.onTextChanged(() => {
|
this.register(locationTextBox.onTextChanged(() => {
|
||||||
this.model.location = locationTextBox.value!;
|
this.model.location = locationTextBox.value!;
|
||||||
locationTextBox.updateProperty('title', locationTextBox.value);
|
locationTextBox.updateProperty('title', locationTextBox.value);
|
||||||
this.updateWorkspaceInputbox(path.join(this.model.location, this.model.name), this.model.name);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const browseFolderButton = view.modelBuilder.button().withProperties<azdataType.ButtonProperties>({
|
const browseFolderButton = view.modelBuilder.button().withProperties<azdataType.ButtonProperties>({
|
||||||
@@ -165,12 +157,8 @@ export class NewProjectDialog extends DialogBase {
|
|||||||
const selectedFolder = folderUris[0].fsPath;
|
const selectedFolder = folderUris[0].fsPath;
|
||||||
locationTextBox.value = selectedFolder;
|
locationTextBox.value = selectedFolder;
|
||||||
this.model.location = selectedFolder;
|
this.model.location = selectedFolder;
|
||||||
|
|
||||||
this.updateWorkspaceInputbox(path.join(this.model.location, this.model.name), this.model.name);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.createWorkspaceContainer(view);
|
|
||||||
|
|
||||||
const form = view.modelBuilder.formContainer().withFormItems([
|
const form = view.modelBuilder.formContainer().withFormItems([
|
||||||
{
|
{
|
||||||
title: constants.TypeTitle,
|
title: constants.TypeTitle,
|
||||||
@@ -185,9 +173,7 @@ export class NewProjectDialog extends DialogBase {
|
|||||||
title: constants.ProjectLocationTitle,
|
title: constants.ProjectLocationTitle,
|
||||||
required: true,
|
required: true,
|
||||||
component: this.createHorizontalContainer(view, [locationTextBox, browseFolderButton])
|
component: this.createHorizontalContainer(view, [locationTextBox, browseFolderButton])
|
||||||
},
|
}
|
||||||
this.workspaceDescriptionFormComponent!,
|
|
||||||
this.workspaceInputFormComponent!
|
|
||||||
]).component();
|
]).component();
|
||||||
await view.initializeModel(form);
|
await view.initializeModel(form);
|
||||||
this.initDialogComplete?.resolve();
|
this.initDialogComplete?.resolve();
|
||||||
|
|||||||
@@ -5,17 +5,15 @@
|
|||||||
|
|
||||||
import type * as azdataType from 'azdata';
|
import type * as azdataType from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
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 { directoryExist, 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 { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry';
|
||||||
import { defaultProjectSaveLocation } from '../common/projectLocationHelper';
|
import { defaultProjectSaveLocation } from '../common/projectLocationHelper';
|
||||||
|
|
||||||
export class OpenExistingDialog extends DialogBase {
|
export class OpenExistingDialog extends DialogBase {
|
||||||
public targetTypeRadioCardGroup: azdataType.RadioCardGroupComponent | undefined;
|
|
||||||
public filePathTextBox: azdataType.InputBoxComponent | undefined;
|
public filePathTextBox: azdataType.InputBoxComponent | undefined;
|
||||||
public filePathAndButtonComponent: azdataType.FormComponent | undefined;
|
public filePathAndButtonComponent: azdataType.FormComponent | undefined;
|
||||||
public gitRepoTextBoxComponent: azdataType.FormComponent | undefined;
|
public gitRepoTextBoxComponent: azdataType.FormComponent | undefined;
|
||||||
@@ -26,17 +24,7 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
public locationRadioButtonFormComponent: azdataType.FormComponent | undefined;
|
public locationRadioButtonFormComponent: azdataType.FormComponent | undefined;
|
||||||
public formBuilder: azdataType.FormBuilder | undefined;
|
public formBuilder: azdataType.FormBuilder | undefined;
|
||||||
|
|
||||||
private _targetTypes = [
|
constructor(private workspaceService: IWorkspaceService) {
|
||||||
{
|
|
||||||
name: constants.Project,
|
|
||||||
icon: this.extensionContext.asAbsolutePath('images/Open_existing_Project.svg')
|
|
||||||
}, {
|
|
||||||
name: constants.Workspace,
|
|
||||||
icon: this.extensionContext.asAbsolutePath('images/Open_existing_Workspace.svg')
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
constructor(private workspaceService: IWorkspaceService, private extensionContext: vscode.ExtensionContext) {
|
|
||||||
super(constants.OpenExistingDialogTitle, 'OpenProject', constants.OpenButtonText);
|
super(constants.OpenExistingDialogTitle, 'OpenProject', constants.OpenButtonText);
|
||||||
|
|
||||||
// dialog launched from Welcome message button (only visible when no current workspace) vs. "add project" button
|
// dialog launched from Welcome message button (only visible when no current workspace) vs. "add project" button
|
||||||
@@ -47,27 +35,15 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
|
|
||||||
async validate(): Promise<boolean> {
|
async validate(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
// the selected location should be an existing directory
|
if (await this.workspaceService.validateWorkspace() === false) {
|
||||||
if (this.targetTypeRadioCardGroup?.selectedCardId === constants.Project) {
|
return false;
|
||||||
if (this.localRadioButton?.checked) {
|
|
||||||
await this.validateFile(this.filePathTextBox!.value!, constants.Project.toLowerCase());
|
|
||||||
} else {
|
|
||||||
await this.validateClonePath(<string>this.localClonePathTextBox!.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.workspaceInputBox!.enabled) {
|
|
||||||
await this.validateNewWorkspace(false);
|
|
||||||
}
|
|
||||||
} else if (this.targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.localRadioButton?.checked) {
|
||||||
|
await this.validateFile(this.filePathTextBox!.value!, constants.Project.toLowerCase());
|
||||||
|
} else {
|
||||||
|
await this.validateClonePath(<string>this.localClonePathTextBox!.value);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
@@ -94,55 +70,27 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
|
|
||||||
override async onComplete(): Promise<void> {
|
override async onComplete(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (this.targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) {
|
// save datapoint now because it'll get set to new value during validateWorkspace()
|
||||||
// capture that workspace was selected, also if there's already an open workspace that's being replaced
|
const telemetryProps: any = { hasWorkspaceOpen: (vscode.workspace.workspaceFile !== undefined).toString() };
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.OpeningWorkspace)
|
|
||||||
.withAdditionalProperties({ hasWorkspaceOpen: (vscode.workspace.workspaceFile !== undefined).toString() })
|
let addProjectsPromise: Promise<void>;
|
||||||
|
|
||||||
|
if (this.remoteGitRepoRadioButton!.checked) {
|
||||||
|
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.GitClone)
|
||||||
|
.withAdditionalProperties({ selectedTarget: 'project' })
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
if (this.remoteGitRepoRadioButton!.checked) {
|
addProjectsPromise = this.workspaceService.gitCloneProject((<azdataType.InputBoxComponent>this.gitRepoTextBoxComponent?.component).value!, this.localClonePathTextBox!.value!);
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.GitClone)
|
|
||||||
.withAdditionalProperties({ selectedTarget: 'workspace' })
|
|
||||||
.send();
|
|
||||||
|
|
||||||
// show git output channel
|
|
||||||
vscode.commands.executeCommand('git.showOutput');
|
|
||||||
// after this executes, the git extension will show a popup asking if you want to enter the workspace
|
|
||||||
await vscode.commands.executeCommand('git.clone', (<azdataType.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()
|
telemetryProps.cancelled = 'false';
|
||||||
const telemetryProps: any = { hasWorkspaceOpen: (vscode.workspace.workspaceFile !== undefined).toString() };
|
addProjectsPromise = this.workspaceService.addProjectsToWorkspace([vscode.Uri.file(this.filePathTextBox!.value!)]);
|
||||||
|
|
||||||
const validateWorkspace = await this.workspaceService.validateWorkspace();
|
|
||||||
let addProjectsPromise: Promise<void>;
|
|
||||||
|
|
||||||
if (this.remoteGitRepoRadioButton!.checked) {
|
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.GitClone)
|
|
||||||
.withAdditionalProperties({ selectedTarget: 'project' })
|
|
||||||
.send();
|
|
||||||
|
|
||||||
addProjectsPromise = this.workspaceService.gitCloneProject((<azdataType.InputBoxComponent>this.gitRepoTextBoxComponent?.component).value!, this.localClonePathTextBox!.value!, vscode.Uri.file(this.workspaceInputBox!.value!));
|
|
||||||
} else {
|
|
||||||
if (validateWorkspace) {
|
|
||||||
telemetryProps.workspaceProjectRelativity = calculateRelativity(this.filePathTextBox!.value!, this.workspaceInputBox!.value!);
|
|
||||||
telemetryProps.cancelled = 'false';
|
|
||||||
addProjectsPromise = this.workspaceService.addProjectsToWorkspace([vscode.Uri.file(this.filePathTextBox!.value!)], vscode.Uri.file(this.workspaceInputBox!.value!));
|
|
||||||
} else {
|
|
||||||
telemetryProps.workspaceProjectRelativity = 'none';
|
|
||||||
telemetryProps.cancelled = 'true';
|
|
||||||
addProjectsPromise = this.workspaceService.addProjectsToWorkspace([vscode.Uri.file(this.filePathTextBox!.value!)], vscode.Uri.file(this.workspaceInputBox!.value!));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.OpeningProject)
|
|
||||||
.withAdditionalProperties(telemetryProps)
|
|
||||||
.send();
|
|
||||||
|
|
||||||
await addProjectsPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TelemetryReporter.createActionEvent(TelemetryViews.OpenExistingDialog, TelemetryActions.OpeningProject)
|
||||||
|
.withAdditionalProperties(telemetryProps)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
await addProjectsPromise;
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
vscode.window.showErrorMessage(err?.message ? err.message : err);
|
vscode.window.showErrorMessage(err?.message ? err.message : err);
|
||||||
@@ -150,32 +98,6 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async initialize(view: azdataType.ModelView): Promise<void> {
|
protected async initialize(view: azdataType.ModelView): Promise<void> {
|
||||||
this.targetTypeRadioCardGroup = view.modelBuilder.radioCardGroup().withProperties<azdataType.RadioCardGroupComponentProperties>({
|
|
||||||
cards: this._targetTypes.map((targetType) => {
|
|
||||||
return <azdataType.RadioCard>{
|
|
||||||
id: targetType.name,
|
|
||||||
label: targetType.name,
|
|
||||||
icon: targetType.icon,
|
|
||||||
descriptions: [
|
|
||||||
{
|
|
||||||
textValue: targetType.name,
|
|
||||||
textStyles: {
|
|
||||||
'font-size': '13px'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
iconHeight: '100px',
|
|
||||||
iconWidth: '100px',
|
|
||||||
cardWidth: '170px',
|
|
||||||
cardHeight: '170px',
|
|
||||||
ariaLabel: constants.TypeTitle,
|
|
||||||
width: '500px',
|
|
||||||
iconPosition: 'top',
|
|
||||||
selectedCardId: constants.Project
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this.localRadioButton = view.modelBuilder.radioButton().withProperties<azdataType.RadioButtonProperties>({
|
this.localRadioButton = view.modelBuilder.radioButton().withProperties<azdataType.RadioButtonProperties>({
|
||||||
name: 'location',
|
name: 'location',
|
||||||
label: constants.Local,
|
label: constants.Local,
|
||||||
@@ -186,7 +108,7 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
if (checked) {
|
if (checked) {
|
||||||
this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.gitRepoTextBoxComponent);
|
this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.gitRepoTextBoxComponent);
|
||||||
this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.localClonePathComponent);
|
this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.localClonePathComponent);
|
||||||
this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.filePathAndButtonComponent, 2);
|
this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.filePathAndButtonComponent, 1);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -207,8 +129,8 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
this.register(this.remoteGitRepoRadioButton.onDidChangeCheckedState(checked => {
|
this.register(this.remoteGitRepoRadioButton.onDidChangeCheckedState(checked => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.filePathAndButtonComponent);
|
this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.filePathAndButtonComponent);
|
||||||
this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.gitRepoTextBoxComponent, 2);
|
this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.gitRepoTextBoxComponent, 1);
|
||||||
this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.localClonePathComponent, 3);
|
this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.localClonePathComponent, 2);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -221,7 +143,6 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
|
|
||||||
this.register(gitRepoTextBox.onTextChanged(() => {
|
this.register(gitRepoTextBox.onTextChanged(() => {
|
||||||
gitRepoTextBox.updateProperty('title', this.localClonePathTextBox!.value!);
|
gitRepoTextBox.updateProperty('title', this.localClonePathTextBox!.value!);
|
||||||
this.updateWorkspaceInputbox(this.localClonePathTextBox!.value!, path.basename(gitRepoTextBox!.value!, '.git'));
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.gitRepoTextBoxComponent = {
|
this.gitRepoTextBoxComponent = {
|
||||||
@@ -238,7 +159,6 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
|
|
||||||
this.register(this.localClonePathTextBox.onTextChanged(() => {
|
this.register(this.localClonePathTextBox.onTextChanged(() => {
|
||||||
this.localClonePathTextBox!.updateProperty('title', this.localClonePathTextBox!.value!);
|
this.localClonePathTextBox!.updateProperty('title', this.localClonePathTextBox!.value!);
|
||||||
this.updateWorkspaceInputbox(this.localClonePathTextBox!.value!, path.basename(gitRepoTextBox!.value!, '.git'));
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const localClonePathBrowseFolderButton = view.modelBuilder.button().withProperties<azdataType.ButtonProperties>({
|
const localClonePathBrowseFolderButton = view.modelBuilder.button().withProperties<azdataType.ButtonProperties>({
|
||||||
@@ -262,7 +182,6 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
const selectedFolder = folderUris[0].fsPath;
|
const selectedFolder = folderUris[0].fsPath;
|
||||||
this.localClonePathTextBox!.value = selectedFolder;
|
this.localClonePathTextBox!.value = selectedFolder;
|
||||||
this.localClonePathTextBox!.updateProperty('title', this.localClonePathTextBox!.value);
|
this.localClonePathTextBox!.updateProperty('title', this.localClonePathTextBox!.value);
|
||||||
this.updateWorkspaceInputbox(path.dirname(this.localClonePathTextBox!.value!), path.basename((<azdataType.InputBoxComponent>this.gitRepoTextBoxComponent?.component)!.value!, '.git'));
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.localClonePathComponent = {
|
this.localClonePathComponent = {
|
||||||
@@ -280,7 +199,6 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
|
|
||||||
this.register(this.filePathTextBox.onTextChanged(() => {
|
this.register(this.filePathTextBox.onTextChanged(() => {
|
||||||
this.filePathTextBox!.updateProperty('title', this.filePathTextBox!.value!);
|
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<azdataType.ButtonProperties>({
|
const localProjectBrowseFolderButton = view.modelBuilder.button().withProperties<azdataType.ButtonProperties>({
|
||||||
@@ -290,11 +208,7 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
height: '16px'
|
height: '16px'
|
||||||
}).component();
|
}).component();
|
||||||
this.register(localProjectBrowseFolderButton.onDidClick(async () => {
|
this.register(localProjectBrowseFolderButton.onDidClick(async () => {
|
||||||
if (this.targetTypeRadioCardGroup?.selectedCardId === constants.Project) {
|
await this.projectBrowse();
|
||||||
await this.projectBrowse();
|
|
||||||
} else if (this.targetTypeRadioCardGroup?.selectedCardId === constants.Workspace) {
|
|
||||||
await this.workspaceBrowse();
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const flexContainer = this.createHorizontalContainer(view, [this.filePathTextBox, localProjectBrowseFolderButton]);
|
const flexContainer = this.createHorizontalContainer(view, [this.filePathTextBox, localProjectBrowseFolderButton]);
|
||||||
@@ -303,74 +217,14 @@ export class OpenExistingDialog extends DialogBase {
|
|||||||
component: flexContainer
|
component: flexContainer
|
||||||
};
|
};
|
||||||
|
|
||||||
this.register(this.targetTypeRadioCardGroup.onSelectionChanged(({ cardId }) => {
|
|
||||||
if (cardId === constants.Project) {
|
|
||||||
this.filePathTextBox!.placeHolder = constants.ProjectFilePlaceholder;
|
|
||||||
|
|
||||||
if (this.remoteGitRepoRadioButton!.checked) {
|
|
||||||
this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.filePathAndButtonComponent);
|
|
||||||
this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.gitRepoTextBoxComponent, 2);
|
|
||||||
this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.localClonePathComponent, 3);
|
|
||||||
} else {
|
|
||||||
this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.gitRepoTextBoxComponent);
|
|
||||||
this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.localClonePathComponent);
|
|
||||||
this.formBuilder?.addFormItem(this.filePathAndButtonComponent!);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.formBuilder?.addFormItem(this.workspaceDescriptionFormComponent!);
|
|
||||||
this.formBuilder?.addFormItem(this.workspaceInputFormComponent!);
|
|
||||||
} else if (cardId === constants.Workspace) {
|
|
||||||
this.filePathTextBox!.placeHolder = constants.WorkspacePlaceholder;
|
|
||||||
|
|
||||||
this.formBuilder?.removeFormItem(this.workspaceDescriptionFormComponent!);
|
|
||||||
this.formBuilder?.removeFormItem(this.workspaceInputFormComponent!);
|
|
||||||
|
|
||||||
if (this.remoteGitRepoRadioButton!.checked) {
|
|
||||||
this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.filePathAndButtonComponent);
|
|
||||||
this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.gitRepoTextBoxComponent, 2);
|
|
||||||
this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.localClonePathComponent, 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear selected file textbox
|
|
||||||
this.filePathTextBox!.value = '';
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.createWorkspaceContainer(view);
|
|
||||||
|
|
||||||
this.formBuilder = view.modelBuilder.formContainer().withFormItems([
|
this.formBuilder = view.modelBuilder.formContainer().withFormItems([
|
||||||
{
|
|
||||||
title: constants.TypeTitle,
|
|
||||||
required: true,
|
|
||||||
component: this.targetTypeRadioCardGroup,
|
|
||||||
},
|
|
||||||
this.locationRadioButtonFormComponent,
|
this.locationRadioButtonFormComponent,
|
||||||
this.filePathAndButtonComponent,
|
this.filePathAndButtonComponent,
|
||||||
this.workspaceDescriptionFormComponent!,
|
|
||||||
this.workspaceInputFormComponent!
|
|
||||||
]);
|
]);
|
||||||
await view.initializeModel(this.formBuilder?.component());
|
await view.initializeModel(this.formBuilder?.component());
|
||||||
this.initDialogComplete?.resolve();
|
this.initDialogComplete?.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async workspaceBrowse(): Promise<void> {
|
|
||||||
const filters: { [name: string]: string[] } = { [constants.Workspace]: [constants.WorkspaceFileExtension.substring(1)] }; // filter already adds a period before the extension
|
|
||||||
const fileUris = await vscode.window.showOpenDialog({
|
|
||||||
canSelectFiles: true,
|
|
||||||
canSelectFolders: false,
|
|
||||||
canSelectMany: false,
|
|
||||||
openLabel: constants.SelectProjectFileActionName,
|
|
||||||
filters: filters
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!fileUris || fileUris.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const workspaceFilePath = fileUris[0].fsPath;
|
|
||||||
this.filePathTextBox!.value = workspaceFilePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async projectBrowse(): Promise<void> {
|
public async projectBrowse(): Promise<void> {
|
||||||
const filters: { [name: string]: string[] } = {};
|
const filters: { [name: string]: string[] } = {};
|
||||||
const projectTypes = await this.workspaceService.getAllProjectTypes();
|
const projectTypes = await this.workspaceService.getAllProjectTypes();
|
||||||
|
|||||||
@@ -17,22 +17,12 @@ import { getAzdataApi } from './common/utils';
|
|||||||
import { createNewProjectWithQuickpick } from './dialogs/newProjectQuickpick';
|
import { createNewProjectWithQuickpick } from './dialogs/newProjectQuickpick';
|
||||||
|
|
||||||
export async function activate(context: vscode.ExtensionContext): Promise<IExtension> {
|
export async function activate(context: vscode.ExtensionContext): Promise<IExtension> {
|
||||||
const workspaceService = new WorkspaceService(context);
|
const workspaceService = new WorkspaceService();
|
||||||
|
|
||||||
// this is not being awaited to not block the rest of activate function from running while loading any temp projects and
|
|
||||||
// checking for projects not added to the workspace yet
|
|
||||||
workspaceService.loadTempProjects().then(() => {
|
|
||||||
return workspaceService.checkForProjectsNotAddedToWorkspace();
|
|
||||||
}).catch(error => {
|
|
||||||
console.error(error);
|
|
||||||
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
|
|
||||||
});
|
|
||||||
|
|
||||||
context.subscriptions.push(vscode.workspace.onDidChangeWorkspaceFolders(() => {
|
|
||||||
workspaceService.checkForProjectsNotAddedToWorkspace();
|
|
||||||
}));
|
|
||||||
|
|
||||||
const workspaceTreeDataProvider = new WorkspaceTreeDataProvider(workspaceService);
|
const workspaceTreeDataProvider = new WorkspaceTreeDataProvider(workspaceService);
|
||||||
|
context.subscriptions.push(vscode.workspace.onDidChangeWorkspaceFolders(() => {
|
||||||
|
workspaceTreeDataProvider.refresh();
|
||||||
|
}));
|
||||||
const dataWorkspaceExtension = new DataWorkspaceExtension(workspaceService);
|
const dataWorkspaceExtension = new DataWorkspaceExtension(workspaceService);
|
||||||
context.subscriptions.push(vscode.window.registerTreeDataProvider('dataworkspace.views.main', workspaceTreeDataProvider));
|
context.subscriptions.push(vscode.window.registerTreeDataProvider('dataworkspace.views.main', workspaceTreeDataProvider));
|
||||||
context.subscriptions.push(vscode.extensions.onDidChange(() => {
|
context.subscriptions.push(vscode.extensions.onDidChange(() => {
|
||||||
@@ -51,7 +41,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<IExten
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
context.subscriptions.push(vscode.commands.registerCommand('projects.openExisting', async () => {
|
context.subscriptions.push(vscode.commands.registerCommand('projects.openExisting', async () => {
|
||||||
const dialog = new OpenExistingDialog(workspaceService, context);
|
const dialog = new OpenExistingDialog(workspaceService);
|
||||||
await dialog.open();
|
await dialog.open();
|
||||||
|
|
||||||
}));
|
}));
|
||||||
@@ -64,9 +54,6 @@ export async function activate(context: vscode.ExtensionContext): Promise<IExten
|
|||||||
vscode.commands.executeCommand('workbench.action.closeFolder');
|
vscode.commands.executeCommand('workbench.action.closeFolder');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
context.subscriptions.push(vscode.commands.registerCommand('projects.removeProject', async (treeItem: WorkspaceTreeItem) => {
|
|
||||||
await workspaceService.removeProject(vscode.Uri.file(treeItem.element.project.projectFilePath));
|
|
||||||
}));
|
|
||||||
context.subscriptions.push(vscode.commands.registerCommand('projects.manageProject', async (treeItem: WorkspaceTreeItem) => {
|
context.subscriptions.push(vscode.commands.registerCommand('projects.manageProject', async (treeItem: WorkspaceTreeItem) => {
|
||||||
const dashboard = new ProjectDashboard(workspaceService, treeItem);
|
const dashboard = new ProjectDashboard(workspaceService, treeItem);
|
||||||
await dashboard.showDashboard();
|
await dashboard.showDashboard();
|
||||||
|
|||||||
@@ -12,62 +12,13 @@ import * as glob from 'fast-glob';
|
|||||||
import { IWorkspaceService } from '../common/interfaces';
|
import { IWorkspaceService } from '../common/interfaces';
|
||||||
import { ProjectProviderRegistry } from '../common/projectProviderRegistry';
|
import { ProjectProviderRegistry } from '../common/projectProviderRegistry';
|
||||||
import Logger from '../common/logger';
|
import Logger from '../common/logger';
|
||||||
import { TelemetryReporter, TelemetryViews, calculateRelativity, TelemetryActions } from '../common/telemetry';
|
import { TelemetryReporter, TelemetryViews, TelemetryActions } from '../common/telemetry';
|
||||||
import { getAzdataApi, isCurrentWorkspaceUntitled } from '../common/utils';
|
|
||||||
|
|
||||||
const WorkspaceConfigurationName = 'dataworkspace';
|
|
||||||
const ProjectsConfigurationName = 'projects';
|
|
||||||
const TempProject = 'tempProject';
|
|
||||||
|
|
||||||
export class WorkspaceService implements IWorkspaceService {
|
export class WorkspaceService implements IWorkspaceService {
|
||||||
private _onDidWorkspaceProjectsChange: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
|
private _onDidWorkspaceProjectsChange: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
|
||||||
readonly onDidWorkspaceProjectsChange: vscode.Event<void> = this._onDidWorkspaceProjectsChange?.event;
|
readonly onDidWorkspaceProjectsChange: vscode.Event<void> = this._onDidWorkspaceProjectsChange?.event;
|
||||||
|
|
||||||
constructor(private _context: vscode.ExtensionContext) {
|
constructor() { }
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load any temp project that needed to be loaded before ADS was restarted
|
|
||||||
* which would happen if a workspace was created in order open or create a project
|
|
||||||
*/
|
|
||||||
async loadTempProjects(): Promise<void> {
|
|
||||||
const tempProjects: string[] | undefined = this._context.globalState.get(TempProject) ?? undefined;
|
|
||||||
|
|
||||||
if (tempProjects && vscode.workspace.workspaceFile) {
|
|
||||||
// add project to workspace now that the workspace has been created and saved
|
|
||||||
for (let project of tempProjects) {
|
|
||||||
await this.addProjectsToWorkspace([vscode.Uri.file(<string>project)]);
|
|
||||||
}
|
|
||||||
await this._context.globalState.update(TempProject, undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new workspace in the same folder as the project. Because ADS gets restarted when
|
|
||||||
* a new workspace is created and opened, the project needs to be saved as the temp project that will be loaded
|
|
||||||
* when the extension gets restarted
|
|
||||||
* @param projectFileFsPath project to add to the workspace
|
|
||||||
*/
|
|
||||||
async CreateNewWorkspaceForProject(projectFileFsPath: string, workspaceFile: vscode.Uri | undefined): Promise<void> {
|
|
||||||
// create workspace
|
|
||||||
const projectFolder = vscode.Uri.file(path.dirname(projectFileFsPath));
|
|
||||||
const azdataApi = getAzdataApi();
|
|
||||||
if (azdataApi) {
|
|
||||||
// save temp project
|
|
||||||
await this._context.globalState.update(TempProject, [projectFileFsPath]);
|
|
||||||
if (isCurrentWorkspaceUntitled()) {
|
|
||||||
vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders!.length, null, { uri: projectFolder });
|
|
||||||
await azdataApi.workspace.saveAndEnterWorkspace(workspaceFile!);
|
|
||||||
} else {
|
|
||||||
await azdataApi.workspace.createAndEnterWorkspace(projectFolder, workspaceFile);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// In VS Code we don't have access to the workspace APIs exposed by ADS and so can't actually create a new saved workspace.
|
|
||||||
// Instead we'll just always call this, which will either add it to the existing untitled workspace or create a new
|
|
||||||
// untitled workspace which the user can then save later on as they wish.
|
|
||||||
vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders?.length || 0, null, { uri: projectFolder });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get isProjectProviderAvailable(): boolean {
|
get isProjectProviderAvailable(): boolean {
|
||||||
for (const extension of vscode.extensions.all) {
|
for (const extension of vscode.extensions.all) {
|
||||||
@@ -83,8 +34,8 @@ export class WorkspaceService implements IWorkspaceService {
|
|||||||
* Verify that a workspace is open or that if one isn't, it's ok to create a workspace and restart ADS
|
* Verify that a workspace is open or that if one isn't, it's ok to create a workspace and restart ADS
|
||||||
*/
|
*/
|
||||||
async validateWorkspace(): Promise<boolean> {
|
async validateWorkspace(): Promise<boolean> {
|
||||||
if (!vscode.workspace.workspaceFile || isCurrentWorkspaceUntitled()) {
|
if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) {
|
||||||
const result = await vscode.window.showWarningMessage(constants.CreateWorkspaceConfirmation, { modal: true }, constants.OkButtonText);
|
const result = await vscode.window.showWarningMessage(constants.RestartConfirmation, { modal: true }, constants.OkButtonText);
|
||||||
if (result === constants.OkButtonText) {
|
if (result === constants.OkButtonText) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -96,33 +47,12 @@ export class WorkspaceService implements IWorkspaceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async addProjectsToWorkspace(projectFiles: vscode.Uri[]): Promise<void> {
|
||||||
* Shows confirmation message that the ADS will be restarted and current workspace/file will be closed. If confirmed, the specified workspace will be entered.
|
|
||||||
* @param workspaceFile
|
|
||||||
*/
|
|
||||||
async enterWorkspace(workspaceFile: vscode.Uri): Promise<void> {
|
|
||||||
const result = await vscode.window.showWarningMessage(constants.EnterWorkspaceConfirmation, { modal: true }, constants.OkButtonText);
|
|
||||||
if (result === constants.OkButtonText) {
|
|
||||||
await getAzdataApi()?.workspace.enterWorkspace(workspaceFile);
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async addProjectsToWorkspace(projectFiles: vscode.Uri[], workspaceFilePath?: vscode.Uri): Promise<void> {
|
|
||||||
if (!projectFiles || projectFiles.length === 0) {
|
if (!projectFiles || projectFiles.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// a workspace needs to be open to add projects
|
const currentProjects: vscode.Uri[] = await this.getProjectsInWorkspace();
|
||||||
if (!vscode.workspace.workspaceFile || isCurrentWorkspaceUntitled()) {
|
|
||||||
await this.CreateNewWorkspaceForProject(projectFiles[0].fsPath, workspaceFilePath);
|
|
||||||
|
|
||||||
// this won't get hit since ADS will get restarted, but helps with testing
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentProjects: vscode.Uri[] = this.getProjectsInWorkspace();
|
|
||||||
const newWorkspaceFolders: string[] = [];
|
const newWorkspaceFolders: string[] = [];
|
||||||
let newProjectFileAdded = false;
|
let newProjectFileAdded = false;
|
||||||
for (const projectFile of projectFiles) {
|
for (const projectFile of projectFiles) {
|
||||||
@@ -132,7 +62,6 @@ export class WorkspaceService implements IWorkspaceService {
|
|||||||
|
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.WorkspaceTreePane, TelemetryActions.ProjectAddedToWorkspace)
|
TelemetryReporter.createActionEvent(TelemetryViews.WorkspaceTreePane, TelemetryActions.ProjectAddedToWorkspace)
|
||||||
.withAdditionalProperties({
|
.withAdditionalProperties({
|
||||||
workspaceProjectRelativity: calculateRelativity(projectFile.fsPath),
|
|
||||||
projectType: path.extname(projectFile.fsPath)
|
projectType: path.extname(projectFile.fsPath)
|
||||||
}).send();
|
}).send();
|
||||||
|
|
||||||
@@ -148,14 +77,12 @@ export class WorkspaceService implements IWorkspaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (newProjectFileAdded) {
|
if (newProjectFileAdded) {
|
||||||
// Save the new set of projects to the workspace configuration.
|
|
||||||
await this.setWorkspaceConfigurationValue(ProjectsConfigurationName, currentProjects.map(project => this.toRelativePath(project)));
|
|
||||||
this._onDidWorkspaceProjectsChange.fire();
|
this._onDidWorkspaceProjectsChange.fire();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newWorkspaceFolders.length > 0) {
|
if (newWorkspaceFolders.length > 0) {
|
||||||
// second parameter is null means don't remove any workspace folders
|
// Add to the end of the workspace folders to avoid a restart of the extension host if we can
|
||||||
vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders!.length, null, ...(newWorkspaceFolders.map(folder => ({ uri: vscode.Uri.file(folder) }))));
|
vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders?.length || 0, undefined, ...(newWorkspaceFolders.map(folder => ({ uri: vscode.Uri.file(folder) }))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,8 +95,12 @@ export class WorkspaceService implements IWorkspaceService {
|
|||||||
return projectTypes;
|
return projectTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
getProjectsInWorkspace(ext?: string): vscode.Uri[] {
|
async getProjectsInWorkspace(ext?: string): Promise<vscode.Uri[]> {
|
||||||
let projects = vscode.workspace.workspaceFile ? this.getWorkspaceConfigurationValue<string[]>(ProjectsConfigurationName).map(project => this.toUri(project)) : [];
|
const projectPromises = vscode.workspace.workspaceFolders?.map(f => this.getAllProjectsInFolder(f.uri));
|
||||||
|
if (!projectPromises) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let projects = (await Promise.all(projectPromises)).reduce((prev, curr) => prev.concat(curr), []);
|
||||||
|
|
||||||
// filter by specified extension
|
// filter by specified extension
|
||||||
if (ext) {
|
if (ext) {
|
||||||
@@ -179,57 +110,12 @@ export class WorkspaceService implements IWorkspaceService {
|
|||||||
return projects;
|
return projects;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check for projects that are in the workspace folders but have not been added to the workspace through the dialog or by editing the .code-workspace file
|
|
||||||
*/
|
|
||||||
async checkForProjectsNotAddedToWorkspace(): Promise<void> {
|
|
||||||
const config = vscode.workspace.getConfiguration(constants.projectsConfigurationKey);
|
|
||||||
|
|
||||||
// only check if the user hasn't selected not to show this prompt again
|
|
||||||
if (!config[constants.showNotAddedProjectsMessageKey]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// look for any projects that haven't been added to the workspace
|
|
||||||
const projectsInWorkspace = this.getProjectsInWorkspace();
|
|
||||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
|
||||||
|
|
||||||
if (!workspaceFolders) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const folder of workspaceFolders) {
|
|
||||||
const results = await this.getAllProjectsInFolder(folder.uri);
|
|
||||||
|
|
||||||
let containsNotAddedProject = false;
|
|
||||||
for (const projFile of results) {
|
|
||||||
// if any of the found projects aren't already in the workspace's projects, we can stop checking and show the info message
|
|
||||||
if (!projectsInWorkspace.find(p => p.fsPath === projFile)) {
|
|
||||||
containsNotAddedProject = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (containsNotAddedProject) {
|
|
||||||
const result = await vscode.window.showInformationMessage(constants.WorkspaceContainsNotAddedProjects, constants.LaunchOpenExisitingDialog, constants.DoNotAskAgain);
|
|
||||||
if (result === constants.LaunchOpenExisitingDialog) {
|
|
||||||
// open settings
|
|
||||||
await vscode.commands.executeCommand('projects.openExisting');
|
|
||||||
} else if (result === constants.DoNotAskAgain) {
|
|
||||||
await config.update(constants.showNotAddedProjectsMessageKey, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of all the supported projects in the folder
|
* Returns an array of all the supported projects in the folder
|
||||||
* @param folder folder to look look for projects
|
* @param folder folder to look look for projects
|
||||||
* @returns array of file paths of supported projects
|
* @returns array of file URIs for supported projects
|
||||||
*/
|
*/
|
||||||
async getAllProjectsInFolder(folder: vscode.Uri): Promise<string[]> {
|
async getAllProjectsInFolder(folder: vscode.Uri): Promise<vscode.Uri[]> {
|
||||||
// get the unique supported project extensions
|
// get the unique supported project extensions
|
||||||
const supportedProjectExtensions = [...new Set((await this.getAllProjectTypes()).map(p => { return p.projectFileExtension; }))];
|
const supportedProjectExtensions = [...new Set((await this.getAllProjectTypes()).map(p => { return p.projectFileExtension; }))];
|
||||||
|
|
||||||
@@ -241,7 +127,7 @@ export class WorkspaceService implements IWorkspaceService {
|
|||||||
const projFilter = supportedProjectExtensions.length > 1 ? path.posix.join(escapedPath, '**', `*.{${supportedProjectExtensions.toString()}}`) : path.posix.join(escapedPath, '**', `*.${supportedProjectExtensions[0]}`);
|
const projFilter = supportedProjectExtensions.length > 1 ? path.posix.join(escapedPath, '**', `*.{${supportedProjectExtensions.toString()}}`) : path.posix.join(escapedPath, '**', `*.${supportedProjectExtensions[0]}`);
|
||||||
|
|
||||||
// glob will return an array of file paths with forward slashes, so they need to be converted back if on windows
|
// glob will return an array of file paths with forward slashes, so they need to be converted back if on windows
|
||||||
return (await glob(projFilter)).map(p => path.resolve(p));
|
return (await glob(projFilter)).map(p => vscode.Uri.file(path.resolve(p)));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProjectProvider(projectFile: vscode.Uri): Promise<dataworkspace.IProjectProvider | undefined> {
|
async getProjectProvider(projectFile: vscode.Uri): Promise<dataworkspace.IProjectProvider | undefined> {
|
||||||
@@ -253,29 +139,11 @@ export class WorkspaceService implements IWorkspaceService {
|
|||||||
return ProjectProviderRegistry.getProviderByProjectExtension(projectType);
|
return ProjectProviderRegistry.getProviderByProjectExtension(projectType);
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeProject(projectFile: vscode.Uri): Promise<void> {
|
|
||||||
if (vscode.workspace.workspaceFile) {
|
|
||||||
const currentProjects: vscode.Uri[] = this.getProjectsInWorkspace();
|
|
||||||
const projectIdx = currentProjects.findIndex((p: vscode.Uri) => p.fsPath === projectFile.fsPath);
|
|
||||||
if (projectIdx !== -1) {
|
|
||||||
currentProjects.splice(projectIdx, 1);
|
|
||||||
|
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.WorkspaceTreePane, TelemetryActions.ProjectRemovedFromWorkspace)
|
|
||||||
.withAdditionalProperties({
|
|
||||||
projectType: path.extname(projectFile.fsPath)
|
|
||||||
}).send();
|
|
||||||
|
|
||||||
await this.setWorkspaceConfigurationValue(ProjectsConfigurationName, currentProjects.map(project => this.toRelativePath(project)));
|
|
||||||
this._onDidWorkspaceProjectsChange.fire();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async createProject(name: string, location: vscode.Uri, projectTypeId: string, workspaceFile?: vscode.Uri): Promise<vscode.Uri> {
|
async createProject(name: string, location: vscode.Uri, projectTypeId: string, workspaceFile?: vscode.Uri): Promise<vscode.Uri> {
|
||||||
const provider = ProjectProviderRegistry.getProviderByProjectType(projectTypeId);
|
const provider = ProjectProviderRegistry.getProviderByProjectType(projectTypeId);
|
||||||
if (provider) {
|
if (provider) {
|
||||||
const projectFile = await provider.createProject(name, location, projectTypeId);
|
const projectFile = await provider.createProject(name, location, projectTypeId);
|
||||||
this.addProjectsToWorkspace([projectFile], workspaceFile);
|
this.addProjectsToWorkspace([projectFile]);
|
||||||
this._onDidWorkspaceProjectsChange.fire();
|
this._onDidWorkspaceProjectsChange.fire();
|
||||||
return projectFile;
|
return projectFile;
|
||||||
} else {
|
} else {
|
||||||
@@ -283,7 +151,7 @@ export class WorkspaceService implements IWorkspaceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async gitCloneProject(url: string, localClonePath: string, workspaceFile: vscode.Uri): Promise<void> {
|
async gitCloneProject(url: string, localClonePath: string): Promise<void> {
|
||||||
const gitApi: git.API = (<git.GitExtension>vscode.extensions.getExtension('vscode.git')!.exports).getAPI(1);
|
const gitApi: git.API = (<git.GitExtension>vscode.extensions.getExtension('vscode.git')!.exports).getAPI(1);
|
||||||
const opts = {
|
const opts = {
|
||||||
location: vscode.ProgressLocation.Notification,
|
location: vscode.ProgressLocation.Notification,
|
||||||
@@ -300,8 +168,8 @@ export class WorkspaceService implements IWorkspaceService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// get all the project files in the cloned repo and add them to workspace
|
// get all the project files in the cloned repo and add them to workspace
|
||||||
const repoProjects = (await this.getAllProjectsInFolder(vscode.Uri.file(repositoryPath))).map(p => { return vscode.Uri.file(p); });
|
const repoProjects = (await this.getAllProjectsInFolder(vscode.Uri.file(repositoryPath)));
|
||||||
this.addProjectsToWorkspace(repoProjects, workspaceFile);
|
this.addProjectsToWorkspace(repoProjects);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
vscode.window.showErrorMessage(constants.gitCloneError);
|
vscode.window.showErrorMessage(constants.gitCloneError);
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -344,29 +212,4 @@ export class WorkspaceService implements IWorkspaceService {
|
|||||||
ProjectProviderRegistry.registerProvider(extension.exports, extension.id);
|
ProjectProviderRegistry.registerProvider(extension.exports, extension.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getWorkspaceConfigurationValue<T>(configurationName: string): T {
|
|
||||||
return vscode.workspace.getConfiguration(WorkspaceConfigurationName).get(configurationName) as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setWorkspaceConfigurationValue(configurationName: string, value: any): Promise<void> {
|
|
||||||
await vscode.workspace.getConfiguration(WorkspaceConfigurationName).update(configurationName, value, vscode.ConfigurationTarget.Workspace);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the relative path to the workspace file
|
|
||||||
* @param filePath the absolute path
|
|
||||||
*/
|
|
||||||
private toRelativePath(filePath: vscode.Uri): string {
|
|
||||||
return path.relative(path.dirname(vscode.workspace.workspaceFile!.path!), filePath.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the Uri of the given relative path
|
|
||||||
* @param relativePath the relative path
|
|
||||||
*/
|
|
||||||
private toUri(relativePath: string): vscode.Uri {
|
|
||||||
const fullPath = path.join(path.dirname(vscode.workspace.workspaceFile!.path!), relativePath);
|
|
||||||
return vscode.Uri.file(fullPath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
import * as should from 'should';
|
|
||||||
import * as TypeMoq from 'typemoq';
|
|
||||||
import * as os from 'os';
|
|
||||||
import * as path from 'path';
|
|
||||||
import * as sinon from 'sinon';
|
|
||||||
import * as utils from '../../common/utils';
|
|
||||||
import * as constants from '../../common/constants';
|
|
||||||
import { NewProjectDialog } from '../../dialogs/newProjectDialog';
|
|
||||||
import { WorkspaceService } from '../../services/workspaceService';
|
|
||||||
import { testProjectType } from '../testUtils';
|
|
||||||
|
|
||||||
suite('DialogBase - workspace validation', function (): void {
|
|
||||||
// DialogBase is an abstract class, so we'll just use a NewProjectDialog to test the common base class functions
|
|
||||||
let dialog: NewProjectDialog;
|
|
||||||
|
|
||||||
this.beforeEach(async () => {
|
|
||||||
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
|
||||||
workspaceServiceMock.setup(x => x.getAllProjectTypes()).returns(() => Promise.resolve([testProjectType]));
|
|
||||||
|
|
||||||
dialog = new NewProjectDialog(workspaceServiceMock.object);
|
|
||||||
await dialog.open();
|
|
||||||
|
|
||||||
dialog.model.name = `TestProject_${new Date().getTime()}`;
|
|
||||||
dialog.model.location = os.tmpdir();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.afterEach(() => {
|
|
||||||
sinon.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Should validate new workspace location missing file extension', async function (): Promise<void> {
|
|
||||||
dialog.workspaceInputBox!.value = 'test';
|
|
||||||
await should(dialog.validateNewWorkspace(false)).be.rejectedWith(constants.WorkspaceFileInvalidError(dialog.workspaceInputBox!.value));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Should validate new workspace location with invalid location', async function (): Promise<void> {
|
|
||||||
// use invalid folder
|
|
||||||
dialog.workspaceInputBox!.value = 'invalidLocation/test.code-workspace';
|
|
||||||
await should(dialog.validateNewWorkspace(false)).be.rejectedWith(constants.WorkspaceParentDirectoryNotExistError(path.dirname(dialog.workspaceInputBox!.value)));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Should validate new workspace location that already exists', async function (): Promise<void> {
|
|
||||||
// use already existing workspace
|
|
||||||
const fileExistStub = sinon.stub(utils, 'fileExist');
|
|
||||||
fileExistStub.resolves(true);
|
|
||||||
const existingWorkspaceFilePath = path.join(os.tmpdir(), `${dialog.model.name}.code-workspace`);
|
|
||||||
dialog.workspaceInputBox!.value = existingWorkspaceFilePath;
|
|
||||||
await should(dialog.validateNewWorkspace(false)).be.rejectedWith(constants.WorkspaceFileAlreadyExistsError(existingWorkspaceFilePath));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Should validate new workspace location that is valid', async function (): Promise<void> {
|
|
||||||
// same folder as the project should be valid even if the project folder isn't created yet
|
|
||||||
dialog.workspaceInputBox!.value = path.join(dialog.model.location, dialog.model.name, 'test.code-workspace');
|
|
||||||
await should(dialog.validateNewWorkspace(true)).not.be.rejected();
|
|
||||||
|
|
||||||
// a workspace not in the same folder as the project should also be valid
|
|
||||||
dialog.workspaceInputBox!.value = path.join(os.tmpdir(), `TestWorkspace_${new Date().getTime()}.code-workspace`);
|
|
||||||
await should(dialog.validateNewWorkspace(false)).not.be.rejected();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -7,7 +7,6 @@ import * as should from 'should';
|
|||||||
import * as TypeMoq from 'typemoq';
|
import * as TypeMoq from 'typemoq';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as vscode from 'vscode';
|
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { NewProjectDialog } from '../../dialogs/newProjectDialog';
|
import { NewProjectDialog } from '../../dialogs/newProjectDialog';
|
||||||
@@ -28,7 +27,6 @@ suite('New Project Dialog', function (): void {
|
|||||||
|
|
||||||
dialog.model.name = 'TestProject';
|
dialog.model.name = 'TestProject';
|
||||||
dialog.model.location = '';
|
dialog.model.location = '';
|
||||||
dialog.workspaceInputBox!.value = 'test.code-workspace';
|
|
||||||
should.equal(await dialog.validate(), false, 'Validation should fail because the parent directory does not exist');
|
should.equal(await dialog.validate(), false, 'Validation should fail because the parent directory does not exist');
|
||||||
|
|
||||||
// create a folder with the same name
|
// create a folder with the same name
|
||||||
@@ -41,24 +39,5 @@ suite('New Project Dialog', function (): void {
|
|||||||
dialog.model.name = `TestProject_${new Date().getTime()}`;
|
dialog.model.name = `TestProject_${new Date().getTime()}`;
|
||||||
should.equal(await dialog.validate(), true, 'Validation should pass because name is unique and parent directory exists');
|
should.equal(await dialog.validate(), true, 'Validation should pass because name is unique and parent directory exists');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('Should validate workspace in onComplete', async function (): Promise<void> {
|
|
||||||
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
|
||||||
workspaceServiceMock.setup(x => x.validateWorkspace()).returns(() => Promise.resolve(true));
|
|
||||||
workspaceServiceMock.setup(x => x.getAllProjectTypes()).returns(() => Promise.resolve([testProjectType]));
|
|
||||||
|
|
||||||
const dialog = new NewProjectDialog(workspaceServiceMock.object);
|
|
||||||
await dialog.open();
|
|
||||||
|
|
||||||
dialog.model.name = 'TestProject';
|
|
||||||
dialog.model.location = '';
|
|
||||||
should.doesNotThrow(async () => await dialog.onComplete());
|
|
||||||
|
|
||||||
workspaceServiceMock.setup(x => x.validateWorkspace()).throws(new Error('test error'));
|
|
||||||
const spy = sinon.spy(vscode.window, 'showErrorMessage');
|
|
||||||
should.doesNotThrow(async () => await dialog.onComplete(), 'Error should be caught');
|
|
||||||
should(spy.calledOnce).be.true();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,23 +12,19 @@ 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';
|
||||||
import { OpenExistingDialog } from '../../dialogs/openExistingDialog';
|
import { OpenExistingDialog } from '../../dialogs/openExistingDialog';
|
||||||
import { createProjectFile, generateUniqueProjectFilePath, generateUniqueWorkspaceFilePath, testProjectType } from '../testUtils';
|
import { createProjectFile, generateUniqueProjectFilePath, testProjectType } from '../testUtils';
|
||||||
|
|
||||||
suite('Open Existing Dialog', function (): void {
|
suite('Open Existing Dialog', function (): void {
|
||||||
const mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
|
||||||
|
|
||||||
this.afterEach(() => {
|
this.afterEach(() => {
|
||||||
sinon.restore();
|
sinon.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Should validate project file exists', async function (): Promise<void> {
|
test('Should validate project file exists', async function (): Promise<void> {
|
||||||
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
||||||
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
const dialog = new OpenExistingDialog(workspaceServiceMock.object);
|
||||||
await dialog.open();
|
await dialog.open();
|
||||||
|
|
||||||
dialog.targetTypeRadioCardGroup?.updateProperty( 'selectedCardId', constants.Project);
|
|
||||||
dialog.filePathTextBox!.value = 'nonExistentProjectFile';
|
dialog.filePathTextBox!.value = 'nonExistentProjectFile';
|
||||||
dialog.workspaceInputBox!.value = 'test.code-workspace';
|
|
||||||
|
|
||||||
const validateResult = await dialog.validate();
|
const validateResult = await dialog.validate();
|
||||||
|
|
||||||
@@ -41,32 +37,12 @@ suite('Open Existing Dialog', function (): void {
|
|||||||
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}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Should validate workspace file exists', 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.filePathTextBox!.value = 'nonExistentWorkspaceFile';
|
|
||||||
const fileExistStub = sinon.stub(utils, 'fileExist').resolves(false);
|
|
||||||
|
|
||||||
const validateResult = await dialog.validate();
|
|
||||||
const msg = constants.FileNotExistError('workspace', 'nonExistentWorkspaceFile');
|
|
||||||
should.equal(dialog.dialogObject.message.text, msg);
|
|
||||||
should.equal(validateResult, false, 'Validation should fail because workspace file does not exist, but passed');
|
|
||||||
|
|
||||||
// validation should pass if workspace file exists
|
|
||||||
dialog.filePathTextBox!.value = generateUniqueWorkspaceFilePath();
|
|
||||||
fileExistStub.resolves(true);
|
|
||||||
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> {
|
test('Should validate workspace git clone location', async function (): Promise<void> {
|
||||||
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
||||||
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
const dialog = new OpenExistingDialog(workspaceServiceMock.object);
|
||||||
await dialog.open();
|
await dialog.open();
|
||||||
|
|
||||||
dialog.targetTypeRadioCardGroup?.updateProperty( 'selectedCardId', constants.Workspace);
|
|
||||||
dialog.localRadioButton!.checked = false;
|
dialog.localRadioButton!.checked = false;
|
||||||
dialog.remoteGitRepoRadioButton!.checked = true;
|
dialog.remoteGitRepoRadioButton!.checked = true;
|
||||||
dialog.localClonePathTextBox!.value = 'invalidLocation';
|
dialog.localClonePathTextBox!.value = 'invalidLocation';
|
||||||
@@ -74,7 +50,7 @@ suite('Open Existing Dialog', function (): void {
|
|||||||
|
|
||||||
const validateResult = await dialog.validate();
|
const validateResult = await dialog.validate();
|
||||||
const msg = constants.CloneParentDirectoryNotExistError(dialog.localClonePathTextBox!.value);
|
const msg = constants.CloneParentDirectoryNotExistError(dialog.localClonePathTextBox!.value);
|
||||||
should.equal(dialog.dialogObject.message.text, msg);
|
should.equal(dialog.dialogObject.message.text, msg, 'Dialog message should be correct');
|
||||||
should.equal(validateResult, false, 'Validation should fail because clone directory does not exist, but passed');
|
should.equal(validateResult, false, 'Validation should fail because clone directory does not exist, but passed');
|
||||||
|
|
||||||
// validation should pass if directory exists
|
// validation should pass if directory exists
|
||||||
@@ -83,58 +59,22 @@ suite('Open Existing Dialog', function (): void {
|
|||||||
should.equal(await dialog.validate(), true, `Validation should pass because clone directory exists, but failed with: ${dialog.dialogObject.message.text}`);
|
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> {
|
|
||||||
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
|
||||||
workspaceServiceMock.setup(x => x.validateWorkspace()).returns(() => Promise.resolve(true));
|
|
||||||
workspaceServiceMock.setup(x => x.addProjectsToWorkspace(TypeMoq.It.isAny())).returns(() => Promise.resolve());
|
|
||||||
|
|
||||||
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
|
||||||
await dialog.open();
|
|
||||||
|
|
||||||
dialog.filePathTextBox!.value = generateUniqueProjectFilePath('testproj');
|
|
||||||
should.doesNotThrow(async () => await dialog.onComplete());
|
|
||||||
|
|
||||||
workspaceServiceMock.setup(x => x.validateWorkspace()).throws(new Error('test error'));
|
|
||||||
const spy = sinon.spy(vscode.window, 'showErrorMessage');
|
|
||||||
should.doesNotThrow(async () => await dialog.onComplete(), 'Error should be caught');
|
|
||||||
should(spy.calledOnce).be.true();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('workspace browse', async function (): Promise<void> {
|
|
||||||
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
|
||||||
sinon.stub(vscode.window, 'showOpenDialog').returns(Promise.resolve([]));
|
|
||||||
|
|
||||||
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
|
||||||
await dialog.open();
|
|
||||||
should.equal(dialog.filePathTextBox!.value, '');
|
|
||||||
await dialog.workspaceBrowse();
|
|
||||||
should.equal(dialog.filePathTextBox!.value, '', 'Workspace file should not be set when no file is selected');
|
|
||||||
|
|
||||||
sinon.restore();
|
|
||||||
const workspaceFile = vscode.Uri.file(generateUniqueWorkspaceFilePath());
|
|
||||||
sinon.stub(vscode.window, 'showOpenDialog').returns(Promise.resolve([workspaceFile]));
|
|
||||||
await dialog.workspaceBrowse();
|
|
||||||
should.equal(dialog.filePathTextBox!.value, workspaceFile.fsPath, 'Workspace file should get set');
|
|
||||||
should.equal(dialog.filePathTextBox?.value, workspaceFile.fsPath);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('project browse', async function (): Promise<void> {
|
test('project browse', async function (): Promise<void> {
|
||||||
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
|
||||||
workspaceServiceMock.setup(x => x.getAllProjectTypes()).returns(() => Promise.resolve([testProjectType]));
|
workspaceServiceMock.setup(x => x.getAllProjectTypes()).returns(() => Promise.resolve([testProjectType]));
|
||||||
sinon.stub(vscode.window, 'showOpenDialog').returns(Promise.resolve([]));
|
sinon.stub(vscode.window, 'showOpenDialog').returns(Promise.resolve([]));
|
||||||
|
|
||||||
const dialog = new OpenExistingDialog(workspaceServiceMock.object, mockExtensionContext.object);
|
const dialog = new OpenExistingDialog(workspaceServiceMock.object);
|
||||||
await dialog.open();
|
await dialog.open();
|
||||||
should.equal(dialog.filePathTextBox!.value, '');
|
should.equal(dialog.filePathTextBox!.value ?? '', '', 'Project file should initially be empty');
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,6 @@ export function createProjectProvider(projectTypes: IProjectType[], projectActio
|
|||||||
const treeDataProvider = new MockTreeDataProvider();
|
const treeDataProvider = new MockTreeDataProvider();
|
||||||
const projectProvider: IProjectProvider = {
|
const projectProvider: IProjectProvider = {
|
||||||
supportedProjectTypes: projectTypes,
|
supportedProjectTypes: projectTypes,
|
||||||
RemoveProject: (projectFile: vscode.Uri): Promise<void> => {
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
getProjectTreeDataProvider: (projectFile: vscode.Uri): Promise<vscode.TreeDataProvider<any>> => {
|
getProjectTreeDataProvider: (projectFile: vscode.Uri): Promise<vscode.TreeDataProvider<any>> => {
|
||||||
return Promise.resolve(treeDataProvider);
|
return Promise.resolve(treeDataProvider);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,7 +31,3 @@ export async function createProjectFile(fileExt: string, contents?: string): Pro
|
|||||||
export function generateUniqueProjectFilePath(fileExt: string): string {
|
export function generateUniqueProjectFilePath(fileExt: string): string {
|
||||||
return path.join(os.tmpdir(), `TestProject_${new Date().getTime()}.${fileExt}`);
|
return path.join(os.tmpdir(), `TestProject_${new Date().getTime()}.${fileExt}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateUniqueWorkspaceFilePath(): string {
|
|
||||||
return path.join(os.tmpdir(), `TestWorkspace_${new Date().getTime()}.code-workspace`);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
import 'mocha';
|
|
||||||
import * as os from 'os';
|
|
||||||
import * as path from 'path';
|
|
||||||
import should = require('should');
|
|
||||||
import { calculateRelativity } from '../common/telemetry';
|
|
||||||
|
|
||||||
suite('Utilities Tests', function (): void {
|
|
||||||
test('test CalculateRelativity', async () => {
|
|
||||||
const root = os.platform() === 'win32' ? 'Z:\\' : '/';
|
|
||||||
const workspacePath = path.join(root, 'Source', 'Workspace', 'qwerty.code-workspace');
|
|
||||||
|
|
||||||
should.equal(calculateRelativity(path.join(root, 'Source', 'Workspace', 'qwerty.sqlproj'), workspacePath), 'sameFolder');
|
|
||||||
should.equal(calculateRelativity(path.join(root, 'Source', 'Workspace', 'qwerty', 'asdfg', 'qwerty.sqlproj'), workspacePath), 'directAncestor');
|
|
||||||
should.equal(calculateRelativity(path.join(root, 'Users', 'BillG', 'qwerty.sqlproj'), workspacePath), 'other');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -5,42 +5,13 @@
|
|||||||
|
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as azdata from 'azdata';
|
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import * as should from 'should';
|
import * as should from 'should';
|
||||||
import * as path from 'path';
|
|
||||||
import * as TypeMoq from 'typemoq';
|
|
||||||
import * as constants from '../common/constants';
|
import * as constants from '../common/constants';
|
||||||
import * as utils from '../common/utils';
|
|
||||||
import { WorkspaceService } from '../services/workspaceService';
|
import { WorkspaceService } from '../services/workspaceService';
|
||||||
import { ProjectProviderRegistry } from '../common/projectProviderRegistry';
|
import { ProjectProviderRegistry } from '../common/projectProviderRegistry';
|
||||||
import { createProjectProvider } from './projectProviderRegistry.test';
|
import { createProjectProvider } from './projectProviderRegistry.test';
|
||||||
|
|
||||||
const DefaultWorkspaceFilePath = '/test/folder/ws.code-workspace';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a stub for vscode.workspace.workspaceFile
|
|
||||||
* @param workspaceFilePath The workspace file to return
|
|
||||||
*/
|
|
||||||
function stubWorkspaceFile(workspaceFilePath: string | undefined): sinon.SinonStub {
|
|
||||||
return sinon.stub(vscode.workspace, 'workspaceFile').value(workspaceFilePath ? vscode.Uri.file(workspaceFilePath) : undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a stub for vscode.workspace.getConfiguration
|
|
||||||
* @param returnValue the configuration value to return
|
|
||||||
*/
|
|
||||||
function stubGetConfigurationValue(getStub?: sinon.SinonStub, updateStub?: sinon.SinonStub): sinon.SinonStub {
|
|
||||||
return sinon.stub(vscode.workspace, 'getConfiguration').returns({
|
|
||||||
get: (configurationName: string) => {
|
|
||||||
return getStub!(configurationName);
|
|
||||||
},
|
|
||||||
update: (section: string, value: any, configurationTarget?: vscode.ConfigurationTarget | boolean, overrideInLanguage?: boolean) => {
|
|
||||||
updateStub!(section, value, configurationTarget);
|
|
||||||
}
|
|
||||||
} as vscode.WorkspaceConfiguration);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a stub for vscode.extensions.all
|
* Create a stub for vscode.extensions.all
|
||||||
* @param extensions extensions to return
|
* @param extensions extensions to return
|
||||||
@@ -64,41 +35,29 @@ function createMockExtension(id: string, isActive: boolean, projectTypes: string
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExtensionGlobalMemento extends vscode.Memento {
|
suite('WorkspaceService', function (): void {
|
||||||
setKeysForSync(keys: string[]): void;
|
let service = new WorkspaceService();
|
||||||
}
|
|
||||||
|
|
||||||
suite('WorkspaceService Tests', function (): void {
|
|
||||||
const mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
|
||||||
const mockGlobalState = TypeMoq.Mock.ofType<ExtensionGlobalMemento>();
|
|
||||||
mockGlobalState.setup(x => x.update(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve());
|
|
||||||
mockExtensionContext.setup(x => x.globalState).returns(() => mockGlobalState.object);
|
|
||||||
|
|
||||||
const service = new WorkspaceService(mockExtensionContext.object);
|
|
||||||
|
|
||||||
this.afterEach(() => {
|
this.afterEach(() => {
|
||||||
sinon.restore();
|
sinon.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test getProjectsInWorkspace', async () => {
|
test('getProjectsInWorkspace', async () => {
|
||||||
// No workspace is loaded
|
// No workspace is loaded
|
||||||
stubWorkspaceFile(undefined);
|
|
||||||
let projects = await service.getProjectsInWorkspace();
|
let projects = await service.getProjectsInWorkspace();
|
||||||
should.strictEqual(projects.length, 0, 'no projects should be returned when no workspace is loaded');
|
should.strictEqual(projects.length, 0, 'no projects should be returned when no workspace is loaded');
|
||||||
|
|
||||||
// from this point on, workspace is loaded
|
|
||||||
stubWorkspaceFile(DefaultWorkspaceFilePath);
|
|
||||||
|
|
||||||
// No projects are present in the workspace file
|
// No projects are present in the workspace file
|
||||||
const getConfigurationStub = stubGetConfigurationValue(sinon.stub().returns([]));
|
const workspaceFoldersStub = sinon.stub(vscode.workspace, 'workspaceFolders').value([]);
|
||||||
projects = await service.getProjectsInWorkspace();
|
projects = await service.getProjectsInWorkspace();
|
||||||
should.strictEqual(projects.length, 0, 'no projects should be returned when projects are present in the workspace file');
|
should.strictEqual(projects.length, 0, 'no projects should be returned when projects are present in the workspace file');
|
||||||
getConfigurationStub.restore();
|
workspaceFoldersStub.restore();
|
||||||
|
|
||||||
// Projects are present
|
// Projects are present
|
||||||
stubGetConfigurationValue(sinon.stub().returns(['abc.sqlproj', 'folder1/abc1.sqlproj', 'folder2/abc2.sqlproj']));
|
sinon.stub(vscode.workspace, 'workspaceFolders').value([{ uri: vscode.Uri.file('')}]);
|
||||||
|
sinon.stub(service, 'getAllProjectsInFolder').resolves([vscode.Uri.file('/test/folder/abc.sqlproj'), vscode.Uri.file('/test/folder/folder1/abc1.sqlproj'), vscode.Uri.file('/test/folder/folder2/abc2.sqlproj')]);
|
||||||
projects = await service.getProjectsInWorkspace();
|
projects = await service.getProjectsInWorkspace();
|
||||||
should.strictEqual(projects.length, 3, 'there should be 2 projects');
|
should.strictEqual(projects.length, 3, 'there should be 3 projects');
|
||||||
const project1 = vscode.Uri.file('/test/folder/abc.sqlproj');
|
const project1 = vscode.Uri.file('/test/folder/abc.sqlproj');
|
||||||
const project2 = vscode.Uri.file('/test/folder/folder1/abc1.sqlproj');
|
const project2 = vscode.Uri.file('/test/folder/folder1/abc1.sqlproj');
|
||||||
const project3 = vscode.Uri.file('/test/folder/folder2/abc2.sqlproj');
|
const project3 = vscode.Uri.file('/test/folder/folder2/abc2.sqlproj');
|
||||||
@@ -107,7 +66,7 @@ suite('WorkspaceService Tests', function (): void {
|
|||||||
should.strictEqual(projects[2].path, project3.path);
|
should.strictEqual(projects[2].path, project3.path);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test getAllProjectTypes', async () => {
|
test('getAllProjectTypes', async () => {
|
||||||
// extensions that are already activated
|
// extensions that are already activated
|
||||||
const extension1 = createMockExtension('ext1', true, ['csproj']); // with projects contribution
|
const extension1 = createMockExtension('ext1', true, ['csproj']); // with projects contribution
|
||||||
const extension2 = createMockExtension('ext2', true, []); // with empty projects contribution
|
const extension2 = createMockExtension('ext2', true, []); // with empty projects contribution
|
||||||
@@ -219,7 +178,7 @@ suite('WorkspaceService Tests', function (): void {
|
|||||||
should.strictEqual(consoleErrorStub.calledOnce, true, 'Logger.error should be called once');
|
should.strictEqual(consoleErrorStub.calledOnce, true, 'Logger.error should be called once');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test getProjectProvider', async () => {
|
test('getProjectProvider', async () => {
|
||||||
const extension1 = createMockExtension('ext1', true, ['csproj']);
|
const extension1 = createMockExtension('ext1', true, ['csproj']);
|
||||||
const extension2 = createMockExtension('ext2', false, ['sqlproj']);
|
const extension2 = createMockExtension('ext2', false, ['sqlproj']);
|
||||||
const extension3 = createMockExtension('ext3', false, ['dbproj']);
|
const extension3 = createMockExtension('ext3', false, ['dbproj']);
|
||||||
@@ -297,147 +256,64 @@ suite('WorkspaceService Tests', function (): void {
|
|||||||
should.strictEqual(extension3.activationStub.notCalled, true, 'the ext3.activate() should not have been called for csproj');
|
should.strictEqual(extension3.activationStub.notCalled, true, 'the ext3.activate() should not have been called for csproj');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test addProjectsToWorkspace', async () => {
|
test('addProjectsToWorkspace', async () => {
|
||||||
const processPath = (original: string): string => {
|
sinon.stub(service, 'getProjectsInWorkspace').resolves([vscode.Uri.file('folder/folder1/proj2.sqlproj')]);
|
||||||
return original.replace(/\//g, path.sep);
|
|
||||||
};
|
|
||||||
stubWorkspaceFile(DefaultWorkspaceFilePath);
|
|
||||||
const updateConfigurationStub = sinon.stub();
|
|
||||||
const getConfigurationStub = sinon.stub().returns([processPath('folder1/proj2.sqlproj')]);
|
|
||||||
const onWorkspaceProjectsChangedStub = sinon.stub();
|
const onWorkspaceProjectsChangedStub = sinon.stub();
|
||||||
const showInformationMessageStub = sinon.stub(vscode.window, 'showInformationMessage');
|
const showInformationMessageStub = sinon.stub(vscode.window, 'showInformationMessage');
|
||||||
const onWorkspaceProjectsChangedDisposable = service.onDidWorkspaceProjectsChange(() => {
|
const onWorkspaceProjectsChangedDisposable = service.onDidWorkspaceProjectsChange(() => {
|
||||||
onWorkspaceProjectsChangedStub();
|
onWorkspaceProjectsChangedStub();
|
||||||
});
|
});
|
||||||
stubGetConfigurationValue(getConfigurationStub, updateConfigurationStub);
|
|
||||||
const asRelativeStub = sinon.stub(vscode.workspace, 'asRelativePath');
|
const asRelativeStub = sinon.stub(vscode.workspace, 'asRelativePath');
|
||||||
sinon.stub(vscode.workspace, 'workspaceFolders').value(['.']);
|
sinon.stub(vscode.workspace, 'workspaceFolders').value(['.']);
|
||||||
asRelativeStub.onFirstCall().returns(`proj1.sqlproj`);
|
asRelativeStub.onFirstCall().returns(`proj1.sqlproj`);
|
||||||
asRelativeStub.onSecondCall().returns(processPath('/test/other/proj3.sqlproj'));
|
asRelativeStub.onSecondCall().returns('other/proj3.sqlproj');
|
||||||
const updateWorkspaceFoldersStub = sinon.stub(vscode.workspace, 'updateWorkspaceFolders');
|
const updateWorkspaceFoldersStub = sinon.stub(vscode.workspace, 'updateWorkspaceFolders');
|
||||||
await service.addProjectsToWorkspace([
|
await service.addProjectsToWorkspace([
|
||||||
vscode.Uri.file('/test/folder/proj1.sqlproj'), // within the workspace folder
|
vscode.Uri.file('folder/proj1.sqlproj'), // within the workspace folder
|
||||||
vscode.Uri.file('/test/folder/folder1/proj2.sqlproj'), //already exists
|
vscode.Uri.file('folder/folder1/proj2.sqlproj'), //already exists
|
||||||
vscode.Uri.file('/test/other/proj3.sqlproj') // outside of workspace folder
|
vscode.Uri.file('other/proj3.sqlproj') // new workspace folder
|
||||||
]);
|
]);
|
||||||
should.strictEqual(updateConfigurationStub.calledOnce, true, 'update configuration should have been called once');
|
|
||||||
should.strictEqual(updateWorkspaceFoldersStub.calledOnce, true, 'updateWorkspaceFolders should have been called once');
|
should.strictEqual(updateWorkspaceFoldersStub.calledOnce, true, 'updateWorkspaceFolders should have been called once');
|
||||||
should.strictEqual(showInformationMessageStub.calledOnce, true, 'showInformationMessage should be called once');
|
should.strictEqual(showInformationMessageStub.calledOnce, true, 'showInformationMessage should be called once');
|
||||||
should(showInformationMessageStub.calledWith(constants.ProjectAlreadyOpened(processPath('/test/folder/folder1/proj2.sqlproj')))).be.true(`showInformationMessage not called with expected message '${constants.ProjectAlreadyOpened(processPath('/test/folder/folder1/proj2.sqlproj'))}' Actual '${showInformationMessageStub.getCall(0).args[0]}'`);
|
const expectedProjPath = vscode.Uri.file('folder/folder1/proj2.sqlproj').fsPath;
|
||||||
should.strictEqual(updateConfigurationStub.calledWith('projects', sinon.match.array.deepEquals([
|
should(showInformationMessageStub.calledWith(constants.ProjectAlreadyOpened(expectedProjPath))).be.true(`showInformationMessage not called with expected message '${constants.ProjectAlreadyOpened(expectedProjPath)}' Actual '${showInformationMessageStub.getCall(0).args[0]}'`);
|
||||||
processPath('folder1/proj2.sqlproj'),
|
should.strictEqual(updateWorkspaceFoldersStub.calledWith(1, undefined, sinon.match((arg) => {
|
||||||
processPath('proj1.sqlproj'),
|
return arg.uri.path === vscode.Uri.file('other').path;
|
||||||
processPath('../other/proj3.sqlproj')
|
|
||||||
]), vscode.ConfigurationTarget.Workspace), true, 'updateConfiguration parameters does not match expectation');
|
|
||||||
should.strictEqual(updateWorkspaceFoldersStub.calledWith(1, null, sinon.match((arg) => {
|
|
||||||
return arg.uri.path === '/test/other';
|
|
||||||
})), true, 'updateWorkspaceFolder parameters does not match expectation');
|
})), true, 'updateWorkspaceFolder parameters does not match expectation');
|
||||||
should.strictEqual(onWorkspaceProjectsChangedStub.calledOnce, true, 'the onDidWorkspaceProjectsChange event should have been fired');
|
should.strictEqual(onWorkspaceProjectsChangedStub.calledOnce, true, 'the onDidWorkspaceProjectsChange event should have been fired');
|
||||||
onWorkspaceProjectsChangedDisposable.dispose();
|
onWorkspaceProjectsChangedDisposable.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test addProjectsToWorkspace when no workspace open', async () => {
|
test('addProjectsToWorkspace when no workspace open', async () => {
|
||||||
stubWorkspaceFile(undefined);
|
|
||||||
const onWorkspaceProjectsChangedStub = sinon.stub();
|
const onWorkspaceProjectsChangedStub = sinon.stub();
|
||||||
const onWorkspaceProjectsChangedDisposable = service.onDidWorkspaceProjectsChange(() => {
|
const onWorkspaceProjectsChangedDisposable = service.onDidWorkspaceProjectsChange(() => {
|
||||||
onWorkspaceProjectsChangedStub();
|
onWorkspaceProjectsChangedStub();
|
||||||
});
|
});
|
||||||
const createWorkspaceStub = sinon.stub(azdata.workspace, 'createAndEnterWorkspace').resolves(undefined);
|
const updateWorkspaceFoldersStub = sinon.stub(vscode.workspace, 'updateWorkspaceFolders').returns(true);
|
||||||
|
|
||||||
await service.addProjectsToWorkspace([
|
await service.addProjectsToWorkspace([
|
||||||
vscode.Uri.file('/test/folder/proj1.sqlproj')
|
vscode.Uri.file('/test/folder/proj1.sqlproj')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
should.strictEqual(createWorkspaceStub.calledOnce, true, 'createAndEnterWorkspace should have been called once');
|
should.strictEqual(onWorkspaceProjectsChangedStub.calledOnce, true, 'the onDidWorkspaceProjectsChange event should have been fired');
|
||||||
should.strictEqual(onWorkspaceProjectsChangedStub.notCalled, true, 'the onDidWorkspaceProjectsChange event should not have been fired');
|
should.strictEqual(updateWorkspaceFoldersStub.calledOnce, true, 'updateWorkspaceFolders should have been called');
|
||||||
onWorkspaceProjectsChangedDisposable.dispose();
|
onWorkspaceProjectsChangedDisposable.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test addProjectsToWorkspace when untitled workspace is open', async () => {
|
test('addProjectsToWorkspace when untitled workspace is open', async () => {
|
||||||
stubWorkspaceFile(undefined);
|
sinon.stub(service, 'getProjectsInWorkspace').resolves([]);
|
||||||
const onWorkspaceProjectsChangedStub = sinon.stub();
|
const onWorkspaceProjectsChangedStub = sinon.stub();
|
||||||
const onWorkspaceProjectsChangedDisposable = service.onDidWorkspaceProjectsChange(() => {
|
const onWorkspaceProjectsChangedDisposable = service.onDidWorkspaceProjectsChange(() => {
|
||||||
onWorkspaceProjectsChangedStub();
|
onWorkspaceProjectsChangedStub();
|
||||||
});
|
});
|
||||||
const saveWorkspaceStub = sinon.stub(azdata.workspace, 'saveAndEnterWorkspace').resolves(undefined);
|
sinon.replaceGetter(vscode.workspace, 'workspaceFolders', () => [{ uri: vscode.Uri.file('folder1'), name: '', index: 0}]);
|
||||||
sinon.stub(utils, 'isCurrentWorkspaceUntitled').returns(true);
|
const updateWorkspaceFoldersStub = sinon.stub(vscode.workspace, 'updateWorkspaceFolders').returns(true);
|
||||||
sinon.stub(vscode.workspace, 'workspaceFolders').value(['folder1']);
|
|
||||||
|
|
||||||
await service.addProjectsToWorkspace([
|
await service.addProjectsToWorkspace([
|
||||||
vscode.Uri.file('/test/folder/proj1.sqlproj')
|
vscode.Uri.file('/test/folder/proj1.sqlproj')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
should.strictEqual(saveWorkspaceStub.calledOnce, true, 'saveAndEnterWorkspace should have been called once');
|
|
||||||
should.strictEqual(onWorkspaceProjectsChangedStub.notCalled, true, 'the onDidWorkspaceProjectsChange event should not have been fired');
|
|
||||||
onWorkspaceProjectsChangedDisposable.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('test loadTempProjects', async () => {
|
|
||||||
const processPath = (original: string): string => {
|
|
||||||
return original.replace(/\//g, path.sep);
|
|
||||||
};
|
|
||||||
stubWorkspaceFile('/test/folder/proj1.code-workspace');
|
|
||||||
const updateConfigurationStub = sinon.stub();
|
|
||||||
const getConfigurationStub = sinon.stub().returns([processPath('folder1/proj2.sqlproj')]);
|
|
||||||
const onWorkspaceProjectsChangedStub = sinon.stub();
|
|
||||||
const onWorkspaceProjectsChangedDisposable = service.onDidWorkspaceProjectsChange(() => {
|
|
||||||
onWorkspaceProjectsChangedStub();
|
|
||||||
});
|
|
||||||
stubGetConfigurationValue(getConfigurationStub, updateConfigurationStub);
|
|
||||||
sinon.stub(azdata.workspace, 'createAndEnterWorkspace').resolves(undefined);
|
|
||||||
sinon.stub(vscode.workspace, 'workspaceFolders').value(['folder1']);
|
|
||||||
mockGlobalState.setup(x => x.get(TypeMoq.It.isAny())).returns(() => [processPath('folder1/proj2.sqlproj')]);
|
|
||||||
|
|
||||||
await service.loadTempProjects();
|
|
||||||
|
|
||||||
should.strictEqual(onWorkspaceProjectsChangedStub.calledOnce, true, 'the onDidWorkspaceProjectsChange event should have been fired');
|
should.strictEqual(onWorkspaceProjectsChangedStub.calledOnce, true, 'the onDidWorkspaceProjectsChange event should have been fired');
|
||||||
|
should.strictEqual(updateWorkspaceFoldersStub.calledOnce, true, 'updateWorkspaceFolders should have been called');
|
||||||
onWorkspaceProjectsChangedDisposable.dispose();
|
onWorkspaceProjectsChangedDisposable.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test removeProject', async () => {
|
|
||||||
const processPath = (original: string): string => {
|
|
||||||
return original.replace(/\//g, path.sep);
|
|
||||||
};
|
|
||||||
stubWorkspaceFile(DefaultWorkspaceFilePath);
|
|
||||||
const updateConfigurationStub = sinon.stub();
|
|
||||||
const getConfigurationStub = sinon.stub().returns([processPath('folder1/proj2.sqlproj'), processPath('folder2/proj3.sqlproj')]);
|
|
||||||
const onWorkspaceProjectsChangedStub = sinon.stub();
|
|
||||||
const onWorkspaceProjectsChangedDisposable = service.onDidWorkspaceProjectsChange(() => {
|
|
||||||
onWorkspaceProjectsChangedStub();
|
|
||||||
});
|
|
||||||
stubGetConfigurationValue(getConfigurationStub, updateConfigurationStub);
|
|
||||||
await service.removeProject(vscode.Uri.file('/test/folder/folder1/proj2.sqlproj'));
|
|
||||||
should.strictEqual(updateConfigurationStub.calledWith('projects', sinon.match.array.deepEquals([
|
|
||||||
processPath('folder2/proj3.sqlproj')
|
|
||||||
]), vscode.ConfigurationTarget.Workspace), true, 'updateConfiguration parameters does not match expectation for remove project');
|
|
||||||
should.strictEqual(onWorkspaceProjectsChangedStub.calledOnce, true, 'the onDidWorkspaceProjectsChange event should have been fired');
|
|
||||||
onWorkspaceProjectsChangedDisposable.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('test checkForProjectsNotAddedToWorkspace', async () => {
|
|
||||||
const previousSetting = await vscode.workspace.getConfiguration(constants.projectsConfigurationKey)[constants.showNotAddedProjectsMessageKey];
|
|
||||||
await vscode.workspace.getConfiguration(constants.projectsConfigurationKey).update(constants.showNotAddedProjectsMessageKey, true, true);
|
|
||||||
|
|
||||||
sinon.stub(service, 'getProjectsInWorkspace').returns([vscode.Uri.file('abc.sqlproj'), vscode.Uri.file('folder1/abc1.sqlproj')]);
|
|
||||||
sinon.stub(vscode.workspace, 'workspaceFolders').value([{ uri: vscode.Uri.file('.') }]);
|
|
||||||
sinon.stub(service, 'getAllProjectTypes').resolves([{
|
|
||||||
projectFileExtension: 'sqlproj',
|
|
||||||
id: 'sql project',
|
|
||||||
displayName: 'sql project',
|
|
||||||
description: '',
|
|
||||||
icon: ''
|
|
||||||
}]);
|
|
||||||
const infoMessageStub = sinon.stub(vscode.window, 'showInformationMessage').resolves(<any>constants.DoNotAskAgain);
|
|
||||||
const getProjectsInwWorkspaceFolderStub = sinon.stub(service, 'getAllProjectsInFolder').resolves([vscode.Uri.file('abc.sqlproj').fsPath, vscode.Uri.file('folder1/abc1.sqlproj').fsPath]);
|
|
||||||
|
|
||||||
await service.checkForProjectsNotAddedToWorkspace();
|
|
||||||
should(infoMessageStub.notCalled).be.true('Should not have found projects not added to workspace');
|
|
||||||
|
|
||||||
// add a project to the workspace folder not added to the workspace yet
|
|
||||||
getProjectsInwWorkspaceFolderStub.resolves([vscode.Uri.file('abc.sqlproj').fsPath, vscode.Uri.file('folder1/abc1.sqlproj').fsPath, vscode.Uri.file('folder2/abc2.sqlproj').fsPath]);
|
|
||||||
await service.checkForProjectsNotAddedToWorkspace();
|
|
||||||
should(infoMessageStub.calledOnce).be.true('Should have found a project that was not added to the workspace');
|
|
||||||
|
|
||||||
await vscode.workspace.getConfiguration(constants.projectsConfigurationKey).update(constants.showNotAddedProjectsMessageKey, previousSetting, true);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,23 +7,13 @@ import { IDashboardTable, IProjectProvider, WorkspaceTreeItem } from 'dataworksp
|
|||||||
import 'mocha';
|
import 'mocha';
|
||||||
import * as should from 'should';
|
import * as should from 'should';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import * as TypeMoq from 'typemoq';
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { WorkspaceTreeDataProvider } from '../common/workspaceTreeDataProvider';
|
import { WorkspaceTreeDataProvider } from '../common/workspaceTreeDataProvider';
|
||||||
import { WorkspaceService } from '../services/workspaceService';
|
import { WorkspaceService } from '../services/workspaceService';
|
||||||
import { MockTreeDataProvider } from './projectProviderRegistry.test';
|
import { MockTreeDataProvider } from './projectProviderRegistry.test';
|
||||||
|
|
||||||
interface ExtensionGlobalMemento extends vscode.Memento {
|
|
||||||
setKeysForSync(keys: string[]): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
suite('workspaceTreeDataProvider Tests', function (): void {
|
suite('workspaceTreeDataProvider Tests', function (): void {
|
||||||
const mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
const workspaceService = new WorkspaceService();
|
||||||
const mockGlobalState = TypeMoq.Mock.ofType<ExtensionGlobalMemento>();
|
|
||||||
mockGlobalState.setup(x => x.update(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve());
|
|
||||||
mockExtensionContext.setup(x => x.globalState).returns(() => mockGlobalState.object);
|
|
||||||
|
|
||||||
const workspaceService = new WorkspaceService(mockExtensionContext.object);
|
|
||||||
const treeProvider = new WorkspaceTreeDataProvider(workspaceService);
|
const treeProvider = new WorkspaceTreeDataProvider(workspaceService);
|
||||||
|
|
||||||
this.afterEach(() => {
|
this.afterEach(() => {
|
||||||
@@ -63,7 +53,7 @@ suite('workspaceTreeDataProvider Tests', function (): void {
|
|||||||
};
|
};
|
||||||
const children = await treeProvider.getChildren(element);
|
const children = await treeProvider.getChildren(element);
|
||||||
should.strictEqual(children.length, 0, 'children count should be 0');
|
should.strictEqual(children.length, 0, 'children count should be 0');
|
||||||
should.strictEqual(getChildrenStub.calledWithExactly('obj1'), true, 'getChildren parameter should be obj1')
|
should.strictEqual(getChildrenStub.calledWithExactly('obj1'), true, 'getChildren parameter should be obj1');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test getChildren() for root element', async () => {
|
test('test getChildren() for root element', async () => {
|
||||||
@@ -82,9 +72,6 @@ suite('workspaceTreeDataProvider Tests', function (): void {
|
|||||||
displayName: 'sql project',
|
displayName: 'sql project',
|
||||||
description: ''
|
description: ''
|
||||||
}],
|
}],
|
||||||
RemoveProject: (projectFile: vscode.Uri): Promise<void> => {
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
getProjectTreeDataProvider: (projectFile: vscode.Uri): Promise<vscode.TreeDataProvider<any>> => {
|
getProjectTreeDataProvider: (projectFile: vscode.Uri): Promise<vscode.TreeDataProvider<any>> => {
|
||||||
return Promise.resolve(treeDataProvider);
|
return Promise.resolve(treeDataProvider);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -153,16 +153,9 @@ export const projectLocationLabel = localize('projectLocationLabel', "Location")
|
|||||||
export const projectLocationPlaceholderText = localize('projectLocationPlaceholderText', "Select location to create project");
|
export const projectLocationPlaceholderText = localize('projectLocationPlaceholderText', "Select location to create project");
|
||||||
export const browseButtonText = localize('browseButtonText', "Browse folder");
|
export const browseButtonText = localize('browseButtonText', "Browse folder");
|
||||||
export const folderStructureLabel = localize('folderStructureLabel', "Folder structure");
|
export const folderStructureLabel = localize('folderStructureLabel', "Folder structure");
|
||||||
export const addProjectToCurrentWorkspace = localize('addProjectToCurrentWorkspace', "This project will be added to the current workspace.");
|
|
||||||
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 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 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 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
|
// Error messages
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ export async function getSqlProjectFilesInFolder(folderPath: string): Promise<st
|
|||||||
/**
|
/**
|
||||||
* Get all the projects in the workspace that are sqlproj
|
* Get all the projects in the workspace that are sqlproj
|
||||||
*/
|
*/
|
||||||
export function getSqlProjectsInWorkspace(): vscode.Uri[] {
|
export function getSqlProjectsInWorkspace(): Promise<vscode.Uri[]> {
|
||||||
const api = getDataWorkspaceExtensionApi();
|
const api = getDataWorkspaceExtensionApi();
|
||||||
return api.getProjectsInWorkspace(constants.sqlprojExtension);
|
return api.getProjectsInWorkspace(constants.sqlprojExtension);
|
||||||
}
|
}
|
||||||
@@ -251,13 +251,6 @@ export function getDataWorkspaceExtensionApi(): dataworkspace.IExtension {
|
|||||||
return extension.exports;
|
return extension.exports;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* if the current workspace is untitled, the returned URI of vscode.workspace.workspaceFile will use the `untitled` scheme
|
|
||||||
*/
|
|
||||||
export function isCurrentWorkspaceUntitled(): boolean {
|
|
||||||
return !!vscode.workspace.workspaceFile && vscode.workspace.workspaceFile.scheme.toLowerCase() === 'untitled';
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns the default deployment options from DacFx
|
* Returns the default deployment options from DacFx
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -664,7 +664,7 @@ export class ProjectsController {
|
|||||||
if ((<IProjectReferenceSettings>settings).projectName !== undefined) {
|
if ((<IProjectReferenceSettings>settings).projectName !== undefined) {
|
||||||
// get project path and guid
|
// get project path and guid
|
||||||
const projectReferenceSettings = settings as IProjectReferenceSettings;
|
const projectReferenceSettings = settings as IProjectReferenceSettings;
|
||||||
const workspaceProjects = utils.getSqlProjectsInWorkspace();
|
const workspaceProjects = await utils.getSqlProjectsInWorkspace();
|
||||||
const referencedProject = await Project.openProject(workspaceProjects.filter(p => path.parse(p.fsPath).name === projectReferenceSettings.projectName)[0].fsPath);
|
const referencedProject = await Project.openProject(workspaceProjects.filter(p => path.parse(p.fsPath).name === projectReferenceSettings.projectName)[0].fsPath);
|
||||||
const relativePath = path.relative(project.projectFolderPath, referencedProject?.projectFilePath!);
|
const relativePath = path.relative(project.projectFolderPath, referencedProject?.projectFilePath!);
|
||||||
projectReferenceSettings.projectRelativePath = vscode.Uri.file(relativePath);
|
projectReferenceSettings.projectRelativePath = vscode.Uri.file(relativePath);
|
||||||
@@ -882,7 +882,7 @@ export class ProjectsController {
|
|||||||
|
|
||||||
// add project to workspace
|
// add project to workspace
|
||||||
workspaceApi.showProjectsView();
|
workspaceApi.showProjectsView();
|
||||||
await workspaceApi.addProjectsToWorkspace([vscode.Uri.file(newProjFilePath)], model.newWorkspaceFilePath);
|
await workspaceApi.addProjectsToWorkspace([vscode.Uri.file(newProjFilePath)]);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
vscode.window.showErrorMessage(utils.getErrorMessage(err));
|
vscode.window.showErrorMessage(utils.getErrorMessage(err));
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ export class AddDatabaseReferenceDialog {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// get projects in workspace and filter to only sql projects
|
// get projects in workspace and filter to only sql projects
|
||||||
let projectFiles: vscode.Uri[] = utils.getSqlProjectsInWorkspace();
|
let projectFiles: vscode.Uri[] = await utils.getSqlProjectsInWorkspace();
|
||||||
|
|
||||||
// filter out current project
|
// filter out current project
|
||||||
projectFiles = projectFiles.filter(p => p.fsPath !== this.project.projectFilePath);
|
projectFiles = projectFiles.filter(p => p.fsPath !== this.project.projectFilePath);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { cssStyles } from '../common/uiConstants';
|
|||||||
import { ImportDataModel } from '../models/api/import';
|
import { ImportDataModel } from '../models/api/import';
|
||||||
import { Deferred } from '../common/promise';
|
import { Deferred } from '../common/promise';
|
||||||
import { getConnectionName } from './utils';
|
import { getConnectionName } from './utils';
|
||||||
import { exists, getAzdataApi, isCurrentWorkspaceUntitled } from '../common/utils';
|
import { exists, getAzdataApi, getDataWorkspaceExtensionApi } from '../common/utils';
|
||||||
|
|
||||||
export class CreateProjectFromDatabaseDialog {
|
export class CreateProjectFromDatabaseDialog {
|
||||||
public dialog: azdataType.window.Dialog;
|
public dialog: azdataType.window.Dialog;
|
||||||
@@ -26,7 +26,6 @@ export class CreateProjectFromDatabaseDialog {
|
|||||||
public projectNameTextBox: azdataType.InputBoxComponent | undefined;
|
public projectNameTextBox: azdataType.InputBoxComponent | undefined;
|
||||||
public projectLocationTextBox: azdataType.InputBoxComponent | undefined;
|
public projectLocationTextBox: azdataType.InputBoxComponent | undefined;
|
||||||
public folderStructureDropDown: azdataType.DropDownComponent | undefined;
|
public folderStructureDropDown: azdataType.DropDownComponent | undefined;
|
||||||
public workspaceInputBox: azdataType.InputBoxComponent | undefined;
|
|
||||||
private formBuilder: azdataType.FormBuilder | undefined;
|
private formBuilder: azdataType.FormBuilder | undefined;
|
||||||
private connectionId: string | undefined;
|
private connectionId: string | undefined;
|
||||||
private toDispose: vscode.Disposable[] = [];
|
private toDispose: vscode.Disposable[] = [];
|
||||||
@@ -87,10 +86,6 @@ export class CreateProjectFromDatabaseDialog {
|
|||||||
const createProjectSettingsFormSection = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
const createProjectSettingsFormSection = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||||
createProjectSettingsFormSection.addItems([folderStructureRow]);
|
createProjectSettingsFormSection.addItems([folderStructureRow]);
|
||||||
|
|
||||||
const workspaceContainerRow = this.createWorkspaceContainerRow(view);
|
|
||||||
const createworkspaceContainerFormSection = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
|
||||||
createworkspaceContainerFormSection.addItems([workspaceContainerRow]);
|
|
||||||
|
|
||||||
this.formBuilder = <azdataType.FormBuilder>view.modelBuilder.formContainer()
|
this.formBuilder = <azdataType.FormBuilder>view.modelBuilder.formContainer()
|
||||||
.withFormItems([
|
.withFormItems([
|
||||||
{
|
{
|
||||||
@@ -116,14 +111,6 @@ export class CreateProjectFromDatabaseDialog {
|
|||||||
component: createProjectSettingsFormSection,
|
component: createProjectSettingsFormSection,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
title: constants.workspace,
|
|
||||||
components: [
|
|
||||||
{
|
|
||||||
component: createworkspaceContainerFormSection,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
], {
|
], {
|
||||||
horizontal: false,
|
horizontal: false,
|
||||||
@@ -166,7 +153,6 @@ export class CreateProjectFromDatabaseDialog {
|
|||||||
|
|
||||||
this.sourceDatabaseDropDown.onValueChanged(() => {
|
this.sourceDatabaseDropDown.onValueChanged(() => {
|
||||||
this.setProjectName();
|
this.setProjectName();
|
||||||
this.updateWorkspaceInputbox(path.join(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!), this.projectNameTextBox!.value!);
|
|
||||||
this.tryEnableCreateButton();
|
this.tryEnableCreateButton();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -264,7 +250,6 @@ export class CreateProjectFromDatabaseDialog {
|
|||||||
this.projectNameTextBox.onTextChanged(() => {
|
this.projectNameTextBox.onTextChanged(() => {
|
||||||
this.projectNameTextBox!.value = this.projectNameTextBox!.value?.trim();
|
this.projectNameTextBox!.value = this.projectNameTextBox!.value?.trim();
|
||||||
this.projectNameTextBox!.updateProperty('title', this.projectNameTextBox!.value);
|
this.projectNameTextBox!.updateProperty('title', this.projectNameTextBox!.value);
|
||||||
this.updateWorkspaceInputbox(path.join(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!), this.projectNameTextBox!.value!);
|
|
||||||
this.tryEnableCreateButton();
|
this.tryEnableCreateButton();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -291,7 +276,6 @@ export class CreateProjectFromDatabaseDialog {
|
|||||||
|
|
||||||
this.projectLocationTextBox.onTextChanged(() => {
|
this.projectLocationTextBox.onTextChanged(() => {
|
||||||
this.projectLocationTextBox!.updateProperty('title', this.projectLocationTextBox!.value);
|
this.projectLocationTextBox!.updateProperty('title', this.projectLocationTextBox!.value);
|
||||||
this.updateWorkspaceInputbox(path.join(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!), this.projectNameTextBox!.value!);
|
|
||||||
this.tryEnableCreateButton();
|
this.tryEnableCreateButton();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -329,7 +313,6 @@ export class CreateProjectFromDatabaseDialog {
|
|||||||
|
|
||||||
this.projectLocationTextBox!.value = folderUris[0].fsPath;
|
this.projectLocationTextBox!.value = folderUris[0].fsPath;
|
||||||
this.projectLocationTextBox!.updateProperty('title', folderUris[0].fsPath);
|
this.projectLocationTextBox!.updateProperty('title', folderUris[0].fsPath);
|
||||||
this.updateWorkspaceInputbox(path.join(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!), this.projectNameTextBox!.value!);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return browseFolderButton;
|
return browseFolderButton;
|
||||||
@@ -359,80 +342,6 @@ export class CreateProjectFromDatabaseDialog {
|
|||||||
return folderStructureRow;
|
return folderStructureRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates container with information on which workspace the project will be added to and where the workspace will be
|
|
||||||
* created if no workspace is currently open
|
|
||||||
* @param view
|
|
||||||
*/
|
|
||||||
private createWorkspaceContainerRow(view: azdataType.ModelView): azdataType.FlexContainer {
|
|
||||||
const initialWorkspaceInputBoxValue = !!vscode.workspace.workspaceFile && !isCurrentWorkspaceUntitled() ? vscode.workspace.workspaceFile.fsPath : '';
|
|
||||||
|
|
||||||
this.workspaceInputBox = view.modelBuilder.inputBox().withProps({
|
|
||||||
ariaLabel: constants.workspaceLocationTitle,
|
|
||||||
enabled: !vscode.workspace.workspaceFile || isCurrentWorkspaceUntitled(), // want it editable if no saved workspace is open
|
|
||||||
value: initialWorkspaceInputBoxValue,
|
|
||||||
title: initialWorkspaceInputBoxValue, // hovertext for if file path is too long to be seen in textbox
|
|
||||||
width: '100%'
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
const browseFolderButton = view.modelBuilder.button().withProperties<azdataType.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<azdataType.TextComponentProperties>({
|
|
||||||
value: vscode.workspace.workspaceFile ? constants.addProjectToCurrentWorkspace : constants.newWorkspaceWillBeCreated,
|
|
||||||
CSSStyles: { 'margin-top': '-10px', 'margin-bottom': '5px' }
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
let workspaceContainerRow;
|
|
||||||
if (vscode.workspace.workspaceFile && !isCurrentWorkspaceUntitled()) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the workspace inputbox based on the passed in location and name if there isn't a workspace currently open
|
|
||||||
* @param location
|
|
||||||
* @param name
|
|
||||||
*/
|
|
||||||
public updateWorkspaceInputbox(location: string, name: string): void {
|
|
||||||
if (!vscode.workspace.workspaceFile || isCurrentWorkspaceUntitled()) {
|
|
||||||
const fileLocation = location && name ? path.join(location, `${name}.code-workspace`) : '';
|
|
||||||
this.workspaceInputBox!.value = fileLocation;
|
|
||||||
this.workspaceInputBox!.title = fileLocation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// only enable Create button if all fields are filled
|
// only enable Create button if all fields are filled
|
||||||
public tryEnableCreateButton(): void {
|
public tryEnableCreateButton(): void {
|
||||||
if (this.sourceConnectionTextBox!.value && this.sourceDatabaseDropDown!.value
|
if (this.sourceConnectionTextBox!.value && this.sourceDatabaseDropDown!.value
|
||||||
@@ -450,8 +359,7 @@ export class CreateProjectFromDatabaseDialog {
|
|||||||
projName: this.projectNameTextBox!.value!,
|
projName: this.projectNameTextBox!.value!,
|
||||||
filePath: this.projectLocationTextBox!.value!,
|
filePath: this.projectLocationTextBox!.value!,
|
||||||
version: '1.0.0.0',
|
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
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getAzdataApi()!.window.closeDialog(this.dialog);
|
getAzdataApi()!.window.closeDialog(this.dialog);
|
||||||
@@ -477,6 +385,9 @@ export class CreateProjectFromDatabaseDialog {
|
|||||||
|
|
||||||
async validate(): Promise<boolean> {
|
async validate(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
|
if (await getDataWorkspaceExtensionApi().validateWorkspace() === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// the selected location should be an existing directory
|
// the selected location should be an existing directory
|
||||||
const parentDirectoryExists = await exists(this.projectLocationTextBox!.value!);
|
const parentDirectoryExists = await exists(this.projectLocationTextBox!.value!);
|
||||||
if (!parentDirectoryExists) {
|
if (!parentDirectoryExists) {
|
||||||
@@ -490,11 +401,6 @@ export class CreateProjectFromDatabaseDialog {
|
|||||||
this.showErrorMessage(constants.ProjectDirectoryAlreadyExistError(this.projectNameTextBox!.value!, this.projectLocationTextBox!.value!));
|
this.showErrorMessage(constants.ProjectDirectoryAlreadyExistError(this.projectNameTextBox!.value!, this.projectLocationTextBox!.value!));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.workspaceInputBox!.enabled) {
|
|
||||||
await this.validateNewWorkspace();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.showErrorMessage(err?.message ? err.message : err);
|
this.showErrorMessage(err?.message ? err.message : err);
|
||||||
@@ -502,30 +408,6 @@ export class CreateProjectFromDatabaseDialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
protected showErrorMessage(message: string): void {
|
||||||
this.dialog.message = {
|
this.dialog.message = {
|
||||||
text: message,
|
text: message,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { Uri } from 'vscode';
|
|
||||||
import { ExtractTarget } from '../../../../mssql';
|
import { ExtractTarget } from '../../../../mssql';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,5 +15,4 @@ export interface ImportDataModel {
|
|||||||
filePath: string;
|
filePath: string;
|
||||||
version: string;
|
version: string;
|
||||||
extractTarget: ExtractTarget;
|
extractTarget: ExtractTarget;
|
||||||
newWorkspaceFilePath?: Uri;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,16 +30,6 @@ export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvide
|
|||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback method when a project has been removed from the workspace view
|
|
||||||
* @param projectFile The Uri of the project file
|
|
||||||
*/
|
|
||||||
RemoveProject(projectFile: vscode.Uri): Promise<void> {
|
|
||||||
// No resource release needed
|
|
||||||
console.log(`project file unloaded: ${projectFile.fsPath}`);
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the supported project types
|
* Gets the supported project types
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ describe('Add Database Reference Dialog', () => {
|
|||||||
|
|
||||||
beforeEach(function (): void {
|
beforeEach(function (): void {
|
||||||
const dataWorkspaceMock = TypeMoq.Mock.ofType<dataworkspace.IExtension>();
|
const dataWorkspaceMock = TypeMoq.Mock.ofType<dataworkspace.IExtension>();
|
||||||
dataWorkspaceMock.setup(x => x.getProjectsInWorkspace(TypeMoq.It.isAny())).returns(() => []);
|
dataWorkspaceMock.setup(x => x.getProjectsInWorkspace(TypeMoq.It.isAny())).returns(() => Promise.resolve([]));
|
||||||
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: dataWorkspaceMock.object });
|
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: dataWorkspaceMock.object });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import * as should from 'should';
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as mssql from '../../../../mssql';
|
import * as mssql from '../../../../mssql';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import * as path from 'path';
|
|
||||||
import { CreateProjectFromDatabaseDialog } from '../../dialogs/createProjectFromDatabaseDialog';
|
import { CreateProjectFromDatabaseDialog } from '../../dialogs/createProjectFromDatabaseDialog';
|
||||||
import { mockConnectionProfile } from '../testContext';
|
import { mockConnectionProfile } from '../testContext';
|
||||||
import { ImportDataModel } from '../../models/api/import';
|
import { ImportDataModel } from '../../models/api/import';
|
||||||
@@ -83,22 +82,11 @@ describe('Create Project From Database Dialog', () => {
|
|||||||
should.equal(dialog.projectNameTextBox!.value, 'DatabaseProjectMy Database');
|
should.equal(dialog.projectNameTextBox!.value, 'DatabaseProjectMy Database');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should update default workspace name correctly when location and project name are provided', async function (): Promise<void> {
|
|
||||||
sinon.stub(azdata.connection, 'listDatabases').resolves(['My Database']);
|
|
||||||
const dialog = new CreateProjectFromDatabaseDialog(mockConnectionProfile);
|
|
||||||
await dialog.openDialog();
|
|
||||||
dialog.updateWorkspaceInputbox('testLocation', 'testProjectName');
|
|
||||||
|
|
||||||
should.equal(dialog.workspaceInputBox!.value, path.join('testLocation', 'testProjectName.code-workspace'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should include all info in import data model and connect to appropriate call back properties', async function (): Promise<void> {
|
it('Should include all info in import data model and connect to appropriate call back properties', async function (): Promise<void> {
|
||||||
const dialog = new CreateProjectFromDatabaseDialog(mockConnectionProfile);
|
const dialog = new CreateProjectFromDatabaseDialog(mockConnectionProfile);
|
||||||
sinon.stub(azdata.connection, 'listDatabases').resolves(['My Database']);
|
sinon.stub(azdata.connection, 'listDatabases').resolves(['My Database']);
|
||||||
await dialog.openDialog();
|
await dialog.openDialog();
|
||||||
|
|
||||||
dialog.workspaceInputBox!.enabled = false;
|
|
||||||
|
|
||||||
dialog.projectNameTextBox!.value = 'testProject';
|
dialog.projectNameTextBox!.value = 'testProject';
|
||||||
dialog.projectLocationTextBox!.value = 'testLocation';
|
dialog.projectLocationTextBox!.value = 'testLocation';
|
||||||
|
|
||||||
@@ -110,8 +98,7 @@ describe('Create Project From Database Dialog', () => {
|
|||||||
projName: 'testProject',
|
projName: 'testProject',
|
||||||
filePath: 'testLocation',
|
filePath: 'testLocation',
|
||||||
version: '1.0.0.0',
|
version: '1.0.0.0',
|
||||||
extractTarget: mssql.ExtractTarget['schemaObjectType'],
|
extractTarget: mssql.ExtractTarget['schemaObjectType']
|
||||||
newWorkspaceFilePath: undefined
|
|
||||||
};
|
};
|
||||||
|
|
||||||
dialog.createProjectFromDatabaseCallback = (m) => { model = m; };
|
dialog.createProjectFromDatabaseCallback = (m) => { model = m; };
|
||||||
|
|||||||
@@ -579,7 +579,7 @@ describe('ProjectsController', function (): void {
|
|||||||
const project2 = await Project.openProject(vscode.Uri.file(projPath2).fsPath);
|
const project2 = await Project.openProject(vscode.Uri.file(projPath2).fsPath);
|
||||||
const showErrorMessageSpy = sinon.spy(vscode.window, 'showErrorMessage');
|
const showErrorMessageSpy = sinon.spy(vscode.window, 'showErrorMessage');
|
||||||
const dataWorkspaceMock = TypeMoq.Mock.ofType<dataworkspace.IExtension>();
|
const dataWorkspaceMock = TypeMoq.Mock.ofType<dataworkspace.IExtension>();
|
||||||
dataWorkspaceMock.setup(x => x.getProjectsInWorkspace(TypeMoq.It.isAny())).returns(() => [vscode.Uri.file(project1.projectFilePath), vscode.Uri.file(project2.projectFilePath)]);
|
dataWorkspaceMock.setup(x => x.getProjectsInWorkspace(TypeMoq.It.isAny())).returns(() => Promise.resolve([vscode.Uri.file(project1.projectFilePath), vscode.Uri.file(project2.projectFilePath)]));
|
||||||
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: dataWorkspaceMock.object });
|
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: dataWorkspaceMock.object });
|
||||||
|
|
||||||
// add project reference from project1 to project2
|
// add project reference from project1 to project2
|
||||||
|
|||||||
Reference in New Issue
Block a user