Handle no azdata API in data-workspace extension gracefully (#15871)

This commit is contained in:
Charles Gagnon
2021-06-22 16:35:20 -07:00
committed by GitHub
parent 1e2cb1cdf9
commit 00361e52a2
8 changed files with 120 additions and 103 deletions

View File

@@ -9,7 +9,7 @@
"icon": "images/extension.png", "icon": "images/extension.png",
"aiKey": "AIF-37eefaf0-8022-4671-a3fb-64752724682e", "aiKey": "AIF-37eefaf0-8022-4671-a3fb-64752724682e",
"engines": { "engines": {
"vscode": "*", "vscode": ">=1.48.0",
"azdata": ">=1.25.0" "azdata": ">=1.25.0"
}, },
"activationEvents": [ "activationEvents": [
@@ -172,7 +172,7 @@
}, },
"dependencies": { "dependencies": {
"fast-glob": "^3.1.0", "fast-glob": "^3.1.0",
"@microsoft/ads-extension-telemetry": "^1.1.3", "@microsoft/ads-extension-telemetry": "^1.1.5",
"vscode-nls": "^4.0.0" "vscode-nls": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -5,6 +5,7 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import type * as azdataType from 'azdata';
export async function directoryExist(directoryPath: string): Promise<boolean> { export async function directoryExist(directoryPath: string): Promise<boolean> {
const stats = await getFileStatus(directoryPath); const stats = await getFileStatus(directoryPath);
@@ -55,3 +56,20 @@ export function getPackageInfo(packageJson: any): IPackageInfo | undefined {
return undefined; return undefined;
} }
// Try to load the azdata API - but gracefully handle the failure in case we're running
// in a context where the API doesn't exist (such as VS Code)
let azdataApi: typeof azdataType | undefined = undefined;
try {
azdataApi = require('azdata');
} catch {
// no-op
}
/**
* Gets the azdata API if it's available in the context this extension is running in.
* @returns The azdata API if it's available
*/
export function getAzdataApi(): typeof azdataType | undefined {
return azdataApi;
}

View File

@@ -3,12 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata'; import type * as azdataType from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as path from 'path'; import * as path from 'path';
import * as constants from '../common/constants'; import * as constants from '../common/constants';
import { IconPathHelper } from '../common/iconHelper'; import { IconPathHelper } from '../common/iconHelper';
import { directoryExist, fileExist, isCurrentWorkspaceUntitled } from '../common/utils'; import { directoryExist, fileExist, getAzdataApi, isCurrentWorkspaceUntitled } from '../common/utils';
interface Deferred<T> { interface Deferred<T> {
resolve: (result: T | Promise<T>) => void; resolve: (result: T | Promise<T>) => void;
@@ -17,15 +17,15 @@ interface Deferred<T> {
export abstract class DialogBase { export abstract class DialogBase {
protected _toDispose: vscode.Disposable[] = []; protected _toDispose: vscode.Disposable[] = [];
public dialogObject: azdata.window.Dialog; public dialogObject: azdataType.window.Dialog;
protected initDialogComplete: Deferred<void> | undefined; protected initDialogComplete: Deferred<void> | undefined;
protected initDialogPromise: Promise<void> = new Promise<void>((resolve, reject) => this.initDialogComplete = { resolve, reject }); protected initDialogPromise: Promise<void> = new Promise<void>((resolve, reject) => this.initDialogComplete = { resolve, reject });
protected workspaceDescriptionFormComponent: azdata.FormComponent | undefined; protected workspaceDescriptionFormComponent: azdataType.FormComponent | undefined;
public workspaceInputBox: azdata.InputBoxComponent | undefined; public workspaceInputBox: azdataType.InputBoxComponent | undefined;
protected workspaceInputFormComponent: azdata.FormComponent | undefined; protected workspaceInputFormComponent: azdataType.FormComponent | undefined;
constructor(dialogTitle: string, dialogName: string, okButtonText: string, dialogWidth: azdata.window.DialogWidth = 600) { constructor(dialogTitle: string, dialogName: string, okButtonText: string, dialogWidth: azdataType.window.DialogWidth = 600) {
this.dialogObject = azdata.window.createModelViewDialog(dialogTitle, dialogName, dialogWidth); this.dialogObject = getAzdataApi()!.window.createModelViewDialog(dialogTitle, dialogName, dialogWidth);
this.dialogObject.okButton.label = okButtonText; this.dialogObject.okButton.label = okButtonText;
this.register(this.dialogObject.cancelButton.onClick(() => this.onCancelButtonClicked())); this.register(this.dialogObject.cancelButton.onClick(() => this.onCancelButtonClicked()));
this.register(this.dialogObject.okButton.onClick(() => this.onOkButtonClicked())); this.register(this.dialogObject.okButton.onClick(() => this.onOkButtonClicked()));
@@ -34,17 +34,17 @@ export abstract class DialogBase {
}); });
} }
protected abstract initialize(view: azdata.ModelView): Promise<void>; protected abstract initialize(view: azdataType.ModelView): Promise<void>;
abstract validate(): Promise<boolean>; abstract validate(): Promise<boolean>;
public async open(): Promise<void> { public async open(): Promise<void> {
const tab = azdata.window.createTab(''); const tab = getAzdataApi()!.window.createTab('');
tab.registerContent(async (view: azdata.ModelView) => { tab.registerContent(async (view: azdataType.ModelView) => {
return this.initialize(view); return this.initialize(view);
}); });
this.dialogObject.content = [tab]; this.dialogObject.content = [tab];
azdata.window.openDialog(this.dialogObject); getAzdataApi()!.window.openDialog(this.dialogObject);
await this.initDialogPromise; await this.initDialogPromise;
} }
@@ -71,15 +71,15 @@ export abstract class DialogBase {
protected showErrorMessage(message: string): void { protected showErrorMessage(message: string): void {
this.dialogObject.message = { this.dialogObject.message = {
text: message, text: message,
level: azdata.window.MessageLevel.Error level: getAzdataApi()!.window.MessageLevel.Error
}; };
} }
public getErrorMessage(): azdata.window.DialogMessage { public getErrorMessage(): azdataType.window.DialogMessage {
return this.dialogObject.message; return this.dialogObject.message;
} }
protected createHorizontalContainer(view: azdata.ModelView, items: azdata.Component[]): azdata.FlexContainer { protected createHorizontalContainer(view: azdataType.ModelView, items: azdataType.Component[]): azdataType.FlexContainer {
return view.modelBuilder.flexContainer().withItems(items, { CSSStyles: { 'margin-right': '5px', 'margin-bottom': '10px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component(); return view.modelBuilder.flexContainer().withItems(items, { CSSStyles: { 'margin-right': '5px', 'margin-bottom': '10px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
} }
@@ -88,15 +88,15 @@ export abstract class DialogBase {
* created if no workspace is currently open * created if no workspace is currently open
* @param view * @param view
*/ */
protected createWorkspaceContainer(view: azdata.ModelView): void { protected createWorkspaceContainer(view: azdataType.ModelView): void {
const workspaceDescription = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ const workspaceDescription = view.modelBuilder.text().withProperties<azdataType.TextComponentProperties>({
value: vscode.workspace.workspaceFile ? constants.AddProjectToCurrentWorkspace : constants.NewWorkspaceWillBeCreated, value: vscode.workspace.workspaceFile ? constants.AddProjectToCurrentWorkspace : constants.NewWorkspaceWillBeCreated,
CSSStyles: { 'margin-top': '3px', 'margin-bottom': '0px' } CSSStyles: { 'margin-top': '3px', 'margin-bottom': '0px' }
}).component(); }).component();
const initialWorkspaceInputBoxValue = !!vscode.workspace.workspaceFile && !isCurrentWorkspaceUntitled() ? vscode.workspace.workspaceFile.fsPath : ''; const initialWorkspaceInputBoxValue = !!vscode.workspace.workspaceFile && !isCurrentWorkspaceUntitled() ? vscode.workspace.workspaceFile.fsPath : '';
this.workspaceInputBox = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ this.workspaceInputBox = view.modelBuilder.inputBox().withProperties<azdataType.InputBoxProperties>({
ariaLabel: constants.WorkspaceLocationTitle, ariaLabel: constants.WorkspaceLocationTitle,
width: constants.DefaultInputWidth, width: constants.DefaultInputWidth,
enabled: !vscode.workspace.workspaceFile || isCurrentWorkspaceUntitled(), // want it editable if no saved workspace is open enabled: !vscode.workspace.workspaceFile || isCurrentWorkspaceUntitled(), // want it editable if no saved workspace is open
@@ -104,7 +104,7 @@ export abstract class DialogBase {
title: initialWorkspaceInputBoxValue // hovertext for if file path is too long to be seen in textbox title: initialWorkspaceInputBoxValue // hovertext for if file path is too long to be seen in textbox
}).component(); }).component();
const browseFolderButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({ const browseFolderButton = view.modelBuilder.button().withProperties<azdataType.ButtonProperties>({
ariaLabel: constants.BrowseButtonText, ariaLabel: constants.BrowseButtonText,
iconPath: IconPathHelper.folder, iconPath: IconPathHelper.folder,
height: '16px', height: '16px',

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata'; import type * as azdataType from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as path from 'path'; import * as path from 'path';
import { DialogBase } from './dialogBase'; import { DialogBase } from './dialogBase';
@@ -84,11 +84,11 @@ export class NewProjectDialog extends DialogBase {
} }
} }
protected async initialize(view: azdata.ModelView): Promise<void> { protected async initialize(view: azdataType.ModelView): Promise<void> {
const allProjectTypes = await this.workspaceService.getAllProjectTypes(); const allProjectTypes = await this.workspaceService.getAllProjectTypes();
const projectTypeRadioCardGroup = view.modelBuilder.radioCardGroup().withProperties<azdata.RadioCardGroupComponentProperties>({ const projectTypeRadioCardGroup = view.modelBuilder.radioCardGroup().withProperties<azdataType.RadioCardGroupComponentProperties>({
cards: allProjectTypes.map((projectType: IProjectType) => { cards: allProjectTypes.map((projectType: IProjectType) => {
return <azdata.RadioCard>{ return <azdataType.RadioCard>{
id: projectType.id, id: projectType.id,
label: projectType.displayName, label: projectType.displayName,
icon: projectType.icon, icon: projectType.icon,
@@ -119,7 +119,7 @@ export class NewProjectDialog extends DialogBase {
this.model.projectTypeId = e.cardId; this.model.projectTypeId = e.cardId;
})); }));
const projectNameTextBox = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ const projectNameTextBox = view.modelBuilder.inputBox().withProperties<azdataType.InputBoxProperties>({
ariaLabel: constants.ProjectNameTitle, ariaLabel: constants.ProjectNameTitle,
placeHolder: constants.ProjectNamePlaceholder, placeHolder: constants.ProjectNamePlaceholder,
required: true, required: true,
@@ -133,7 +133,7 @@ export class NewProjectDialog extends DialogBase {
this.updateWorkspaceInputbox(path.join(this.model.location, this.model.name), this.model.name); this.updateWorkspaceInputbox(path.join(this.model.location, this.model.name), this.model.name);
})); }));
const locationTextBox = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ const locationTextBox = view.modelBuilder.inputBox().withProperties<azdataType.InputBoxProperties>({
ariaLabel: constants.ProjectLocationTitle, ariaLabel: constants.ProjectLocationTitle,
placeHolder: constants.ProjectLocationPlaceholder, placeHolder: constants.ProjectLocationPlaceholder,
required: true, required: true,
@@ -146,7 +146,7 @@ export class NewProjectDialog extends DialogBase {
this.updateWorkspaceInputbox(path.join(this.model.location, this.model.name), this.model.name); this.updateWorkspaceInputbox(path.join(this.model.location, this.model.name), this.model.name);
})); }));
const browseFolderButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({ const browseFolderButton = view.modelBuilder.button().withProperties<azdataType.ButtonProperties>({
ariaLabel: constants.BrowseButtonText, ariaLabel: constants.BrowseButtonText,
iconPath: IconPathHelper.folder, iconPath: IconPathHelper.folder,
height: '16px', height: '16px',

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata'; import type * as azdataType from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as path from 'path'; import * as path from 'path';
import { DialogBase } from './dialogBase'; import { DialogBase } from './dialogBase';
@@ -15,16 +15,16 @@ import { calculateRelativity, TelemetryActions, TelemetryReporter, TelemetryView
import { defaultProjectSaveLocation } from '../common/projectLocationHelper'; import { defaultProjectSaveLocation } from '../common/projectLocationHelper';
export class OpenExistingDialog extends DialogBase { export class OpenExistingDialog extends DialogBase {
public targetTypeRadioCardGroup: azdata.RadioCardGroupComponent | undefined; public targetTypeRadioCardGroup: azdataType.RadioCardGroupComponent | undefined;
public filePathTextBox: azdata.InputBoxComponent | undefined; public filePathTextBox: azdataType.InputBoxComponent | undefined;
public filePathAndButtonComponent: azdata.FormComponent | undefined; public filePathAndButtonComponent: azdataType.FormComponent | undefined;
public gitRepoTextBoxComponent: azdata.FormComponent | undefined; public gitRepoTextBoxComponent: azdataType.FormComponent | undefined;
public localClonePathComponent: azdata.FormComponent | undefined; public localClonePathComponent: azdataType.FormComponent | undefined;
public localClonePathTextBox: azdata.InputBoxComponent | undefined; public localClonePathTextBox: azdataType.InputBoxComponent | undefined;
public localRadioButton: azdata.RadioButtonComponent | undefined; public localRadioButton: azdataType.RadioButtonComponent | undefined;
public remoteGitRepoRadioButton: azdata.RadioButtonComponent | undefined; public remoteGitRepoRadioButton: azdataType.RadioButtonComponent | undefined;
public locationRadioButtonFormComponent: azdata.FormComponent | undefined; public locationRadioButtonFormComponent: azdataType.FormComponent | undefined;
public formBuilder: azdata.FormBuilder | undefined; public formBuilder: azdataType.FormBuilder | undefined;
private _targetTypes = [ private _targetTypes = [
{ {
@@ -108,7 +108,7 @@ export class OpenExistingDialog extends DialogBase {
// show git output channel // show git output channel
vscode.commands.executeCommand('git.showOutput'); vscode.commands.executeCommand('git.showOutput');
// after this executes, the git extension will show a popup asking if you want to enter the workspace // after this executes, the git extension will show a popup asking if you want to enter the workspace
await vscode.commands.executeCommand('git.clone', (<azdata.InputBoxComponent>this.gitRepoTextBoxComponent?.component).value, this.localClonePathTextBox!.value); await vscode.commands.executeCommand('git.clone', (<azdataType.InputBoxComponent>this.gitRepoTextBoxComponent?.component).value, this.localClonePathTextBox!.value);
} else { } else {
await this.workspaceService.enterWorkspace(vscode.Uri.file(this.filePathTextBox!.value!)); await this.workspaceService.enterWorkspace(vscode.Uri.file(this.filePathTextBox!.value!));
} }
@@ -124,7 +124,7 @@ export class OpenExistingDialog extends DialogBase {
.withAdditionalProperties({ selectedTarget: 'project' }) .withAdditionalProperties({ selectedTarget: 'project' })
.send(); .send();
addProjectsPromise = this.workspaceService.gitCloneProject((<azdata.InputBoxComponent>this.gitRepoTextBoxComponent?.component).value!, this.localClonePathTextBox!.value!, vscode.Uri.file(this.workspaceInputBox!.value!)); addProjectsPromise = this.workspaceService.gitCloneProject((<azdataType.InputBoxComponent>this.gitRepoTextBoxComponent?.component).value!, this.localClonePathTextBox!.value!, vscode.Uri.file(this.workspaceInputBox!.value!));
} else { } else {
if (validateWorkspace) { if (validateWorkspace) {
telemetryProps.workspaceProjectRelativity = calculateRelativity(this.filePathTextBox!.value!, this.workspaceInputBox!.value!); telemetryProps.workspaceProjectRelativity = calculateRelativity(this.filePathTextBox!.value!, this.workspaceInputBox!.value!);
@@ -149,10 +149,10 @@ export class OpenExistingDialog extends DialogBase {
} }
} }
protected async initialize(view: azdata.ModelView): Promise<void> { protected async initialize(view: azdataType.ModelView): Promise<void> {
this.targetTypeRadioCardGroup = view.modelBuilder.radioCardGroup().withProperties<azdata.RadioCardGroupComponentProperties>({ this.targetTypeRadioCardGroup = view.modelBuilder.radioCardGroup().withProperties<azdataType.RadioCardGroupComponentProperties>({
cards: this._targetTypes.map((targetType) => { cards: this._targetTypes.map((targetType) => {
return <azdata.RadioCard>{ return <azdataType.RadioCard>{
id: targetType.name, id: targetType.name,
label: targetType.name, label: targetType.name,
icon: targetType.icon, icon: targetType.icon,
@@ -176,7 +176,7 @@ export class OpenExistingDialog extends DialogBase {
selectedCardId: constants.Project selectedCardId: constants.Project
}).component(); }).component();
this.localRadioButton = view.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({ this.localRadioButton = view.modelBuilder.radioButton().withProperties<azdataType.RadioButtonProperties>({
name: 'location', name: 'location',
label: constants.Local, label: constants.Local,
checked: true checked: true
@@ -184,13 +184,13 @@ export class OpenExistingDialog extends DialogBase {
this.register(this.localRadioButton.onDidChangeCheckedState(checked => { this.register(this.localRadioButton.onDidChangeCheckedState(checked => {
if (checked) { if (checked) {
this.formBuilder?.removeFormItem(<azdata.FormComponent>this.gitRepoTextBoxComponent); this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.gitRepoTextBoxComponent);
this.formBuilder?.removeFormItem(<azdata.FormComponent>this.localClonePathComponent); this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.localClonePathComponent);
this.formBuilder?.insertFormItem(<azdata.FormComponent>this.filePathAndButtonComponent, 2); this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.filePathAndButtonComponent, 2);
} }
})); }));
this.remoteGitRepoRadioButton = view.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({ this.remoteGitRepoRadioButton = view.modelBuilder.radioButton().withProperties<azdataType.RadioButtonProperties>({
name: 'location', name: 'location',
label: constants.RemoteGitRepo label: constants.RemoteGitRepo
}).component(); }).component();
@@ -206,13 +206,13 @@ export class OpenExistingDialog extends DialogBase {
this.register(this.remoteGitRepoRadioButton.onDidChangeCheckedState(checked => { this.register(this.remoteGitRepoRadioButton.onDidChangeCheckedState(checked => {
if (checked) { if (checked) {
this.formBuilder?.removeFormItem(<azdata.FormComponent>this.filePathAndButtonComponent); this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.filePathAndButtonComponent);
this.formBuilder?.insertFormItem(<azdata.FormComponent>this.gitRepoTextBoxComponent, 2); this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.gitRepoTextBoxComponent, 2);
this.formBuilder?.insertFormItem(<azdata.FormComponent>this.localClonePathComponent, 3); this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.localClonePathComponent, 3);
} }
})); }));
const gitRepoTextBox = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ const gitRepoTextBox = view.modelBuilder.inputBox().withProperties<azdataType.InputBoxProperties>({
ariaLabel: constants.GitRepoUrlTitle, ariaLabel: constants.GitRepoUrlTitle,
placeHolder: constants.GitRepoUrlPlaceholder, placeHolder: constants.GitRepoUrlPlaceholder,
required: true, required: true,
@@ -229,7 +229,7 @@ export class OpenExistingDialog extends DialogBase {
component: gitRepoTextBox component: gitRepoTextBox
}; };
this.localClonePathTextBox = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ this.localClonePathTextBox = view.modelBuilder.inputBox().withProperties<azdataType.InputBoxProperties>({
ariaLabel: constants.LocalClonePathTitle, ariaLabel: constants.LocalClonePathTitle,
placeHolder: constants.LocalClonePathPlaceholder, placeHolder: constants.LocalClonePathPlaceholder,
required: true, required: true,
@@ -241,7 +241,7 @@ export class OpenExistingDialog extends DialogBase {
this.updateWorkspaceInputbox(this.localClonePathTextBox!.value!, path.basename(gitRepoTextBox!.value!, '.git')); this.updateWorkspaceInputbox(this.localClonePathTextBox!.value!, path.basename(gitRepoTextBox!.value!, '.git'));
})); }));
const localClonePathBrowseFolderButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({ const localClonePathBrowseFolderButton = view.modelBuilder.button().withProperties<azdataType.ButtonProperties>({
ariaLabel: constants.BrowseButtonText, ariaLabel: constants.BrowseButtonText,
iconPath: IconPathHelper.folder, iconPath: IconPathHelper.folder,
width: '18px', width: '18px',
@@ -262,7 +262,7 @@ export class OpenExistingDialog extends DialogBase {
const selectedFolder = folderUris[0].fsPath; const selectedFolder = folderUris[0].fsPath;
this.localClonePathTextBox!.value = selectedFolder; this.localClonePathTextBox!.value = selectedFolder;
this.localClonePathTextBox!.updateProperty('title', this.localClonePathTextBox!.value); this.localClonePathTextBox!.updateProperty('title', this.localClonePathTextBox!.value);
this.updateWorkspaceInputbox(path.dirname(this.localClonePathTextBox!.value!), path.basename((<azdata.InputBoxComponent>this.gitRepoTextBoxComponent?.component)!.value!, '.git')); this.updateWorkspaceInputbox(path.dirname(this.localClonePathTextBox!.value!), path.basename((<azdataType.InputBoxComponent>this.gitRepoTextBoxComponent?.component)!.value!, '.git'));
})); }));
this.localClonePathComponent = { this.localClonePathComponent = {
@@ -271,7 +271,7 @@ export class OpenExistingDialog extends DialogBase {
required: true required: true
}; };
this.filePathTextBox = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ this.filePathTextBox = view.modelBuilder.inputBox().withProperties<azdataType.InputBoxProperties>({
ariaLabel: constants.LocationSelectorTitle, ariaLabel: constants.LocationSelectorTitle,
placeHolder: constants.ProjectFilePlaceholder, placeHolder: constants.ProjectFilePlaceholder,
required: true, required: true,
@@ -283,7 +283,7 @@ export class OpenExistingDialog extends DialogBase {
this.updateWorkspaceInputbox(path.dirname(this.filePathTextBox!.value!), path.basename(this.filePathTextBox!.value!, path.extname(this.filePathTextBox!.value!))); this.updateWorkspaceInputbox(path.dirname(this.filePathTextBox!.value!), path.basename(this.filePathTextBox!.value!, path.extname(this.filePathTextBox!.value!)));
})); }));
const localProjectBrowseFolderButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({ const localProjectBrowseFolderButton = view.modelBuilder.button().withProperties<azdataType.ButtonProperties>({
ariaLabel: constants.BrowseButtonText, ariaLabel: constants.BrowseButtonText,
iconPath: IconPathHelper.folder, iconPath: IconPathHelper.folder,
width: '18px', width: '18px',
@@ -308,12 +308,12 @@ export class OpenExistingDialog extends DialogBase {
this.filePathTextBox!.placeHolder = constants.ProjectFilePlaceholder; this.filePathTextBox!.placeHolder = constants.ProjectFilePlaceholder;
if (this.remoteGitRepoRadioButton!.checked) { if (this.remoteGitRepoRadioButton!.checked) {
this.formBuilder?.removeFormItem(<azdata.FormComponent>this.filePathAndButtonComponent); this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.filePathAndButtonComponent);
this.formBuilder?.insertFormItem(<azdata.FormComponent>this.gitRepoTextBoxComponent, 2); this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.gitRepoTextBoxComponent, 2);
this.formBuilder?.insertFormItem(<azdata.FormComponent>this.localClonePathComponent, 3); this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.localClonePathComponent, 3);
} else { } else {
this.formBuilder?.removeFormItem(<azdata.FormComponent>this.gitRepoTextBoxComponent); this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.gitRepoTextBoxComponent);
this.formBuilder?.removeFormItem(<azdata.FormComponent>this.localClonePathComponent); this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.localClonePathComponent);
this.formBuilder?.addFormItem(this.filePathAndButtonComponent!); this.formBuilder?.addFormItem(this.filePathAndButtonComponent!);
} }
@@ -326,9 +326,9 @@ export class OpenExistingDialog extends DialogBase {
this.formBuilder?.removeFormItem(this.workspaceInputFormComponent!); this.formBuilder?.removeFormItem(this.workspaceInputFormComponent!);
if (this.remoteGitRepoRadioButton!.checked) { if (this.remoteGitRepoRadioButton!.checked) {
this.formBuilder?.removeFormItem(<azdata.FormComponent>this.filePathAndButtonComponent); this.formBuilder?.removeFormItem(<azdataType.FormComponent>this.filePathAndButtonComponent);
this.formBuilder?.insertFormItem(<azdata.FormComponent>this.gitRepoTextBoxComponent, 2); this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.gitRepoTextBoxComponent, 2);
this.formBuilder?.insertFormItem(<azdata.FormComponent>this.localClonePathComponent, 3); this.formBuilder?.insertFormItem(<azdataType.FormComponent>this.localClonePathComponent, 3);
} }
} }

View File

@@ -3,23 +3,23 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata'; import type * as azdataType from 'azdata';
import { IDashboardColumnInfo, IDashboardTable, IProjectAction, IProjectActionGroup, IProjectProvider, WorkspaceTreeItem } from 'dataworkspace'; import { IDashboardColumnInfo, IDashboardTable, IProjectAction, IProjectActionGroup, IProjectProvider, WorkspaceTreeItem } from 'dataworkspace';
import * as path from 'path'; import * as path from 'path';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as constants from '../common/constants'; import * as constants from '../common/constants';
import { IconPathHelper } from '../common/iconHelper'; import { IconPathHelper } from '../common/iconHelper';
import { IWorkspaceService } from '../common/interfaces'; import { IWorkspaceService } from '../common/interfaces';
import { fileExist } from '../common/utils'; import { fileExist, getAzdataApi } from '../common/utils';
export class ProjectDashboard { export class ProjectDashboard {
private dashboard: azdata.window.ModelViewDashboard | undefined; private dashboard: azdataType.window.ModelViewDashboard | undefined;
private modelView: azdata.ModelView | undefined; private modelView: azdataType.ModelView | undefined;
private projectProvider: IProjectProvider | undefined; private projectProvider: IProjectProvider | undefined;
private overviewTab: azdata.DashboardTab | undefined; private overviewTab: azdataType.DashboardTab | undefined;
private rootContainer: azdata.FlexContainer | undefined; private rootContainer: azdataType.FlexContainer | undefined;
private tableContainer: azdata.Component | undefined; private tableContainer: azdataType.Component | undefined;
constructor(private _workspaceService: IWorkspaceService, private _treeItem: WorkspaceTreeItem) { constructor(private _workspaceService: IWorkspaceService, private _treeItem: WorkspaceTreeItem) {
} }
@@ -45,8 +45,8 @@ export class ProjectDashboard {
} }
private async createDashboard(title: string, projectFilePath: string): Promise<void> { private async createDashboard(title: string, projectFilePath: string): Promise<void> {
this.dashboard = azdata.window.createModelViewDashboard(title, 'ProjectDashboard', { alwaysShowTabs: false }); this.dashboard = getAzdataApi()!.window.createModelViewDashboard(title, 'ProjectDashboard', { alwaysShowTabs: false });
this.dashboard.registerTabs(async (modelView: azdata.ModelView) => { this.dashboard.registerTabs(async (modelView: azdataType.ModelView) => {
this.modelView = modelView; this.modelView = modelView;
this.overviewTab = { this.overviewTab = {
@@ -61,11 +61,11 @@ export class ProjectDashboard {
}); });
} }
private createToolbarContainer(projectFilePath: string): azdata.ToolbarContainer { private createToolbarContainer(projectFilePath: string): azdataType.ToolbarContainer {
const projectActions: (IProjectAction | IProjectActionGroup)[] = this.projectProvider!.projectToolbarActions; const projectActions: (IProjectAction | IProjectActionGroup)[] = this.projectProvider!.projectToolbarActions;
// Add actions as buttons // Add actions as buttons
const buttons: azdata.ToolbarComponent[] = []; const buttons: azdataType.ToolbarComponent[] = [];
const projectActionsLength = projectActions.length; const projectActionsLength = projectActions.length;
@@ -84,7 +84,7 @@ export class ProjectDashboard {
}); });
const refreshButton = this.modelView!.modelBuilder.button() const refreshButton = this.modelView!.modelBuilder.button()
.withProperties<azdata.ButtonProperties>({ .withProperties<azdataType.ButtonProperties>({
label: constants.Refresh, label: constants.Refresh,
iconPath: IconPathHelper.refresh, iconPath: IconPathHelper.refresh,
height: '20px' height: '20px'
@@ -108,9 +108,9 @@ export class ProjectDashboard {
return obj.id !== undefined; return obj.id !== undefined;
} }
private createButton(projectAction: IProjectAction): azdata.ButtonComponent { private createButton(projectAction: IProjectAction): azdataType.ButtonComponent {
let button = this.modelView!.modelBuilder.button() let button = this.modelView!.modelBuilder.button()
.withProperties<azdata.ButtonProperties>({ .withProperties<azdataType.ButtonProperties>({
label: projectAction.id, label: projectAction.id,
iconPath: projectAction.icon, iconPath: projectAction.icon,
height: '20px' height: '20px'
@@ -123,7 +123,7 @@ export class ProjectDashboard {
return button; return button;
} }
private createContainer(title: string, projectFilePath: string): azdata.FlexContainer { private createContainer(title: string, projectFilePath: string): azdataType.FlexContainer {
this.rootContainer = this.modelView!.modelBuilder.flexContainer().withLayout( this.rootContainer = this.modelView!.modelBuilder.flexContainer().withLayout(
{ {
flexFlow: 'column', flexFlow: 'column',
@@ -143,7 +143,7 @@ export class ProjectDashboard {
/** /**
* Create header with title, location and background * Create header with title, location and background
*/ */
private createHeader(title: string, location: string): azdata.Component { private createHeader(title: string, location: string): azdataType.Component {
const headerContainer = this.modelView!.modelBuilder.flexContainer().withLayout( const headerContainer = this.modelView!.modelBuilder.flexContainer().withLayout(
{ {
flexFlow: 'column', flexFlow: 'column',
@@ -159,13 +159,13 @@ export class ProjectDashboard {
}).component(); }).component();
const titleLabel = this.modelView!.modelBuilder.text() const titleLabel = this.modelView!.modelBuilder.text()
.withProperties<azdata.TextComponentProperties>({ value: title, CSSStyles: { 'margin-block-start': '20px', 'margin-block-end': '0px' } }) .withProperties<azdataType.TextComponentProperties>({ value: title, CSSStyles: { 'margin-block-start': '20px', 'margin-block-end': '0px' } })
.component(); .component();
header.addItem(titleLabel, { CSSStyles: { 'padding-left': '34px', 'padding-top': '15px', 'font-size': '36px', 'font-weight': '400' } }); header.addItem(titleLabel, { CSSStyles: { 'padding-left': '34px', 'padding-top': '15px', 'font-size': '36px', 'font-weight': '400' } });
const projectFolderPath = path.dirname(location); const projectFolderPath = path.dirname(location);
const locationLabel = this.modelView!.modelBuilder.text() const locationLabel = this.modelView!.modelBuilder.text()
.withProperties<azdata.TextComponentProperties>({ value: projectFolderPath, CSSStyles: { 'margin-block-start': '20px', 'margin-block-end': '0px' } }) .withProperties<azdataType.TextComponentProperties>({ value: projectFolderPath, CSSStyles: { 'margin-block-start': '20px', 'margin-block-end': '0px' } })
.component(); .component();
header.addItem(locationLabel, { CSSStyles: { 'padding-left': '34px', 'padding-top': '15px', 'padding-bottom': '50px', 'font-size': '16px' } }); header.addItem(locationLabel, { CSSStyles: { 'padding-left': '34px', 'padding-top': '15px', 'padding-bottom': '50px', 'font-size': '16px' } });
@@ -188,7 +188,7 @@ export class ProjectDashboard {
/** /**
* Adds all the tables to the container * Adds all the tables to the container
*/ */
private createTables(projectFile: string): azdata.Component { private createTables(projectFile: string): azdataType.Component {
const dashboardData: IDashboardTable[] = this.projectProvider!.getDashboardComponents(projectFile); const dashboardData: IDashboardTable[] = this.projectProvider!.getDashboardComponents(projectFile);
const tableContainer = this.modelView!.modelBuilder.flexContainer().withLayout( const tableContainer = this.modelView!.modelBuilder.flexContainer().withLayout(
@@ -200,22 +200,22 @@ export class ProjectDashboard {
dashboardData.forEach(info => { dashboardData.forEach(info => {
const tableNameLabel = this.modelView!.modelBuilder.text() const tableNameLabel = this.modelView!.modelBuilder.text()
.withProperties<azdata.TextComponentProperties>({ value: info.name, CSSStyles: { 'margin-block-start': '30px', 'margin-block-end': '0px' } }) .withProperties<azdataType.TextComponentProperties>({ value: info.name, CSSStyles: { 'margin-block-start': '30px', 'margin-block-end': '0px' } })
.component(); .component();
tableContainer.addItem(tableNameLabel, { CSSStyles: { 'padding-left': '25px', 'padding-bottom': '20px', ...constants.cssStyles.title } }); tableContainer.addItem(tableNameLabel, { CSSStyles: { 'padding-left': '25px', 'padding-bottom': '20px', ...constants.cssStyles.title } });
if (info.data.length === 0) { if (info.data.length === 0) {
const noDataText = constants.noPreviousData(info.name.toLocaleLowerCase()); const noDataText = constants.noPreviousData(info.name.toLocaleLowerCase());
const noDataLabel = this.modelView!.modelBuilder.text() const noDataLabel = this.modelView!.modelBuilder.text()
.withProperties<azdata.TextComponentProperties>({ value: noDataText }) .withProperties<azdataType.TextComponentProperties>({ value: noDataText })
.component(); .component();
tableContainer.addItem(noDataLabel, { CSSStyles: { 'padding-left': '25px', 'padding-bottom': '20px' } }); tableContainer.addItem(noDataLabel, { CSSStyles: { 'padding-left': '25px', 'padding-bottom': '20px' } });
} else { } else {
const columns: azdata.DeclarativeTableColumn[] = []; const columns: azdataType.DeclarativeTableColumn[] = [];
info.columns.forEach((column: IDashboardColumnInfo) => { info.columns.forEach((column: IDashboardColumnInfo) => {
let col = { let col = {
displayName: column.displayName, displayName: column.displayName,
valueType: column.type === 'icon' ? azdata.DeclarativeDataType.component : azdata.DeclarativeDataType.string, valueType: column.type === 'icon' ? getAzdataApi()!.DeclarativeDataType.component : getAzdataApi()!.DeclarativeDataType.string,
isReadOnly: true, isReadOnly: true,
width: column.width, width: column.width,
headerCssStyles: { headerCssStyles: {
@@ -229,21 +229,21 @@ export class ProjectDashboard {
columns.push(col); columns.push(col);
}); });
const data: azdata.DeclarativeTableCellValue[][] = []; const data: azdataType.DeclarativeTableCellValue[][] = [];
info.data.forEach(values => { info.data.forEach(values => {
const columnValue: azdata.DeclarativeTableCellValue[] = []; const columnValue: azdataType.DeclarativeTableCellValue[] = [];
values.forEach(val => { values.forEach(val => {
if (typeof val === 'string') { if (typeof val === 'string') {
columnValue.push({ value: val }); columnValue.push({ value: val });
} else { } else {
const iconComponent = this.modelView!.modelBuilder.image().withProperties<azdata.ImageComponentProperties>({ const iconComponent = this.modelView!.modelBuilder.image().withProperties<azdataType.ImageComponentProperties>({
iconPath: val.icon, iconPath: val.icon,
width: '15px', width: '15px',
height: '15px', height: '15px',
iconHeight: '15px', iconHeight: '15px',
iconWidth: '15px' iconWidth: '15px'
}).component(); }).component();
const stringComponent = this.modelView!.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ const stringComponent = this.modelView!.modelBuilder.text().withProperties<azdataType.TextComponentProperties>({
value: val.text, value: val.text,
CSSStyles: { 'margin-block-start': 'auto', 'block-size': 'auto', 'margin-block-end': '0px' } CSSStyles: { 'margin-block-start': 'auto', 'block-size': 'auto', 'margin-block-end': '0px' }
}).component(); }).component();
@@ -256,7 +256,7 @@ export class ProjectDashboard {
}); });
const table = this.modelView!.modelBuilder.declarativeTable() const table = this.modelView!.modelBuilder.declarativeTable()
.withProperties<azdata.DeclarativeTableProperties>({ columns: columns, dataValues: data, ariaLabel: info.name, CSSStyles: { 'margin-left': '30px' } }).component(); .withProperties<azdataType.DeclarativeTableProperties>({ columns: columns, dataValues: data, ariaLabel: info.name, CSSStyles: { 'margin-left': '30px' } }).component();
tableContainer.addItem(table); tableContainer.addItem(table);
} }

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as dataworkspace from 'dataworkspace'; import * as dataworkspace from 'dataworkspace';
import * as path from 'path'; import * as path from 'path';
@@ -14,7 +13,7 @@ import { IWorkspaceService } from '../common/interfaces';
import { ProjectProviderRegistry } from '../common/projectProviderRegistry'; import { ProjectProviderRegistry } from '../common/projectProviderRegistry';
import Logger from '../common/logger'; import Logger from '../common/logger';
import { TelemetryReporter, TelemetryViews, calculateRelativity, TelemetryActions } from '../common/telemetry'; import { TelemetryReporter, TelemetryViews, calculateRelativity, TelemetryActions } from '../common/telemetry';
import { isCurrentWorkspaceUntitled } from '../common/utils'; import { getAzdataApi, isCurrentWorkspaceUntitled } from '../common/utils';
const WorkspaceConfigurationName = 'dataworkspace'; const WorkspaceConfigurationName = 'dataworkspace';
const ProjectsConfigurationName = 'projects'; const ProjectsConfigurationName = 'projects';
@@ -58,9 +57,9 @@ export class WorkspaceService implements IWorkspaceService {
if (isCurrentWorkspaceUntitled()) { if (isCurrentWorkspaceUntitled()) {
vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders!.length, null, { uri: projectFolder }); vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders!.length, null, { uri: projectFolder });
await azdata.workspace.saveAndEnterWorkspace(workspaceFile!); await getAzdataApi()?.workspace.saveAndEnterWorkspace(workspaceFile!);
} else { } else {
await azdata.workspace.createAndEnterWorkspace(projectFolder, workspaceFile); await getAzdataApi()?.workspace.createAndEnterWorkspace(projectFolder, workspaceFile);
} }
} }
@@ -98,7 +97,7 @@ export class WorkspaceService implements IWorkspaceService {
async enterWorkspace(workspaceFile: vscode.Uri): Promise<void> { async enterWorkspace(workspaceFile: vscode.Uri): Promise<void> {
const result = await vscode.window.showWarningMessage(constants.EnterWorkspaceConfirmation, { modal: true }, constants.OkButtonText); const result = await vscode.window.showWarningMessage(constants.EnterWorkspaceConfirmation, { modal: true }, constants.OkButtonText);
if (result === constants.OkButtonText) { if (result === constants.OkButtonText) {
await azdata.workspace.enterWorkspace(workspaceFile); await getAzdataApi()?.workspace.enterWorkspace(workspaceFile);
} else { } else {
return; return;
} }

View File

@@ -182,10 +182,10 @@
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==
"@microsoft/ads-extension-telemetry@^1.1.3": "@microsoft/ads-extension-telemetry@^1.1.5":
version "1.1.3" version "1.1.5"
resolved "https://registry.yarnpkg.com/@microsoft/ads-extension-telemetry/-/ads-extension-telemetry-1.1.3.tgz#54160eefa21f2a536622b0617f3c3f2018cf9c87" resolved "https://registry.yarnpkg.com/@microsoft/ads-extension-telemetry/-/ads-extension-telemetry-1.1.5.tgz#4f2ec72a7730131fdd939ace307a1ff5feef16b6"
integrity sha512-+h6hM9oOA6Zj/N0nCGPzRgydR0YHiHpNJoNlv6a/ziWXO3RYSbQX+3U/PpT3gEA6+8RwByf6RVICo7uIGBy1LQ== integrity sha512-Xq8qQi8CHxXPTCO5cRvJABgtVdFO42kQgVoHkBc7ByBhN4VMdw/kakbWgVtHbMl4oRARtpncE2xYCiVeZHK6XA==
dependencies: dependencies:
vscode-extension-telemetry "^0.1.6" vscode-extension-telemetry "^0.1.6"