From 7181d4c79e331a1be6b3377b6b234e4e31432dfe Mon Sep 17 00:00:00 2001 From: Kim Santiago <31145923+kisantia@users.noreply.github.com> Date: Fri, 11 Mar 2022 16:54:53 -0800 Subject: [PATCH] Make SDK-style project a checkbox option instead of separate template (#18698) * switch to using a checkbox instead of separate template for new SDK style project * let project provider provide sdk learn more url * Reorder --- .../data-workspace/src/common/constants.ts | 2 + .../data-workspace/src/common/interfaces.ts | 3 +- .../data-workspace/src/dataworkspace.d.ts | 11 +-- .../src/dialogs/newProjectDialog.ts | 70 +++++++++++++++---- .../src/services/workspaceService.ts | 4 +- .../src/controllers/projectController.ts | 11 +-- .../createProjectFromDatabaseDialog.ts | 2 +- .../createProjectFromDatabaseQuickpick.ts | 3 +- .../src/models/api/import.ts | 2 +- .../src/projectProvider/projectProvider.ts | 24 +++---- .../dialogs/publishDatabaseDialog.test.ts | 6 +- .../src/test/projectController.test.ts | 13 ++-- 12 files changed, 101 insertions(+), 50 deletions(-) diff --git a/extensions/data-workspace/src/common/constants.ts b/extensions/data-workspace/src/common/constants.ts index a93e94d7f0..ccb3b4b94a 100644 --- a/extensions/data-workspace/src/common/constants.ts +++ b/extensions/data-workspace/src/common/constants.ts @@ -49,6 +49,8 @@ export const SelectProjectType = localize('dataworkspace.selectProjectType', "Se 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"); +export const SdkStyleProject = localize('dataworkspace.sdkStyleProject', "SDK-style project (Preview)"); +export const LearnMore = localize('dataworkspace.learnMore', "Learn More"); //Open Existing Dialog export const OpenExistingDialogTitle = localize('dataworkspace.openExistingDialogTitle', "Open Existing Project"); diff --git a/extensions/data-workspace/src/common/interfaces.ts b/extensions/data-workspace/src/common/interfaces.ts index e074e22e9d..661945c0bb 100644 --- a/extensions/data-workspace/src/common/interfaces.ts +++ b/extensions/data-workspace/src/common/interfaces.ts @@ -74,8 +74,9 @@ export interface IWorkspaceService { * @param location The location of the project * @param projectTypeId The project type id * @param projectTargetPlatform The target platform of the project + * @param sdkStyleProject Whether or not the project is SDK-style */ - createProject(name: string, location: vscode.Uri, projectTypeId: string, projectTargetPlatform?: string): Promise; + createProject(name: string, location: vscode.Uri, projectTypeId: string, projectTargetPlatform?: string, sdkStyleProject?: boolean): Promise; /** * Clones git repository and adds projects to workspace diff --git a/extensions/data-workspace/src/dataworkspace.d.ts b/extensions/data-workspace/src/dataworkspace.d.ts index 980e0bb16e..75168e4ded 100644 --- a/extensions/data-workspace/src/dataworkspace.d.ts +++ b/extensions/data-workspace/src/dataworkspace.d.ts @@ -67,8 +67,9 @@ declare module 'dataworkspace' { * @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 + * @param sdkStyleProject whether or not a project is SDK-style */ - createProject(name: string, location: vscode.Uri, projectTypeId: string, projectTargetPlatform?: string): Promise; + createProject(name: string, location: vscode.Uri, projectTypeId: string, projectTargetPlatform?: string, sdkStyleProject?: boolean): Promise; /** * Gets the project data corresponding to the project file, to be placed in the dashboard container @@ -131,14 +132,14 @@ declare module 'dataworkspace' { readonly defaultTargetPlatform?: string; /** - * Link display value for a link at the end of the project description. linkLocation also needs to be set to use this + * Whether or not sdk style project is an option */ - readonly linkDisplayValue?: string; + readonly sdkStyleOption?: boolean; /** - * Location where clicking on the linkDisplayValue will go to + * Location where clicking on the Learn More next to SDK style checkbox will go. sdkStyleOption needs to be set to true to use this */ - readonly linkLocation?: string + readonly sdkStyleLearnMoreUrl?: string } /** diff --git a/extensions/data-workspace/src/dialogs/newProjectDialog.ts b/extensions/data-workspace/src/dialogs/newProjectDialog.ts index aeb7ab4b8c..4a2413ea3d 100644 --- a/extensions/data-workspace/src/dialogs/newProjectDialog.ts +++ b/extensions/data-workspace/src/dialogs/newProjectDialog.ts @@ -22,6 +22,7 @@ class NewProjectDialogModel { name: string = ''; location: string = ''; targetPlatform?: string; + sdkStyleProject?: boolean; } export async function openSpecificProjectNewProjectDialog(projectType: IProjectType, workspaceService: WorkspaceService): Promise { @@ -35,6 +36,7 @@ export class NewProjectDialog extends DialogBase { public model: NewProjectDialogModel = new NewProjectDialogModel(); public formBuilder: azdataType.FormBuilder | undefined; public targetPlatformDropdownFormComponent: azdataType.FormComponent | undefined; + public sdkProjectCheckboxFormComponent: azdataType.FormComponent | undefined; public newProjectDialogComplete: Deferred | undefined; public newDialogPromise: Promise = new Promise((resolve, reject) => this.newProjectDialogComplete = { resolve, reject }); public projectUri: vscode.Uri | undefined; @@ -87,7 +89,7 @@ export class NewProjectDialog extends DialogBase { .withAdditionalProperties({ projectFileExtension: this.model.projectFileExtension, projectTemplateId: this.model.projectTypeId }) .send(); - this.projectUri = await this.workspaceService.createProject(this.model.name, vscode.Uri.file(this.model.location), this.model.projectTypeId, this.model.targetPlatform); + this.projectUri = await this.workspaceService.createProject(this.model.name, vscode.Uri.file(this.model.location), this.model.projectTypeId, this.model.targetPlatform, this.model.sdkStyleProject); this.newProjectDialogComplete?.resolve(); } catch (err) { @@ -122,8 +124,7 @@ export class NewProjectDialog extends DialogBase { 'font-weight': 'bold' } }, { - textValue: projectType.description, - linkDisplayValue: projectType.linkDisplayValue + textValue: projectType.description } ] }; @@ -138,15 +139,7 @@ export class NewProjectDialog extends DialogBase { selectedCardId: allProjectTypes.length > 0 ? allProjectTypes[0].id : undefined }).component(); - projectTypeRadioCardGroup.onLinkClick(async (value) => { - for (let projectType of allProjectTypes) { - if (value.cardId === projectType.id) { - void vscode.env.openExternal(vscode.Uri.parse(projectType.linkLocation!)); - } - } - }); - - this.register(projectTypeRadioCardGroup.onSelectionChanged((e) => { + this.register(projectTypeRadioCardGroup.onSelectionChanged(async (e) => { this.model.projectTypeId = e.cardId; const selectedProject = allProjectTypes.find(p => p.id === e.cardId); @@ -155,12 +148,30 @@ export class NewProjectDialog extends DialogBase { targetPlatformDropdown.values = selectedProject?.targetPlatforms; targetPlatformDropdown.value = this.getDefaultTargetPlatform(selectedProject); - this.formBuilder?.addFormItem(this.targetPlatformDropdownFormComponent!); + this.formBuilder?.insertFormItem(this.targetPlatformDropdownFormComponent!, 3); } 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; } + + if (selectedProject?.sdkStyleOption) { + sdkProjectCheckbox.checked = true; + this.model.sdkStyleProject = true; + + if (selectedProject.sdkStyleLearnMoreUrl) { + await sdkLearnMore.updateProperty('url', selectedProject.sdkStyleLearnMoreUrl); + sdkFormComponentGroup.addItem(sdkLearnMore); + } else { + // remove learn more link if the project type didn't provide it + sdkFormComponentGroup.removeItem(sdkLearnMore); + } + + this.formBuilder?.addFormItem(this.sdkProjectCheckboxFormComponent!); + } else { + this.model.sdkStyleProject = false; + this.formBuilder?.removeFormItem(this.sdkProjectCheckboxFormComponent!); + } })); const projectNameTextBox = view.modelBuilder.inputBox().withProps({ @@ -227,6 +238,34 @@ export class NewProjectDialog extends DialogBase { component: targetPlatformDropdown }; + const sdkProjectCheckbox = view.modelBuilder.checkBox().withProps({ + checked: true, + label: constants.SdkStyleProject + }).component(); + + this.register(sdkProjectCheckbox.onChanged(() => { + this.model.sdkStyleProject = sdkProjectCheckbox.checked; + })); + + const sdkLearnMore = view.modelBuilder.hyperlink().withProps({ + label: constants.LearnMore, + url: '' + }).component(); + + const sdkFormComponentGroup = view.modelBuilder.flexContainer() + .withLayout({ flexFlow: 'row', alignItems: 'baseline' }) + .withItems([sdkProjectCheckbox], { CSSStyles: { flex: '0 0 auto', 'margin-right': '10px' } }) + .component(); + + if (allProjectTypes[0].sdkStyleLearnMoreUrl) { + await sdkLearnMore.updateProperty('url', allProjectTypes[0].sdkStyleLearnMoreUrl); + sdkFormComponentGroup.addItem(sdkLearnMore); + } + + this.sdkProjectCheckboxFormComponent = { + component: sdkFormComponentGroup, + }; + this.formBuilder = view.modelBuilder.formContainer().withFormItems([ { title: constants.TypeTitle, @@ -250,6 +289,11 @@ export class NewProjectDialog extends DialogBase { this.formBuilder.addFormItem(this.targetPlatformDropdownFormComponent); } + // add sdk style checkbox is the first project has the option + if (allProjectTypes[0].sdkStyleOption) { + this.formBuilder.addFormItem(this.sdkProjectCheckboxFormComponent); + } + await view.initializeModel(this.formBuilder.component()); this.initDialogComplete?.resolve(); } diff --git a/extensions/data-workspace/src/services/workspaceService.ts b/extensions/data-workspace/src/services/workspaceService.ts index 3eadd5551d..0d3aa573c0 100644 --- a/extensions/data-workspace/src/services/workspaceService.ts +++ b/extensions/data-workspace/src/services/workspaceService.ts @@ -197,10 +197,10 @@ export class WorkspaceService implements IWorkspaceService { return ProjectProviderRegistry.getProviderByProjectExtension(projectType); } - async createProject(name: string, location: vscode.Uri, projectTypeId: string, projectTargetVersion?: string): Promise { + async createProject(name: string, location: vscode.Uri, projectTypeId: string, projectTargetVersion?: string, sdkStyleProject?: boolean): Promise { const provider = ProjectProviderRegistry.getProviderByProjectType(projectTypeId); if (provider) { - const projectFile = await provider.createProject(name, location, projectTypeId, projectTargetVersion); + const projectFile = await provider.createProject(name, location, projectTypeId, projectTargetVersion, sdkStyleProject); await this.addProjectsToWorkspace([projectFile]); this._onDidWorkspaceProjectsChange.fire(); return projectFile; diff --git a/extensions/sql-database-projects/src/controllers/projectController.ts b/extensions/sql-database-projects/src/controllers/projectController.ts index ce6ab96f5e..d09b7dea09 100644 --- a/extensions/sql-database-projects/src/controllers/projectController.ts +++ b/extensions/sql-database-projects/src/controllers/projectController.ts @@ -154,7 +154,7 @@ export class ProjectsController { */ public async createNewProject(creationParams: NewProjectParams): Promise { TelemetryReporter.createActionEvent(TelemetryViews.ProjectController, TelemetryActions.createNewProject) - .withAdditionalProperties({ template: creationParams.projectTypeId }) + .withAdditionalProperties({ template: creationParams.projectTypeId, sdkStyle: creationParams.sdkStyle!.toString() }) .send(); if (creationParams.projectGuid && !UUID.isUUID(creationParams.projectGuid)) { @@ -171,7 +171,7 @@ export class ProjectsController { 'PROJECT_DSP': creationParams.targetPlatform ? constants.targetPlatformToVersion.get(creationParams.targetPlatform)! : constants.defaultDSP }; - let newProjFileContents = creationParams.projectTypeId === constants.emptySqlDatabaseSdkProjectTypeId ? templates.macroExpansion(templates.newSdkSqlProjectTemplate, macroDict) : templates.macroExpansion(templates.newSqlProjectTemplate, macroDict); + let newProjFileContents = creationParams.sdkStyle ? templates.macroExpansion(templates.newSdkSqlProjectTemplate, macroDict) : templates.macroExpansion(templates.newSqlProjectTemplate, macroDict); let newProjFileName = creationParams.newProjName; @@ -1111,7 +1111,8 @@ export class ProjectsController { const newProjFilePath = await this.createNewProject({ newProjName: projectInfo.projectName, folderUri: vscode.Uri.file(projectInfo.outputFolder), - projectTypeId: constants.emptySqlDatabaseProjectTypeId + projectTypeId: constants.emptySqlDatabaseProjectTypeId, + sdkStyle: false }); const project = await Project.openProject(newProjFilePath); @@ -1286,7 +1287,8 @@ export class ProjectsController { const newProjFilePath = await this.createNewProject({ newProjName: model.projName, folderUri: vscode.Uri.file(newProjFolderUri), - projectTypeId: model.sdkStyle ? constants.emptySqlDatabaseSdkProjectTypeId : constants.emptySqlDatabaseProjectTypeId + projectTypeId: model.sdkStyle ? constants.emptySqlDatabaseSdkProjectTypeId : constants.emptySqlDatabaseProjectTypeId, + sdkStyle: model.sdkStyle }); model.filePath = path.dirname(newProjFilePath); @@ -1498,6 +1500,7 @@ export interface NewProjectParams { newProjName: string; folderUri: vscode.Uri; projectTypeId: string; + sdkStyle: boolean; projectGuid?: string; targetPlatform?: SqlTargetPlatform; } diff --git a/extensions/sql-database-projects/src/dialogs/createProjectFromDatabaseDialog.ts b/extensions/sql-database-projects/src/dialogs/createProjectFromDatabaseDialog.ts index 668a721e45..89f6d37dcc 100644 --- a/extensions/sql-database-projects/src/dialogs/createProjectFromDatabaseDialog.ts +++ b/extensions/sql-database-projects/src/dialogs/createProjectFromDatabaseDialog.ts @@ -381,7 +381,7 @@ export class CreateProjectFromDatabaseDialog { filePath: this.projectLocationTextBox!.value!, version: '1.0.0.0', extractTarget: mapExtractTargetEnum(this.folderStructureDropDown!.value), - sdkStyle: this.sdkStyleCheckbox?.checked + sdkStyle: this.sdkStyleCheckbox?.checked! }; azdataApi!.window.closeDialog(this.dialog); diff --git a/extensions/sql-database-projects/src/dialogs/createProjectFromDatabaseQuickpick.ts b/extensions/sql-database-projects/src/dialogs/createProjectFromDatabaseQuickpick.ts index 47b77f1b65..66ca21d7db 100644 --- a/extensions/sql-database-projects/src/dialogs/createProjectFromDatabaseQuickpick.ts +++ b/extensions/sql-database-projects/src/dialogs/createProjectFromDatabaseQuickpick.ts @@ -139,6 +139,7 @@ export async function createNewProjectFromDatabaseWithQuickpick(connectionInfo?: projName: projectName, filePath: projectLocation, version: '1.0.0.0', - extractTarget: mapExtractTargetEnum(folderStructure) + extractTarget: mapExtractTargetEnum(folderStructure), + sdkStyle: false // todo: add sdkstyle option to quickpick }; } diff --git a/extensions/sql-database-projects/src/models/api/import.ts b/extensions/sql-database-projects/src/models/api/import.ts index 9f3f5aefc1..1bcd238f67 100644 --- a/extensions/sql-database-projects/src/models/api/import.ts +++ b/extensions/sql-database-projects/src/models/api/import.ts @@ -18,5 +18,5 @@ export interface ImportDataModel { filePath: string; version: string; extractTarget: ExtractTarget; - sdkStyle?: boolean; + sdkStyle: boolean; } diff --git a/extensions/sql-database-projects/src/projectProvider/projectProvider.ts b/extensions/sql-database-projects/src/projectProvider/projectProvider.ts index 6fa89a66a6..770c084be7 100644 --- a/extensions/sql-database-projects/src/projectProvider/projectProvider.ts +++ b/extensions/sql-database-projects/src/projectProvider/projectProvider.ts @@ -36,31 +36,24 @@ export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvide */ get supportedProjectTypes(): dataworkspace.IProjectType[] { return [{ - id: constants.emptySqlDatabaseSdkProjectTypeId, - projectFileExtension: constants.sqlprojExtension.replace(/\./g, ''), - displayName: constants.emptySdkProjectTypeDisplayName, - description: constants.emptySdkProjectTypeDescription, - icon: IconPathHelper.colorfulSqlProject, - targetPlatforms: Array.from(constants.targetPlatformToVersion.keys()), - defaultTargetPlatform: constants.defaultTargetPlatform, - linkDisplayValue: constants.learnMore, - linkLocation: constants.sdkLearnMoreUrl - }, - { id: constants.emptySqlDatabaseProjectTypeId, projectFileExtension: constants.sqlprojExtension.replace(/\./g, ''), displayName: constants.emptyProjectTypeDisplayName, description: constants.emptyProjectTypeDescription, icon: IconPathHelper.colorfulSqlProject, targetPlatforms: Array.from(constants.targetPlatformToVersion.keys()), - defaultTargetPlatform: constants.defaultTargetPlatform + defaultTargetPlatform: constants.defaultTargetPlatform, + sdkStyleOption: true, + sdkStyleLearnMoreUrl: constants.sdkLearnMoreUrl }, { id: constants.edgeSqlDatabaseProjectTypeId, projectFileExtension: constants.sqlprojExtension.replace(/\./g, ''), displayName: constants.edgeProjectTypeDisplayName, description: constants.edgeProjectTypeDescription, - icon: IconPathHelper.sqlEdgeProject + icon: IconPathHelper.sqlEdgeProject, + sdkStyleOption: true, + sdkStyleLearnMoreUrl: constants.sdkLearnMoreUrl }]; } @@ -71,12 +64,13 @@ export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvide * @param projectTypeId the ID of the project/template * @returns Uri of the newly created project file */ - async createProject(name: string, location: vscode.Uri, projectTypeId: string, targetPlatform?: sqldbproj.SqlTargetPlatform): Promise { + async createProject(name: string, location: vscode.Uri, projectTypeId: string, targetPlatform?: sqldbproj.SqlTargetPlatform, sdkStyle: boolean = true): Promise { const projectFile = await this.projectController.createNewProject({ newProjName: name, folderUri: location, projectTypeId: projectTypeId, - targetPlatform: targetPlatform + targetPlatform: targetPlatform, + sdkStyle: sdkStyle }); return vscode.Uri.file(projectFile); diff --git a/extensions/sql-database-projects/src/test/dialogs/publishDatabaseDialog.test.ts b/extensions/sql-database-projects/src/test/dialogs/publishDatabaseDialog.test.ts index 34ea8a3ce0..81a9bb0553 100644 --- a/extensions/sql-database-projects/src/test/dialogs/publishDatabaseDialog.test.ts +++ b/extensions/sql-database-projects/src/test/dialogs/publishDatabaseDialog.test.ts @@ -36,7 +36,8 @@ describe('Publish Database Dialog', () => { newProjName: 'TestProjectName', folderUri: vscode.Uri.file(projFileDir), projectTypeId: emptySqlDatabaseProjectTypeId, - projectGuid: 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575' + projectGuid: 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575', + sdkStyle: false }); const project = new Project(projFilePath); @@ -54,7 +55,8 @@ describe('Publish Database Dialog', () => { newProjName: 'TestProjectName', folderUri: vscode.Uri.file(projFileDir), projectTypeId: emptySqlDatabaseProjectTypeId, - projectGuid: 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575' + projectGuid: 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575', + sdkStyle: false }); const project = new Project(projFilePath); diff --git a/extensions/sql-database-projects/src/test/projectController.test.ts b/extensions/sql-database-projects/src/test/projectController.test.ts index 8113169c47..de2876144e 100644 --- a/extensions/sql-database-projects/src/test/projectController.test.ts +++ b/extensions/sql-database-projects/src/test/projectController.test.ts @@ -59,7 +59,8 @@ describe('ProjectsController', function (): void { newProjName: 'TestProjectName', folderUri: vscode.Uri.file(projFileDir), projectTypeId: constants.emptySqlDatabaseProjectTypeId, - projectGuid: 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575' + projectGuid: 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575', + sdkStyle: false }); let projFileText = (await fs.readFile(projFilePath)).toString(); @@ -77,7 +78,8 @@ describe('ProjectsController', function (): void { folderUri: vscode.Uri.file(projFileDir), projectTypeId: constants.emptySqlDatabaseProjectTypeId, projectGuid: 'BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575', - targetPlatform: projTargetPlatform + targetPlatform: projTargetPlatform, + sdkStyle: false }); const project = await Project.openProject(projFilePath); @@ -496,7 +498,8 @@ describe('ProjectsController', function (): void { projName: 'testProject', filePath: 'testLocation', version: '1.0.0.0', - extractTarget: mssql.ExtractTarget['schemaObjectType'] + extractTarget: mssql.ExtractTarget['schemaObjectType'], + sdkStyle: false }); return Promise.resolve(undefined); @@ -520,7 +523,7 @@ describe('ProjectsController', function (): void { let folderPath = await testUtils.generateTestFolderPath(); let projectName = 'My Project'; let importPath; - let model: ImportDataModel = { connectionUri: 'My Id', database: 'My Database', projName: projectName, filePath: folderPath, version: '1.0.0.0', extractTarget: mssql.ExtractTarget['file'] }; + let model: ImportDataModel = { connectionUri: 'My Id', database: 'My Database', projName: projectName, filePath: folderPath, version: '1.0.0.0', extractTarget: mssql.ExtractTarget['file'], sdkStyle: false }; const projController = new ProjectsController(testContext.outputChannel); projController.setFilePath(model); @@ -533,7 +536,7 @@ describe('ProjectsController', function (): void { let folderPath = await testUtils.generateTestFolderPath(); let projectName = 'My Project'; let importPath; - let model: ImportDataModel = { connectionUri: 'My Id', database: 'My Database', projName: projectName, filePath: folderPath, version: '1.0.0.0', extractTarget: mssql.ExtractTarget['schemaObjectType'] }; + let model: ImportDataModel = { connectionUri: 'My Id', database: 'My Database', projName: projectName, filePath: folderPath, version: '1.0.0.0', extractTarget: mssql.ExtractTarget['schemaObjectType'], sdkStyle: false }; const projController = new ProjectsController(testContext.outputChannel); projController.setFilePath(model);