data workspace extension batch 2 (#12208)

* work in progress

* load projects in view and test cases

* update scope

* make the sql proj menu available in workspace view

* add extension unit test

* address comments

* fix errors
This commit is contained in:
Alan Ren
2020-09-10 17:17:57 -07:00
committed by GitHub
parent cd8102535b
commit 7df132b307
20 changed files with 645 additions and 32 deletions

View File

@@ -46,7 +46,7 @@ jobs:
steps:
- template: linux/sql-product-build-linux.yml
parameters:
extensionsToUnitTest: ["admin-tool-ext-win", "agent", "azdata", "azurecore", "cms", "dacpac", "import", "schema-compare", "notebook", "resource-deployment", "machine-learning", "sql-database-projects"]
extensionsToUnitTest: ["admin-tool-ext-win", "agent", "azdata", "azurecore", "cms", "dacpac", "import", "schema-compare", "notebook", "resource-deployment", "machine-learning", "sql-database-projects", "data-workspace"]
timeoutInMinutes: 70
- job: LinuxWeb

View File

@@ -20,20 +20,41 @@
"type": "git",
"url": "https://github.com/Microsoft/azuredatastudio.git"
},
"extensionDependencies": [
"microsoft.mssql"
],
"extensionDependencies": [],
"contributes": {
"configuration": [
{
"title": "Projects",
"properties": {
"dataworkspace.projects": {
"type": "array",
"default": [],
"description": ""
}
}
}
],
"commands": [
{
"command": "projects.addProject",
"title": "%add-project-command%",
"category": "",
"icon": "$(add)"
},
{
"command": "dataworkspace.refresh",
"title": "%refresh-workspace-command%",
"category": "",
"icon": "$(refresh)"
}
],
"menus": {
"view/title": [
{
"command": "dataworkspace.refresh",
"when": "view == dataworkspace.views.main",
"group": "navigation"
},
{
"command": "projects.addProject",
"when": "view == dataworkspace.views.main",
@@ -44,6 +65,10 @@
{
"command": "projects.addProject",
"when": "false"
},
{
"command": "dataworkspace.refresh",
"when": "false"
}
]
},

View File

@@ -3,5 +3,6 @@
"extension-description": "Data workspace",
"data-workspace-view-container-name": "Projects",
"main-view-name": "Projects",
"add-project-command": "Add Project"
"add-project-command": "Add Project",
"refresh-workspace-command": "Refresh"
}

View File

@@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IProjectProvider, IProjectType } from 'dataworkspace';
import * as vscode from 'vscode';
/**
* Defines the project provider registry
*/
export interface IProjectProviderRegistry {
/**
* Registers a new project provider
* @param provider The project provider
*/
registerProvider(provider: IProjectProvider): vscode.Disposable;
/**
* Clear the providers
*/
clear(): void;
/**
* Gets all the registered providers
*/
readonly providers: IProjectProvider[];
/**
* Gets the project provider for the specified project type
* @param projectType The project type, file extension of the project
*/
getProviderByProjectType(projectType: string): IProjectProvider | undefined;
}
/**
* Defines the project service
*/
export interface IWorkspaceService {
/**
* Gets all supported project types
*/
getAllProjectTypes(): Promise<IProjectType[]>;
/**
* Gets the project files in current workspace
*/
getProjectsInWorkspace(): Promise<string[]>;
/**
* Gets the project provider by project file
* @param projectFilePath The full path of the project file
*/
getProjectProvider(projectFilePath: string): Promise<IProjectProvider | undefined>;
}
/**
* Represents the item for the workspace tree
*/
export interface WorkspaceTreeItem {
/**
* Gets the tree data provider
*/
treeDataProvider: vscode.TreeDataProvider<any>;
/**
* Gets the raw element returned by the tree data provider
*/
element: any;
}

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class Log {
error(msg: string): void {
console.error(msg);
}
}
const Logger = new Log();
export default Logger;

View File

@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IProjectProvider } from 'dataworkspace';
import * as vscode from 'vscode';
import { IProjectProviderRegistry } from './interfaces';
export const ProjectProviderRegistry: IProjectProviderRegistry = new class implements IProjectProviderRegistry {
private _providers = new Array<IProjectProvider>();
private _providerMapping: { [key: string]: IProjectProvider } = {};
registerProvider(provider: IProjectProvider): vscode.Disposable {
this.validateProvider(provider);
this._providers.push(provider);
provider.supportedProjectTypes.forEach(projectType => {
this._providerMapping[projectType.projectFileExtension.toUpperCase()] = provider;
});
return new vscode.Disposable(() => {
const idx = this._providers.indexOf(provider);
if (idx >= 0) {
this._providers.splice(idx, 1);
provider.supportedProjectTypes.forEach(projectType => {
delete this._providerMapping[projectType.projectFileExtension.toUpperCase()];
});
}
});
}
get providers(): IProjectProvider[] {
return this._providers.slice(0);
}
clear(): void {
this._providers.length = 0;
}
validateProvider(provider: IProjectProvider): void {
}
getProviderByProjectType(projectType: string): IProjectProvider | undefined {
return projectType ? this._providerMapping[projectType.toUpperCase()] : undefined;
}
};

View File

@@ -0,0 +1,66 @@
/*---------------------------------------------------------------------------------------------
* 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 { IWorkspaceService, WorkspaceTreeItem as WorkspaceTreeItem } from './interfaces';
import * as nls from 'vscode-nls';
import { EOL } from 'os';
const localize = nls.loadMessageBundle();
/**
* Tree data provider for the workspace main view
*/
export class WorkspaceTreeDataProvider implements vscode.TreeDataProvider<WorkspaceTreeItem>{
constructor(private _workspaceService: IWorkspaceService) { }
private _onDidChangeTreeData: vscode.EventEmitter<void | WorkspaceTreeItem | null | undefined> | undefined = new vscode.EventEmitter<WorkspaceTreeItem | undefined | void>();
readonly onDidChangeTreeData?: vscode.Event<void | WorkspaceTreeItem | null | undefined> | undefined = this._onDidChangeTreeData?.event;
refresh(): void {
this._onDidChangeTreeData?.fire();
}
getTreeItem(element: WorkspaceTreeItem): vscode.TreeItem | Thenable<vscode.TreeItem> {
return element.treeDataProvider.getTreeItem(element.element);
}
async getChildren(element?: WorkspaceTreeItem | undefined): Promise<WorkspaceTreeItem[]> {
if (element) {
const items = await element.treeDataProvider.getChildren(element.element);
return items ? items.map(item => <WorkspaceTreeItem>{ treeDataProvider: element.treeDataProvider, element: item }) : [];
}
else {
// if the element is undefined return the project tree items
const projects = await this._workspaceService.getProjectsInWorkspace();
const unknownProjects: string[] = [];
const treeItems: WorkspaceTreeItem[] = [];
let project: string;
for (project of projects) {
const projectProvider = await this._workspaceService.getProjectProvider(project);
if (projectProvider === undefined) {
unknownProjects.push(project);
continue;
}
const treeDataProvider = await projectProvider.getProjectTreeDataProvider(project);
if (treeDataProvider.onDidChangeTreeData) {
treeDataProvider.onDidChangeTreeData((e: any) => {
this._onDidChangeTreeData?.fire(e);
});
}
const children = await treeDataProvider.getChildren(element);
children?.forEach(child => {
treeItems.push({
treeDataProvider: treeDataProvider,
element: child
});
});
}
if (unknownProjects.length > 0) {
vscode.window.showErrorMessage(localize('UnknownProjectsError', "No provider was found for the following projects: {0}", unknownProjects.join(EOL)));
}
return treeItems;
}
}
}

View File

@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* 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 dataworkspace from 'dataworkspace';
import { ProjectProviderRegistry } from './common/projectProviderRegistry';
export class DataWorkspaceExtension implements dataworkspace.IExtension {
registerProjectProvider(provider: dataworkspace.IProjectProvider): vscode.Disposable {
return ProjectProviderRegistry.registerProvider(provider);
}
}

View File

@@ -0,0 +1,59 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'dataworkspace' {
import * as vscode from 'vscode';
export const enum extension {
name = 'Microsoft.data-workspace'
}
/**
* dataworkspace extension
*/
export interface IExtension {
/**
* register a project provider
* @param provider new project provider
* @requires a disposable object, upon disposal, the provider will be unregistered.
*/
registerProjectProvider(provider: IProjectProvider): vscode.Disposable;
}
/**
* Defines the capabilities of project provider
*/
export interface IProjectProvider {
/**
* Gets the tree data provider for the given project file
* @param projectFilePath The full path of the project file
*/
getProjectTreeDataProvider(projectFilePath: string): Promise<vscode.TreeDataProvider<any>>;
/**
* Gets the supported project types
*/
readonly supportedProjectTypes: IProjectType[];
}
/**
* Defines the project type
*/
export interface IProjectType {
/**
* display name of the project type
*/
readonly displayName: string;
/**
* project file extension, e.g. sqlproj
*/
readonly projectFileExtension: string;
/**
* Gets the icon path of the project type
*/
readonly icon: string | vscode.Uri | { light: string | vscode.Uri, dark: string | vscode.Uri }
}
}

View File

@@ -4,10 +4,23 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as dataworkspace from 'dataworkspace';
import { WorkspaceTreeDataProvider } from './common/workspaceTreeDataProvider';
import { WorkspaceService } from './services/workspaceService';
import { DataWorkspaceExtension } from './dataWorkspaceExtension';
export async function activate(context: vscode.ExtensionContext): Promise<void> {
vscode.commands.registerCommand('projects.addProject', () => {
});
export async function activate(context: vscode.ExtensionContext): Promise<dataworkspace.IExtension> {
const workspaceService = new WorkspaceService();
const workspaceTreeDataProvider = new WorkspaceTreeDataProvider(workspaceService);
context.subscriptions.push(vscode.window.registerTreeDataProvider('dataworkspace.views.main', workspaceTreeDataProvider));
context.subscriptions.push(vscode.commands.registerCommand('projects.addProject', () => {
}));
context.subscriptions.push(vscode.commands.registerCommand('dataworkspace.refresh', () => {
workspaceTreeDataProvider.refresh();
}));
return new DataWorkspaceExtension();
}
export function deactivate(): void {

View File

@@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* 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 dataworkspace from 'dataworkspace';
import * as path from 'path';
import { IWorkspaceService } from '../common/interfaces';
import { ProjectProviderRegistry } from '../common/projectProviderRegistry';
import * as nls from 'vscode-nls';
import Logger from '../common/logger';
const localize = nls.loadMessageBundle();
const WorkspaceConfigurationName = 'dataworkspace';
const ProjectsConfigurationName = 'projects';
export class WorkspaceService implements IWorkspaceService {
async getAllProjectTypes(): Promise<dataworkspace.IProjectType[]> {
await this.ensureProviderExtensionLoaded();
const projectTypes: dataworkspace.IProjectType[] = [];
ProjectProviderRegistry.providers.forEach(provider => {
projectTypes.push(...provider.supportedProjectTypes);
});
return projectTypes;
}
async getProjectsInWorkspace(): Promise<string[]> {
if (vscode.workspace.workspaceFile) {
const projects = <string[]>vscode.workspace.getConfiguration(WorkspaceConfigurationName).get(ProjectsConfigurationName);
return projects.map(project => path.isAbsolute(project) ? project : path.join(vscode.workspace.rootPath!, project));
}
return [];
}
async getProjectProvider(projectFilePath: string): Promise<dataworkspace.IProjectProvider | undefined> {
const projectType = path.extname(projectFilePath).replace(/\./g, '');
let provider = ProjectProviderRegistry.getProviderByProjectType(projectType);
if (!provider) {
await this.ensureProviderExtensionLoaded(projectType);
}
return ProjectProviderRegistry.getProviderByProjectType(projectType);
}
/**
* Ensure the project provider extension for the specified project is loaded
* @param projectType The file extension of the project, if not specified, all project provider extensions will be loaded.
*/
private async ensureProviderExtensionLoaded(projectType: string | undefined = undefined): Promise<void> {
const inactiveExtensions = vscode.extensions.all.filter(ext => !ext.isActive);
const projType = projectType ? projectType.toUpperCase() : undefined;
let extension: vscode.Extension<any>;
for (extension of inactiveExtensions) {
const projectTypes = extension.packageJSON.contributes && extension.packageJSON.contributes.projects as string[];
// Process only when this extension is contributing project providers
if (projectTypes && projectTypes.length > 0) {
if (projType) {
if (projectTypes.findIndex((proj: string) => proj.toUpperCase() === projType) !== -1) {
await this.activateExtension(extension);
break;
}
} else {
await this.activateExtension(extension);
}
}
}
}
private async activateExtension(extension: vscode.Extension<any>): Promise<void> {
try {
await extension.activate();
} catch (err) {
Logger.error(localize('activateExtensionFailed', "Failed to load the project provider extension '{0}'. Error message: {1}", extension.id, err.message ?? err));
}
}
}

View File

@@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* 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 should from 'should';
import { DataWorkspaceExtension } from '../dataWorkspaceExtension';
import { createProjectProvider } from './projectProviderRegistry.test';
import { ProjectProviderRegistry } from '../common/projectProviderRegistry';
suite('DataWorkspaceExtension Tests', function (): void {
test('register and unregister project provider through the extension api', async () => {
const extension = new DataWorkspaceExtension();
const provider = createProjectProvider([
{
projectFileExtension: 'testproj',
icon: '',
displayName: 'test project'
}
]);
const disposable = extension.registerProjectProvider(provider);
should.strictEqual(ProjectProviderRegistry.providers.length, 1, 'project provider should have been registered');
disposable.dispose();
should.strictEqual(ProjectProviderRegistry.providers.length, 0, 'there should be nothing in the ProjectProviderRegistry');
});
});

View File

@@ -0,0 +1,48 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
const testRunner = require('vscodetestcover');
const suite = 'Data Workspace Extension Tests';
const mochaOptions: any = {
ui: 'tdd',
useColors: true,
timeout: 10000
};
// set relevant mocha options from the environment
if (process.env.ADS_TEST_GREP) {
mochaOptions.grep = process.env.ADS_TEST_GREP;
console.log(`setting options.grep to: ${mochaOptions.grep}`);
}
if (process.env.ADS_TEST_INVERT_GREP) {
mochaOptions.invert = parseInt(process.env.ADS_TEST_INVERT_GREP);
console.log(`setting options.invert to: ${mochaOptions.invert}`);
}
if (process.env.ADS_TEST_TIMEOUT) {
mochaOptions.timeout = parseInt(process.env.ADS_TEST_TIMEOUT);
console.log(`setting options.timeout to: ${mochaOptions.timeout}`);
}
if (process.env.ADS_TEST_RETRIES) {
mochaOptions.retries = parseInt(process.env.ADS_TEST_RETRIES);
console.log(`setting options.retries to: ${mochaOptions.retries}`);
}
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
mochaOptions.reporter = 'mocha-multi-reporters';
mochaOptions.reporterOptions = {
reporterEnabled: 'spec, mocha-junit-reporter',
mochaJunitReporterReporterOptions: {
testsuitesTitle: `${suite} ${process.platform}`,
mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
}
};
}
testRunner.configure(mochaOptions, { coverConfig: '../../coverConfig.json' });
export = testRunner;

View File

@@ -0,0 +1,100 @@
/*---------------------------------------------------------------------------------------------
* 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 vscode from 'vscode';
import * as should from 'should';
import { ProjectProviderRegistry } from '../common/projectProviderRegistry';
import { IProjectProvider, IProjectType } from 'dataworkspace';
export class MockTreeDataProvider implements vscode.TreeDataProvider<any>{
onDidChangeTreeData?: vscode.Event<any> | undefined;
getTreeItem(element: any): vscode.TreeItem | Thenable<vscode.TreeItem> {
throw new Error('Method not implemented.');
}
getChildren(element?: any): vscode.ProviderResult<any[]> {
throw new Error('Method not implemented.');
}
}
export function createProjectProvider(projectTypes: IProjectType[]): IProjectProvider {
const treeDataProvider = new MockTreeDataProvider();
const projectProvider: IProjectProvider = {
supportedProjectTypes: projectTypes,
getProjectTreeDataProvider: (projectFile: string): Promise<vscode.TreeDataProvider<any>> => {
return Promise.resolve(treeDataProvider);
}
};
return projectProvider;
}
suite('ProjectProviderRegistry Tests', function (): void {
test('register and unregister project providers', async () => {
const provider1 = createProjectProvider([
{
projectFileExtension: 'testproj',
icon: '',
displayName: 'test project'
}, {
projectFileExtension: 'testproj1',
icon: '',
displayName: 'test project 1'
}
]);
const provider2 = createProjectProvider([
{
projectFileExtension: 'sqlproj',
icon: '',
displayName: 'sql project'
}
]);
should.strictEqual(ProjectProviderRegistry.providers.length, 0, 'there should be no project provider at the beginning of the test');
const disposable1 = ProjectProviderRegistry.registerProvider(provider1);
let providerResult = ProjectProviderRegistry.getProviderByProjectType('testproj');
should.equal(providerResult, provider1, 'provider1 should be returned for testproj project type');
// make sure the project type is case-insensitive for getProviderByProjectType method
providerResult = ProjectProviderRegistry.getProviderByProjectType('TeStProJ');
should.equal(providerResult, provider1, 'provider1 should be returned for testproj project type');
providerResult = ProjectProviderRegistry.getProviderByProjectType('testproj1');
should.equal(providerResult, provider1, 'provider1 should be returned for testproj1 project type');
should.strictEqual(ProjectProviderRegistry.providers.length, 1, 'there should be only one project provider at this time');
const disposable2 = ProjectProviderRegistry.registerProvider(provider2);
providerResult = ProjectProviderRegistry.getProviderByProjectType('sqlproj');
should.equal(providerResult, provider2, 'provider2 should be returned for sqlproj project type');
should.strictEqual(ProjectProviderRegistry.providers.length, 2, 'there should be 2 project providers at this time');
// unregister provider1
disposable1.dispose();
providerResult = ProjectProviderRegistry.getProviderByProjectType('testproj');
should.equal(providerResult, undefined, 'undefined should be returned for testproj project type');
providerResult = ProjectProviderRegistry.getProviderByProjectType('testproj1');
should.equal(providerResult, undefined, 'undefined should be returned for testproj1 project type');
providerResult = ProjectProviderRegistry.getProviderByProjectType('sqlproj');
should.equal(providerResult, provider2, 'provider2 should be returned for sqlproj project type after provider1 is disposed');
should.strictEqual(ProjectProviderRegistry.providers.length, 1, 'there should be only one project provider after unregistering a provider');
should.strictEqual(ProjectProviderRegistry.providers[0].supportedProjectTypes[0].projectFileExtension, 'sqlproj', 'the remaining project provider should be sqlproj');
// unregister provider2
disposable2.dispose();
providerResult = ProjectProviderRegistry.getProviderByProjectType('sqlproj');
should.equal(providerResult, undefined, 'undefined should be returned for sqlproj project type after provider2 is disposed');
should.strictEqual(ProjectProviderRegistry.providers.length, 0, 'there should be no project provider after unregistering the providers');
});
test('Clear the project provider registry', async () => {
const provider = createProjectProvider([
{
projectFileExtension: 'testproj',
icon: '',
displayName: 'test project'
}
]);
should.strictEqual(ProjectProviderRegistry.providers.length, 0, 'there should be no project provider at the beginning of the test');
ProjectProviderRegistry.registerProvider(provider);
should.strictEqual(ProjectProviderRegistry.providers.length, 1, 'there should be only one project provider at this time');
ProjectProviderRegistry.clear();
should.strictEqual(ProjectProviderRegistry.providers.length, 0, 'there should be no project provider after clearing the registry');
});
});

View File

@@ -16,7 +16,8 @@
"onCommand:sqlDatabaseProjects.new",
"onCommand:sqlDatabaseProjects.open",
"onCommand:sqlDatabaseProjects.importDatabase",
"workspaceContains:**/*.sqlproj"
"workspaceContains:**/*.sqlproj",
"onView:dataworkspace.views.main"
],
"main": "./out/extension",
"repository": {
@@ -25,9 +26,13 @@
},
"extensionDependencies": [
"Microsoft.mssql",
"Microsoft.schema-compare"
"Microsoft.schema-compare",
"Microsoft.data-workspace"
],
"contributes": {
"projects": [
"sqlproj"
],
"configuration": [
{
"title": "%sqlDatabaseProjects.Settings%",
@@ -243,87 +248,87 @@
"view/item/context": [
{
"command": "sqlDatabaseProjects.build",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.project",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project",
"group": "1_dbProjectsFirst@1"
},
{
"command": "sqlDatabaseProjects.publish",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.project",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project",
"group": "1_dbProjectsFirst@2"
},
{
"command": "sqlDatabaseProjects.schemaCompare",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.project",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project",
"group": "1_dbProjectsFirst@3"
},
{
"command": "sqlDatabaseProjects.newItem",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"group": "2_dbProjects_newMain@1"
},
{
"command": "sqlDatabaseProjects.newFolder",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"group": "2_dbProjects_newMain@2"
},
{
"command": "sqlDatabaseProjects.newTable",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"group": "3_dbProjects_newItem@1"
},
{
"command": "sqlDatabaseProjects.newView",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"group": "3_dbProjects_newItem@2"
},
{
"command": "sqlDatabaseProjects.newStoredProcedure",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"group": "3_dbProjects_newItem@3"
},
{
"command": "sqlDatabaseProjects.newScript",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"group": "3_dbProjects_newItem@7"
},
{
"command": "sqlDatabaseProjects.newPreDeploymentScript",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"group": "3_dbProjects_newItem@8"
},
{
"command": "sqlDatabaseProjects.newPostDeploymentScript",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project || viewItem == databaseProject.itemType.folder",
"group": "3_dbProjects_newItem@9"
},
{
"command": "sqlDatabaseProjects.addDatabaseReference",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.referencesRoot",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.referencesRoot",
"group": "4_dbProjects_addDatabaseReference"
},
{
"command": "sqlDatabaseProjects.exclude",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.folder || viewItem == databaseProject.itemType.file",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.folder || viewItem == databaseProject.itemType.file",
"group": "9_dbProjectsLast@1"
},
{
"command": "sqlDatabaseProjects.delete",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.folder || viewItem == databaseProject.itemType.file",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.folder || viewItem == databaseProject.itemType.file",
"group": "9_dbProjectsLast@2"
},
{
"command": "sqlDatabaseProjects.editProjectFile",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.project",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project",
"group": "9_dbProjectsLast@7"
},
{
"command": "sqlDatabaseProjects.openContainingFolder",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.project",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project",
"group": "9_dbProjectsLast@8"
},
{
"command": "sqlDatabaseProjects.close",
"when": "view == sqlDatabaseProjectsView && viewItem == databaseProject.itemType.project",
"when": "view =~ /^(sqlDatabaseProjectsView|dataworkspace.views.main)$/ && viewItem == databaseProject.itemType.project",
"group": "9_dbProjectsLast@9"
}
],
@@ -339,7 +344,6 @@
"group": "export"
}
],
"dataExplorer/context": [
{
"command": "sqlDatabaseProjects.importDatabase",

View File

@@ -22,6 +22,9 @@ export const msdbDacpac = 'msdb.dacpac';
export const MicrosoftDatatoolsSchemaSqlSql = 'Microsoft.Data.Tools.Schema.Sql.Sql';
export const databaseSchemaProvider = 'DatabaseSchemaProvider';
// Project Provider
export const projectTypeDisplayName = localize('projectTypeDisplayName', 'Database Project');
// commands
export const revealFileInOsCommand = 'revealFileInOS';
export const schemaCompareStartCommand = 'schemaCompare.start';

View File

@@ -5,6 +5,7 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as dataworkspace from 'dataworkspace';
import * as templates from '../templates/templates';
import * as constants from '../common/constants';
import * as path from 'path';
@@ -19,6 +20,7 @@ import { NetCoreTool } from '../tools/netcoreTool';
import { Project } from '../models/project';
import { FileNode, FolderNode } from '../models/tree/fileFolderTreeItem';
import { IconPathHelper } from '../common/iconHelper';
import { SqlDatabaseProjectProvider } from '../projectProvider/projectProvider';
const SQL_DATABASE_PROJECTS_VIEW_ID = 'sqlDatabaseProjectsView';
@@ -78,6 +80,7 @@ export default class MainController implements vscode.Disposable {
vscode.commands.registerCommand('sqlDatabaseProjects.exclude', async (node: FileNode | FolderNode) => { await this.projectsController.exclude(node); });
IconPathHelper.setExtensionContext(this.extensionContext);
this.registerProjectProvider();
// init view
const treeView = vscode.window.createTreeView(SQL_DATABASE_PROJECTS_VIEW_ID, {
@@ -190,6 +193,13 @@ export default class MainController implements vscode.Disposable {
}
}
private registerProjectProvider(): void {
const dataWorkspaceApi: dataworkspace.IExtension = <dataworkspace.IExtension>vscode.extensions.getExtension(dataworkspace.extension.name)?.exports;
if (dataWorkspaceApi) {
dataWorkspaceApi.registerProjectProvider(new SqlDatabaseProjectProvider());
}
}
public dispose(): void {
this.deactivate();
}

View File

@@ -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 dataworkspace from 'dataworkspace';
import * as vscode from 'vscode';
import { sqlprojExtension, projectTypeDisplayName } from '../common/constants';
import { IconPathHelper } from '../common/iconHelper';
import { SqlDatabaseProjectTreeViewProvider } from '../controllers/databaseProjectTreeViewProvider';
import { Project } from '../models/project';
import { BaseProjectTreeItem } from '../models/tree/baseTreeItem';
export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvider {
/**
* Gets the project tree data provider
* @param projectFilePath The project file path
*/
async getProjectTreeDataProvider(projectFilePath: string): Promise<vscode.TreeDataProvider<BaseProjectTreeItem>> {
const provider = new SqlDatabaseProjectTreeViewProvider();
const project = await Project.openProject(projectFilePath);
provider.load([project]);
return provider;
}
/**
* Gets the supported project types
*/
get supportedProjectTypes(): dataworkspace.IProjectType[] {
return [{
projectFileExtension: sqlprojExtension.replace(/\./g, ''),
displayName: projectTypeDisplayName,
icon: IconPathHelper.databaseProject
}];
}
}

View File

@@ -6,4 +6,5 @@
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../src/sql/azdata.d.ts'/>
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
/// <reference path='../../../data-workspace/src/dataworkspace.d.ts'/>
/// <reference types='@types/node'/>

View File

@@ -22,7 +22,9 @@ const extensionList = [
'notebook',
'resource-deployment',
'machine-learning',
'sql-database-projects'];
'sql-database-projects',
'data-workspace'
];
let argv = require('yargs')
.command('$0 [extensions...]')