diff --git a/extensions/sql-database-projects/images/dark/symbol-string.svg b/extensions/sql-database-projects/images/dark/symbol-string.svg new file mode 100644 index 0000000000..ef5f226505 --- /dev/null +++ b/extensions/sql-database-projects/images/dark/symbol-string.svg @@ -0,0 +1,3 @@ + + + diff --git a/extensions/sql-database-projects/images/dark/symbol-variable.svg b/extensions/sql-database-projects/images/dark/symbol-variable.svg new file mode 100644 index 0000000000..5ee50e0e81 --- /dev/null +++ b/extensions/sql-database-projects/images/dark/symbol-variable.svg @@ -0,0 +1,3 @@ + + + diff --git a/extensions/sql-database-projects/images/light/symbol-string.svg b/extensions/sql-database-projects/images/light/symbol-string.svg new file mode 100644 index 0000000000..2fabca5751 --- /dev/null +++ b/extensions/sql-database-projects/images/light/symbol-string.svg @@ -0,0 +1,3 @@ + + + diff --git a/extensions/sql-database-projects/images/light/symbol-variable.svg b/extensions/sql-database-projects/images/light/symbol-variable.svg new file mode 100644 index 0000000000..3656d9e13a --- /dev/null +++ b/extensions/sql-database-projects/images/light/symbol-variable.svg @@ -0,0 +1,3 @@ + + + diff --git a/extensions/sql-database-projects/src/common/constants.ts b/extensions/sql-database-projects/src/common/constants.ts index b3c1428d98..7dfcda638d 100644 --- a/extensions/sql-database-projects/src/common/constants.ts +++ b/extensions/sql-database-projects/src/common/constants.ts @@ -79,6 +79,7 @@ export const refreshDataWorkspaceCommand = 'dataworkspace.refresh'; // UI Strings export const dataSourcesNodeName = localize('dataSourcesNodeName', "Data Sources"); export const databaseReferencesNodeName = localize('databaseReferencesNodeName', "Database References"); +export const sqlcmdVariablesNodeName = localize('sqlcmdVariablesNodeName', "SQLCMD Variables"); export const sqlConnectionStringFriendly = localize('sqlConnectionStringFriendly', "SQL connection string"); export const yesString = localize('yesString', "Yes"); export const openEulaString = localize('openEulaString', "Open License Agreement"); @@ -553,6 +554,8 @@ export enum DatabaseProjectItemType { referencesRoot = 'databaseProject.itemType.referencesRoot', reference = 'databaseProject.itemType.reference', dataSourceRoot = 'databaseProject.itemType.dataSourceRoot', + sqlcmdVariablesRoot = 'databaseProject.itemType.sqlcmdVariablesRoot', + sqlcmdVariable = 'databaseProject.itemType.sqlcmdVariable' } // AutoRest diff --git a/extensions/sql-database-projects/src/common/iconHelper.ts b/extensions/sql-database-projects/src/common/iconHelper.ts index 5c32bc09d0..49d19bb2a7 100644 --- a/extensions/sql-database-projects/src/common/iconHelper.ts +++ b/extensions/sql-database-projects/src/common/iconHelper.ts @@ -24,6 +24,9 @@ export class IconPathHelper { public static referenceGroup: IconPath; public static referenceDatabase: IconPath; + public static sqlCmdVariablesGroup: IconPath; + public static sqlCmdVariable: IconPath; + public static refresh: IconPath; public static folder_blue: IconPath; public static selectConnection: IconPath; @@ -57,6 +60,9 @@ export class IconPathHelper { IconPathHelper.referenceGroup = IconPathHelper.makeIcon('referenceGroup'); IconPathHelper.referenceDatabase = IconPathHelper.makeIcon('reference-database'); + IconPathHelper.sqlCmdVariablesGroup = IconPathHelper.makeIcon('symbol-string'); + IconPathHelper.sqlCmdVariable = IconPathHelper.makeIcon('symbol-variable'); + IconPathHelper.refresh = IconPathHelper.makeIcon('refresh', true); IconPathHelper.folder_blue = IconPathHelper.makeIcon('folder_blue', true); IconPathHelper.selectConnection = IconPathHelper.makeIcon('selectConnection', true); diff --git a/extensions/sql-database-projects/src/models/tree/projectTreeItem.ts b/extensions/sql-database-projects/src/models/tree/projectTreeItem.ts index f6d9965609..29f24f0064 100644 --- a/extensions/sql-database-projects/src/models/tree/projectTreeItem.ts +++ b/extensions/sql-database-projects/src/models/tree/projectTreeItem.ts @@ -16,6 +16,7 @@ import { IconPathHelper } from '../../common/iconHelper'; import { FileProjectEntry } from '../projectEntry'; import { EntryType } from 'sqldbproj'; import { DBProjectConfigurationKey } from '../../tools/netcoreTool'; +import { SqlCmdVariablesTreeItem } from './sqlcmdVariableTreeItem'; /** * TreeNode root that represents an entire project @@ -23,6 +24,7 @@ import { DBProjectConfigurationKey } from '../../tools/netcoreTool'; export class ProjectRootTreeItem extends BaseProjectTreeItem { dataSourceNode: DataSourcesTreeItem; databaseReferencesNode: DatabaseReferencesTreeItem; + sqlCmdVariablesNode: SqlCmdVariablesTreeItem; fileChildren: { [childName: string]: (fileTree.FolderNode | fileTree.FileNode) } = {}; project: Project; fileSystemUri: vscode.Uri; @@ -34,7 +36,7 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem { this.fileSystemUri = vscode.Uri.file(project.projectFilePath); this.dataSourceNode = new DataSourcesTreeItem(this); this.databaseReferencesNode = new DatabaseReferencesTreeItem(this); - + this.sqlCmdVariablesNode = new SqlCmdVariablesTreeItem(this); this.construct(); } @@ -43,6 +45,7 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem { // [8/31/2020] Hiding Data source for Preview since we do not have a way to add or update those. // output.push(this.dataSourceNode); output.push(this.databaseReferencesNode); + output.push(this.sqlCmdVariablesNode); return output.concat(Object.values(this.fileChildren).sort(fileTree.sortFileFolderNodes)); } diff --git a/extensions/sql-database-projects/src/models/tree/sqlcmdVariableTreeItem.ts b/extensions/sql-database-projects/src/models/tree/sqlcmdVariableTreeItem.ts new file mode 100644 index 0000000000..ddaa53937d --- /dev/null +++ b/extensions/sql-database-projects/src/models/tree/sqlcmdVariableTreeItem.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as constants from '../../common/constants'; + +import { BaseProjectTreeItem } from './baseTreeItem'; +import { ProjectRootTreeItem } from './projectTreeItem'; +import { IconPathHelper } from '../../common/iconHelper'; + +/** + * Folder for containing SQLCMD variable nodes in the tree + */ +export class SqlCmdVariablesTreeItem extends BaseProjectTreeItem { + private sqlcmdVariables: SqlCmdVariableTreeItem[] = []; + + constructor(project: ProjectRootTreeItem) { + super(vscode.Uri.file(path.join(project.projectUri.fsPath, constants.sqlcmdVariablesNodeName)), project); + + this.construct(); + } + + private construct() { + const sqlCmdVariables = (this.parent as ProjectRootTreeItem).project.sqlCmdVariables; + + if (!sqlCmdVariables) { + return; + } + + for (const sqlCmdVariable of Object.keys(sqlCmdVariables)) { + if (sqlCmdVariable) { + this.sqlcmdVariables.push(new SqlCmdVariableTreeItem(sqlCmdVariable, this)); + } + } + } + + public get children(): SqlCmdVariableTreeItem[] { + return this.sqlcmdVariables; + } + + public get treeItem(): vscode.TreeItem { + const sqlCmdVariableFolderItem = new vscode.TreeItem(this.projectUri, vscode.TreeItemCollapsibleState.Collapsed); + sqlCmdVariableFolderItem.contextValue = constants.DatabaseProjectItemType.sqlcmdVariablesRoot; + sqlCmdVariableFolderItem.iconPath = IconPathHelper.sqlCmdVariablesGroup; + + return sqlCmdVariableFolderItem; + } +} + +/** + * Represents a SQLCMD variable in a .sqlproj + */ +export class SqlCmdVariableTreeItem extends BaseProjectTreeItem { + constructor(private sqlcmdVar: string, sqlcmdVarsTreeItem: SqlCmdVariablesTreeItem) { + super(vscode.Uri.file(path.join(sqlcmdVarsTreeItem.projectUri.fsPath, sqlcmdVar)), sqlcmdVarsTreeItem); + } + + public get children(): BaseProjectTreeItem[] { + return []; + } + + public get treeItem(): vscode.TreeItem { + const sqlcmdVariableItem = new vscode.TreeItem(this.projectUri, vscode.TreeItemCollapsibleState.None); + sqlcmdVariableItem.label = this.sqlcmdVar; + sqlcmdVariableItem.contextValue = constants.DatabaseProjectItemType.sqlcmdVariable; + sqlcmdVariableItem.iconPath = IconPathHelper.sqlCmdVariable; + + return sqlcmdVariableItem; + } +} diff --git a/extensions/sql-database-projects/src/test/projectTree.test.ts b/extensions/sql-database-projects/src/test/projectTree.test.ts index c393a32007..4ac4cc2d7c 100644 --- a/extensions/sql-database-projects/src/test/projectTree.test.ts +++ b/extensions/sql-database-projects/src/test/projectTree.test.ts @@ -70,6 +70,7 @@ describe('Project Tree tests', function (): void { const tree = new ProjectRootTreeItem(proj); should(tree.children.map(x => x.projectUri.path)).deepEqual([ '/TestProj/Database References', + '/TestProj/SQLCMD Variables', '/TestProj/duplicateFolder', '/TestProj/someFolder', '/TestProj/duplicate.sql']); @@ -82,6 +83,7 @@ describe('Project Tree tests', function (): void { should(tree.children.map(x => x.treeItem.contextValue)).deepEqual([ DatabaseProjectItemType.referencesRoot, + DatabaseProjectItemType.sqlcmdVariablesRoot, DatabaseProjectItemType.folder, DatabaseProjectItemType.folder, DatabaseProjectItemType.file]); @@ -106,12 +108,13 @@ describe('Project Tree tests', function (): void { const tree = new ProjectRootTreeItem(proj); should(tree.children.map(x => x.projectUri.path)).deepEqual([ '/TestProj/Database References', + '/TestProj/SQLCMD Variables', '/TestProj/someFolder1']); should(tree.children.find(x => x.projectUri.path === '/TestProj/someFolder1')?.children.map(y => y.projectUri.path)).deepEqual([ - '/TestProj/someFolder1/MyNestedFolder1', - '/TestProj/someFolder1/MyNestedFolder2', - '/TestProj/someFolder1/MyFile2.sql']); + '/TestProj/someFolder1/MyNestedFolder1', + '/TestProj/someFolder1/MyNestedFolder2', + '/TestProj/someFolder1/MyFile2.sql']); }); it('Should be able to parse and include relative paths outside project folder', function (): void { @@ -127,6 +130,7 @@ describe('Project Tree tests', function (): void { const tree = new ProjectRootTreeItem(proj); should(tree.children.map(x => x.projectUri.path)).deepEqual([ '/TestProj/Database References', + '/TestProj/SQLCMD Variables', '/TestProj/MyFile1.sql', '/TestProj/MyFile2.sql']); });