mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Show database references in project tree (#10837)
* add database references to same itemgroup * show database references in tree * update tests * refresh project tree after a reference is added * addressing comments * add validation * Add test * sort imports
This commit is contained in:
@@ -26,6 +26,7 @@ export const databaseSchemaProvider = 'DatabaseSchemaProvider';
|
|||||||
|
|
||||||
export const projectNodeName = localize('projectNodeName', "Database Project");
|
export const projectNodeName = localize('projectNodeName', "Database Project");
|
||||||
export const dataSourcesNodeName = localize('dataSourcesNodeName', "Data Sources");
|
export const dataSourcesNodeName = localize('dataSourcesNodeName', "Data Sources");
|
||||||
|
export const databaseReferencesNodeName = localize('databaseReferencesNodeName', "Database References");
|
||||||
export const sqlConnectionStringFriendly = localize('sqlConnectionStringFriendly', "SQL connection string");
|
export const sqlConnectionStringFriendly = localize('sqlConnectionStringFriendly', "SQL connection string");
|
||||||
export const newDatabaseProjectName = localize('newDatabaseProjectName', "New database project name:");
|
export const newDatabaseProjectName = localize('newDatabaseProjectName', "New database project name:");
|
||||||
export const sqlDatabaseProject = localize('sqlDatabaseProject', "SQL database project");
|
export const sqlDatabaseProject = localize('sqlDatabaseProject', "SQL database project");
|
||||||
@@ -83,6 +84,7 @@ export const dacpacFileLocationRequired = localize('dacpacFileLocationRequired',
|
|||||||
export const databaseLocationRequired = localize('databaseLocation', "Database location is required for adding a reference to a database");
|
export const databaseLocationRequired = localize('databaseLocation', "Database location is required for adding a reference to a database");
|
||||||
export const databaseNameRequired = localize('databaseNameRequired', "Database name is required for adding a reference to a different database");
|
export const databaseNameRequired = localize('databaseNameRequired', "Database name is required for adding a reference to a different database");
|
||||||
export const invalidDataSchemaProvider = localize('invalidDataSchemaProvider', "Invalid DSP in .sqlproj file");
|
export const invalidDataSchemaProvider = localize('invalidDataSchemaProvider', "Invalid DSP in .sqlproj file");
|
||||||
|
export const invalidDatabaseReference = localize('invalidDatabaseReference', "Invalid database reference in .sqlproj file");
|
||||||
export const databaseSelectionRequired = localize('databaseSelectionRequired', "Database selection is required to import a project");
|
export const databaseSelectionRequired = localize('databaseSelectionRequired', "Database selection is required to import a project");
|
||||||
export function projectAlreadyOpened(path: string) { return localize('projectAlreadyOpened', "Project '{0}' is already opened.", path); }
|
export function projectAlreadyOpened(path: string) { return localize('projectAlreadyOpened', "Project '{0}' is already opened.", path); }
|
||||||
export function projectAlreadyExists(name: string, path: string) { return localize('projectAlreadyExists', "A project named {0} already exists in {1}.", name, path); }
|
export function projectAlreadyExists(name: string, path: string) { return localize('projectAlreadyExists', "A project named {0} already exists in {1}.", name, path); }
|
||||||
|
|||||||
@@ -321,6 +321,8 @@ export class ProjectsController {
|
|||||||
await project.addDatabaseReference(dacpacFileLocation, <DatabaseReferenceLocation>databaseLocation, false);
|
await project.addDatabaseReference(dacpacFileLocation, <DatabaseReferenceLocation>databaseLocation, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.refreshProjectsTree();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
|
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export class Project {
|
|||||||
public files: ProjectEntry[] = [];
|
public files: ProjectEntry[] = [];
|
||||||
public dataSources: DataSource[] = [];
|
public dataSources: DataSource[] = [];
|
||||||
public importedTargets: string[] = [];
|
public importedTargets: string[] = [];
|
||||||
|
public databaseReferences: string[] = [];
|
||||||
public sqlCmdVariables: Record<string, string> = {};
|
public sqlCmdVariables: Record<string, string> = {};
|
||||||
|
|
||||||
public get projectFolderPath() {
|
public get projectFolderPath() {
|
||||||
@@ -60,6 +61,16 @@ export class Project {
|
|||||||
const importTarget = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.Import)[i];
|
const importTarget = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.Import)[i];
|
||||||
this.importedTargets.push(importTarget.getAttribute(constants.Project));
|
this.importedTargets.push(importTarget.getAttribute(constants.Project));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// find all database references to include
|
||||||
|
for (let r = 0; r < this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ArtifactReference).length; r++) {
|
||||||
|
const filepath = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ArtifactReference)[r].getAttribute(constants.Include);
|
||||||
|
if (!filepath) {
|
||||||
|
throw new Error(constants.invalidDatabaseReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.databaseReferences.push(path.parse(filepath).name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateProjectForRoundTrip() {
|
public async updateProjectForRoundTrip() {
|
||||||
@@ -259,7 +270,8 @@ export class Project {
|
|||||||
referenceNode.appendChild(databaseVariableLiteralValue);
|
referenceNode.appendChild(databaseVariableLiteralValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.findOrCreateItemGroup().appendChild(referenceNode);
|
this.findOrCreateItemGroup(constants.ArtifactReference).appendChild(referenceNode);
|
||||||
|
this.databaseReferences.push(path.parse(entry.fsUri.fsPath.toString()).name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateImportedTargetsToProjFile(condition: string, projectAttributeVal: string, oldImportNode?: any): Promise<any> {
|
private async updateImportedTargetsToProjFile(condition: string, projectAttributeVal: string, oldImportNode?: any): Promise<any> {
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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, MessageTreeItem } from './baseTreeItem';
|
||||||
|
import { ProjectRootTreeItem } from './projectTreeItem';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Folder for containing references nodes in the tree
|
||||||
|
*/
|
||||||
|
export class DatabaseReferencesTreeItem extends BaseProjectTreeItem {
|
||||||
|
private references: MessageTreeItem[] = [];
|
||||||
|
|
||||||
|
constructor(project: ProjectRootTreeItem) {
|
||||||
|
super(vscode.Uri.file(path.join(project.uri.path, constants.databaseReferencesNodeName)), project);
|
||||||
|
|
||||||
|
this.construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
private construct() {
|
||||||
|
for (const reference of (this.parent as ProjectRootTreeItem).project.databaseReferences) {
|
||||||
|
this.references.push(new MessageTreeItem(reference));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get children(): BaseProjectTreeItem[] {
|
||||||
|
return this.references;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get treeItem(): vscode.TreeItem {
|
||||||
|
return new vscode.TreeItem(this.uri, vscode.TreeItemCollapsibleState.Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,12 +10,14 @@ import { BaseProjectTreeItem } from './baseTreeItem';
|
|||||||
import * as fileTree from './fileFolderTreeItem';
|
import * as fileTree from './fileFolderTreeItem';
|
||||||
import { Project, ProjectEntry, EntryType } from '../project';
|
import { Project, ProjectEntry, EntryType } from '../project';
|
||||||
import * as utils from '../../common/utils';
|
import * as utils from '../../common/utils';
|
||||||
|
import { DatabaseReferencesTreeItem } from './databaseReferencesTreeItem';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TreeNode root that represents an entire project
|
* TreeNode root that represents an entire project
|
||||||
*/
|
*/
|
||||||
export class ProjectRootTreeItem extends BaseProjectTreeItem {
|
export class ProjectRootTreeItem extends BaseProjectTreeItem {
|
||||||
dataSourceNode: DataSourcesTreeItem;
|
dataSourceNode: DataSourcesTreeItem;
|
||||||
|
databaseReferencesNode: DatabaseReferencesTreeItem;
|
||||||
fileChildren: { [childName: string]: (fileTree.FolderNode | fileTree.FileNode) } = {};
|
fileChildren: { [childName: string]: (fileTree.FolderNode | fileTree.FileNode) } = {};
|
||||||
project: Project;
|
project: Project;
|
||||||
|
|
||||||
@@ -24,6 +26,7 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem {
|
|||||||
|
|
||||||
this.project = project;
|
this.project = project;
|
||||||
this.dataSourceNode = new DataSourcesTreeItem(this);
|
this.dataSourceNode = new DataSourcesTreeItem(this);
|
||||||
|
this.databaseReferencesNode = new DatabaseReferencesTreeItem(this);
|
||||||
|
|
||||||
this.construct();
|
this.construct();
|
||||||
}
|
}
|
||||||
@@ -31,6 +34,7 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem {
|
|||||||
public get children(): BaseProjectTreeItem[] {
|
public get children(): BaseProjectTreeItem[] {
|
||||||
const output: BaseProjectTreeItem[] = [];
|
const output: BaseProjectTreeItem[] = [];
|
||||||
output.push(this.dataSourceNode);
|
output.push(this.dataSourceNode);
|
||||||
|
output.push(this.databaseReferencesNode);
|
||||||
|
|
||||||
return output.concat(Object.values(this.fileChildren).sort(fileTree.sortFileFolderNodes));
|
return output.concat(Object.values(this.fileChildren).sort(fileTree.sortFileFolderNodes));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,4 +73,10 @@
|
|||||||
<Folder Include="Views\User" />
|
<Folder Include="Views\User" />
|
||||||
<Build Include="Views\User\Profile.sql" />
|
<Build Include="Views\User\Profile.sql" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ArtifactReference Condition="'$(NetCoreBuild)' == 'true'" Include="$(NETCoreTargetsPath)\SystemDacpacs\130\master.dacpac">
|
||||||
|
<SuppressMissingDependenciesErrors>False</SuppressMissingDependenciesErrors>
|
||||||
|
<DatabaseVariableLiteralValue>master</DatabaseVariableLiteralValue>
|
||||||
|
</ArtifactReference>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import * as testUtils from './testUtils';
|
|||||||
import * as constants from '../common/constants';
|
import * as constants from '../common/constants';
|
||||||
|
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { Project, EntryType, TargetPlatform } from '../models/project';
|
import { Project, EntryType, TargetPlatform, SystemDatabase, DatabaseReferenceLocation } from '../models/project';
|
||||||
import { exists } from '../common/utils';
|
import { exists } from '../common/utils';
|
||||||
import { Uri } from 'vscode';
|
import { Uri } from 'vscode';
|
||||||
|
|
||||||
@@ -34,6 +34,9 @@ describe('Project: sqlproj content operations', function (): void {
|
|||||||
|
|
||||||
should(project.files.find(f => f.type === EntryType.Folder && f.relativePath === 'Views\\User')).not.equal(undefined); // mixed ItemGroup folder
|
should(project.files.find(f => f.type === EntryType.Folder && f.relativePath === 'Views\\User')).not.equal(undefined); // mixed ItemGroup folder
|
||||||
should(project.files.find(f => f.type === EntryType.File && f.relativePath === 'Views\\User\\Profile.sql')).not.equal(undefined); // mixed ItemGroup file
|
should(project.files.find(f => f.type === EntryType.File && f.relativePath === 'Views\\User\\Profile.sql')).not.equal(undefined); // mixed ItemGroup file
|
||||||
|
|
||||||
|
should(project.databaseReferences.length).equal(1);
|
||||||
|
should(project.databaseReferences[0]).containEql(constants.master);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should add Folder and Build entries to sqlproj', async function (): Promise<void> {
|
it('Should add Folder and Build entries to sqlproj', async function (): Promise<void> {
|
||||||
@@ -127,6 +130,25 @@ describe('Project: sqlproj content operations', function (): void {
|
|||||||
project.changeDSP('invalidPlatform');
|
project.changeDSP('invalidPlatform');
|
||||||
await testUtils.shouldThrowSpecificError(async () => await project.getSystemDacpacUri(constants.masterDacpac), constants.invalidDataSchemaProvider);
|
await testUtils.shouldThrowSpecificError(async () => await project.getSystemDacpacUri(constants.masterDacpac), constants.invalidDataSchemaProvider);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should add database references correctly', async function(): Promise<void> {
|
||||||
|
projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
|
||||||
|
const project = new Project(projFilePath);
|
||||||
|
await project.readProjFile();
|
||||||
|
|
||||||
|
should(project.databaseReferences.length).equal(0);
|
||||||
|
await project.addSystemDatabaseReference(SystemDatabase.master);
|
||||||
|
should(project.databaseReferences.length).equal(1);
|
||||||
|
should(project.databaseReferences[0]).equal(constants.master);
|
||||||
|
|
||||||
|
await project.addSystemDatabaseReference(SystemDatabase.msdb);
|
||||||
|
should(project.databaseReferences.length).equal(2);
|
||||||
|
should(project.databaseReferences[1]).equal(constants.msdb);
|
||||||
|
|
||||||
|
await project.addDatabaseReference(Uri.parse('test.dacpac'), DatabaseReferenceLocation.sameDatabase, false);
|
||||||
|
should(project.databaseReferences.length).equal(3);
|
||||||
|
should(project.databaseReferences[2]).equal('test');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Project: round trip updates', function (): void {
|
describe('Project: round trip updates', function (): void {
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ describe('Project Tree tests', function (): void {
|
|||||||
const tree = new ProjectRootTreeItem(proj);
|
const tree = new ProjectRootTreeItem(proj);
|
||||||
should(tree.children.map(x => x.uri.path)).deepEqual([
|
should(tree.children.map(x => x.uri.path)).deepEqual([
|
||||||
'/TestProj.sqlproj/Data Sources',
|
'/TestProj.sqlproj/Data Sources',
|
||||||
|
'/TestProj.sqlproj/Database References',
|
||||||
'/TestProj.sqlproj/duplicateFolder',
|
'/TestProj.sqlproj/duplicateFolder',
|
||||||
'/TestProj.sqlproj/someFolder',
|
'/TestProj.sqlproj/someFolder',
|
||||||
'/TestProj.sqlproj/duplicate.sql']);
|
'/TestProj.sqlproj/duplicate.sql']);
|
||||||
|
|||||||
Reference in New Issue
Block a user