Add target platform dropdown to new project dialog (#16091)

* add target platform as an option in create project api

* move constant

* WIP

* show versions in dialog and create project with selected version

* validate version

* add error messages

* add test

* change version to target platform

* cleanup

* more cleanup

* use withProps
This commit is contained in:
Kim Santiago
2021-07-15 13:43:48 -07:00
committed by GitHub
parent 2a74ad4190
commit 5059c94adc
9 changed files with 150 additions and 18 deletions

View File

@@ -46,6 +46,8 @@ export const ProjectDirectoryAlreadyExistErrorShort = (projectName: string) => {
export const SelectProjectType = localize('dataworkspace.selectProjectType', "Select Project Type");
export const SelectProjectLocation = localize('dataworkspace.selectProjectLocation', "Select Project Location");
export const NameCannotBeEmpty = localize('dataworkspace.nameCannotBeEmpty', "Name cannot be empty");
export const TargetPlatform = localize('dataworkspace.targetPlatform', "Target Platform");
//Open Existing Dialog
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); };

View File

@@ -71,8 +71,9 @@ export interface IWorkspaceService {
* @param name The name of the project
* @param location The location of the project
* @param projectTypeId The project type id
* @param projectTargetPlatform The target platform of the project
*/
createProject(name: string, location: vscode.Uri, projectTypeId: string): Promise<vscode.Uri>;
createProject(name: string, location: vscode.Uri, projectTypeId: string, projectTargetPlatform?: string): Promise<vscode.Uri>;
/**
* Clones git repository and adds projects to workspace

View File

@@ -57,8 +57,9 @@ declare module 'dataworkspace' {
* @param name Create a project
* @param location the parent directory of the project
* @param projectTypeId the identifier of the selected project type
* @param projectTargetPlatform the target platform of the project
*/
createProject(name: string, location: vscode.Uri, projectTypeId: string): Promise<vscode.Uri>;
createProject(name: string, location: vscode.Uri, projectTypeId: string, projectTargetPlatform?: string): Promise<vscode.Uri>;
/**
* Gets the project data corresponding to the project file, to be placed in the dashboard container
@@ -108,7 +109,17 @@ declare module 'dataworkspace' {
/**
* Gets the icon path of the project type
*/
readonly icon: azdata.IconPath
readonly icon: azdata.IconPath;
/**
* Gets the target platforms that can be selected when creating a new project
*/
readonly targetPlatforms?: string[];
/**
* Gets the default target platform
*/
readonly defaultTargetPlatform?: string;
}
/**

View File

@@ -20,9 +20,13 @@ class NewProjectDialogModel {
projectFileExtension: string = '';
name: string = '';
location: string = '';
targetPlatform?: string;
}
export class NewProjectDialog extends DialogBase {
public model: NewProjectDialogModel = new NewProjectDialogModel();
public formBuilder: azdataType.FormBuilder | undefined;
public targetPlatformDropdownFormComponent: azdataType.FormComponent | undefined;
constructor(private workspaceService: IWorkspaceService) {
super(constants.NewProjectDialogTitle, 'NewProject', constants.CreateButtonText);
@@ -67,7 +71,7 @@ export class NewProjectDialog extends DialogBase {
.withAdditionalProperties({ projectFileExtension: this.model.projectFileExtension, projectTemplateId: this.model.projectTypeId })
.send();
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, this.model.targetPlatform);
}
catch (err) {
@@ -81,7 +85,7 @@ export class NewProjectDialog extends DialogBase {
protected async initialize(view: azdataType.ModelView): Promise<void> {
const allProjectTypes = await this.workspaceService.getAllProjectTypes();
const projectTypeRadioCardGroup = view.modelBuilder.radioCardGroup().withProperties<azdataType.RadioCardGroupComponentProperties>({
const projectTypeRadioCardGroup = view.modelBuilder.radioCardGroup().withProps({
cards: allProjectTypes.map((projectType: IProjectType) => {
return <azdataType.RadioCard>{
id: projectType.id,
@@ -112,9 +116,22 @@ export class NewProjectDialog extends DialogBase {
this.register(projectTypeRadioCardGroup.onSelectionChanged((e) => {
this.model.projectTypeId = e.cardId;
const selectedProject = allProjectTypes.find(p => p.id === e.cardId);
if (selectedProject?.targetPlatforms) {
// update the target platforms dropdown for the selected project type
targetPlatformDropdown.values = selectedProject?.targetPlatforms;
targetPlatformDropdown.value = this.getDefaultTargetPlatform(selectedProject);
this.formBuilder?.addFormItem(this.targetPlatformDropdownFormComponent!);
} else {
// remove the target version dropdown if the selected project type didn't provide values for this
this.formBuilder?.removeFormItem(this.targetPlatformDropdownFormComponent!);
this.model.targetPlatform = undefined;
}
}));
const projectNameTextBox = view.modelBuilder.inputBox().withProperties<azdataType.InputBoxProperties>({
const projectNameTextBox = view.modelBuilder.inputBox().withProps({
ariaLabel: constants.ProjectNameTitle,
placeHolder: constants.ProjectNamePlaceholder,
required: true,
@@ -126,7 +143,7 @@ export class NewProjectDialog extends DialogBase {
projectNameTextBox.updateProperty('title', projectNameTextBox.value);
}));
const locationTextBox = view.modelBuilder.inputBox().withProperties<azdataType.InputBoxProperties>({
const locationTextBox = view.modelBuilder.inputBox().withProps({
ariaLabel: constants.ProjectLocationTitle,
placeHolder: constants.ProjectLocationPlaceholder,
required: true,
@@ -138,7 +155,7 @@ export class NewProjectDialog extends DialogBase {
locationTextBox.updateProperty('title', locationTextBox.value);
}));
const browseFolderButton = view.modelBuilder.button().withProperties<azdataType.ButtonProperties>({
const browseFolderButton = view.modelBuilder.button().withProps({
ariaLabel: constants.BrowseButtonText,
iconPath: IconPathHelper.folder,
height: '16px',
@@ -159,7 +176,26 @@ export class NewProjectDialog extends DialogBase {
this.model.location = selectedFolder;
}));
const form = view.modelBuilder.formContainer().withFormItems([
const targetPlatformDropdown = view.modelBuilder.dropDown().withProps({
values: allProjectTypes[0].targetPlatforms,
value: this.getDefaultTargetPlatform(allProjectTypes[0]),
ariaLabel: constants.TargetPlatform,
required: true,
width: constants.DefaultInputWidth
}).component();
this.register(targetPlatformDropdown.onValueChanged(() => {
this.model.targetPlatform = targetPlatformDropdown.value! as string;
}));
this.targetPlatformDropdownFormComponent = {
title: constants.TargetPlatform,
required: true,
component: targetPlatformDropdown
};
this.formBuilder = view.modelBuilder.formContainer().withFormItems([
{
title: constants.TypeTitle,
required: true,
@@ -169,13 +205,34 @@ export class NewProjectDialog extends DialogBase {
title: constants.ProjectNameTitle,
required: true,
component: this.createHorizontalContainer(view, [projectNameTextBox])
}, {
},
{
title: constants.ProjectLocationTitle,
required: true,
component: this.createHorizontalContainer(view, [locationTextBox, browseFolderButton])
}
]).component();
await view.initializeModel(form);
]);
// add version dropdown if the first project type has one
if (allProjectTypes[0].targetPlatforms) {
this.formBuilder.addFormItem(this.targetPlatformDropdownFormComponent);
}
await view.initializeModel(this.formBuilder.component());
this.initDialogComplete?.resolve();
}
/**
* Gets the default target platform of the project type if there is one
* @param projectType
* @returns
*/
getDefaultTargetPlatform(projectType: IProjectType): string | undefined {
// only return the specified default target platform if it's also included in the project type's array of target platforms
if (projectType.defaultTargetPlatform && projectType.targetPlatforms?.includes(projectType.defaultTargetPlatform)) {
return projectType.defaultTargetPlatform;
} else {
return undefined;
}
}
}

View File

@@ -139,10 +139,10 @@ export class WorkspaceService implements IWorkspaceService {
return ProjectProviderRegistry.getProviderByProjectExtension(projectType);
}
async createProject(name: string, location: vscode.Uri, projectTypeId: string, workspaceFile?: vscode.Uri): Promise<vscode.Uri> {
async createProject(name: string, location: vscode.Uri, projectTypeId: string, projectTargetVersion?: string): Promise<vscode.Uri> {
const provider = ProjectProviderRegistry.getProviderByProjectType(projectTypeId);
if (provider) {
const projectFile = await provider.createProject(name, location, projectTypeId);
const projectFile = await provider.createProject(name, location, projectTypeId, projectTargetVersion);
await this.addProjectsToWorkspace([projectFile]);
this._onDidWorkspaceProjectsChange.fire();
return projectFile;

View File

@@ -12,6 +12,7 @@ import { promises as fs } from 'fs';
import { NewProjectDialog } from '../../dialogs/newProjectDialog';
import { WorkspaceService } from '../../services/workspaceService';
import { testProjectType } from '../testUtils';
import { IProjectType } from 'dataworkspace';
suite('New Project Dialog', function (): void {
this.afterEach(() => {
@@ -39,5 +40,54 @@ suite('New Project Dialog', function (): void {
dialog.model.name = `TestProject_${new Date().getTime()}`;
should.equal(await dialog.validate(), true, 'Validation should pass because name is unique and parent directory exists');
});
test('Should select correct target platform if provided default', async function (): Promise<void> {
const projectTypeWithTargetPlatforms: IProjectType = {
id: 'tp2',
description: '',
projectFileExtension: 'testproj2',
icon: '',
displayName: 'test project 2',
targetPlatforms: ['platform1', 'platform2', 'platform3'],
defaultTargetPlatform: 'platform2'
};
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
workspaceServiceMock.setup(x => x.getAllProjectTypes()).returns(() => Promise.resolve([projectTypeWithTargetPlatforms]));
const dialog = new NewProjectDialog(workspaceServiceMock.object);
await dialog.open();
should.equal(dialog.model.targetPlatform, 'platform2', 'Target platform should be platform2');
});
test('Should handle invalid default target platform', async function (): Promise<void> {
const projectTypeWithTargetPlatforms: IProjectType = {
id: 'tp2',
description: '',
projectFileExtension: 'testproj2',
icon: '',
displayName: 'test project 2',
targetPlatforms: ['platform1', 'platform2', 'platform3'],
defaultTargetPlatform: 'invalid'
};
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
workspaceServiceMock.setup(x => x.getAllProjectTypes()).returns(() => Promise.resolve([projectTypeWithTargetPlatforms]));
const dialog = new NewProjectDialog(workspaceServiceMock.object);
await dialog.open();
should.equal(dialog.model.targetPlatform, 'platform1', 'Target platform should be platform1 (the first value in target platforms)');
});
test('Should handle no target platforms provided by project type', async function (): Promise<void> {
const workspaceServiceMock = TypeMoq.Mock.ofType<WorkspaceService>();
workspaceServiceMock.setup(x => x.getAllProjectTypes()).returns(() => Promise.resolve([testProjectType]));
const dialog = new NewProjectDialog(workspaceServiceMock.object);
await dialog.open();
should.equal(dialog.model.targetPlatform, undefined, 'Target platform should be undefined');
});
});