Compare commits

...

22 Commits

Author SHA1 Message Date
Karl Burtram
83544eba25 Bump to 1.15.1 for Hotfix release (#9162) 2020-02-14 18:30:08 -08:00
Alex Ma
fd5e9c087a call refreshDatasets upon loading new dataset (#9160)
* call refreshDatasets upon loading new dataset

* readded space
2020-02-14 18:27:20 -08:00
Charles Gagnon
8f6f84178c Make connectionProfileGroup support undefined values (#9102)
* Make connectionProfileGroup able to support undefined values being passed

* Fix strict null checks

* More strict null check fixes
2020-02-14 18:27:00 -08:00
Anthony Dresser
e57eb999fc Revert "Revert "fix ctrl+tab keybinding"" (#9121)
This reverts commit 99af31fa1f.
2020-02-11 16:01:53 -08:00
Anthony Dresser
99af31fa1f Revert "fix ctrl+tab keybinding"
This reverts commit e2990db33e.
2020-02-11 14:55:16 -08:00
Anthony Dresser
e2990db33e fix ctrl+tab keybinding 2020-02-11 14:53:59 -08:00
Alan Ren
b2238dc37b fix charting issue (#9094)
* fix charting issue

* fix charting issue 2

* revert version bump

(cherry picked from commit 8c61538a27)
2020-02-08 17:45:32 -08:00
Amir Omidi
6770bf877e ok button targetted fix (#9095) 2020-02-07 17:17:58 -08:00
Amir Omidi
ba66305bb5 Add device code authentication (#9097)
* Add device code authentication

* Change string
2020-02-07 17:15:08 -08:00
Charles Gagnon
8e731c75c3 Remove quality checks for events (#8934)
* Remove quality checks for events

* Update iKey

* Add missed quality check

(cherry picked from commit ec0a7a29d3)
2020-02-07 14:48:23 -08:00
Alex Ma
db0409569a made changes (#9092) 2020-02-07 14:31:53 -08:00
Amir Omidi
fb4ab6b501 Show error to user (#9014)
* Show error to user

* Update the errors

* change message

(cherry picked from commit df3f1768f7)
2020-02-07 14:15:02 -08:00
Amir Omidi
bd1d96a57a Amir/target/notarization (#9082)
* runtime hardening

* Notarization

* Fix the parameters

* Update sql-product-build-darwin.yml

* Format

* Change task name

* Remove the array

* Support new format

* Use new format for notarizing too

* Change timeouts to accommodate how long apple takes

* Change timeout in minutes to 2 hours

* Update sql-product-build-darwin.yml

* Increase timeouts

* Change the pipeline

* Fix formatting

* Fix formatting

* Fix formatting

* Fix formatting

* Change path

* Fix path for cleanup

* Don't do hardening

* Enable hardening again

* Self sign

* add display name

* Change key identity

* Deep codesign

* Fix path

* add extras

* add hardening back

* use ditto instead of zip per apple docs

* Changing signing syntax

* Don't deep sign

* Use --deep and add new entitlement

* Temporarily disable notarization

* entitlements fix

* Don't do runtime hardening for self sign

* Will this fix codesign I wonder

* codesigning

* sign everything first

* Change signing to delete bad files

* remove xattr

* Change the find command

* Delete mssql and big-data-cluster

* Stablize the signing

* Bring in notarization changes
2020-02-07 13:08:21 -08:00
Amir Omidi
6fe49e8cef Targeted fix for saving issue on MacOS (#9073)
* Create a targeted fix

* Add sql carbon edit
2020-02-06 16:22:21 -08:00
Alex Ma
6da98ed439 disable updateNameFromFirstLine from untitledTextEditorModel (#9064)
* disable updateNameFromFirstLine

* change to either

* WIP commit

* final fix

* formatting

* fix for tab space

* replace tab with space

* Fix formatting

Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>
2020-02-06 14:09:45 -08:00
Chris LaFreniere
5ef69f5133 BDC: Add another command for bdc extension activation events (#9050)
* Add another command for bdc extension activation

* spaces

* Add other activation events

* spacing

(cherry picked from commit 8e67b104c3)
2020-02-05 16:05:49 -08:00
Alex Ma
ebf47df35e removed unnecessary formatting (#9045) 2020-02-04 14:01:42 -08:00
Amir Omidi
2a93d7c5e9 Move files locations (#9015) (#9044)
* Move files locations

* Extension path

(cherry picked from commit c6689700f6)
2020-02-04 13:42:06 -08:00
Chris LaFreniere
573c5b4470 Books: Ensure associatedResource is Present for Untitled Notebooks that have a File Associated with them (#9037)
* Conditionally add associatedResource t untitled nb

* Fix Jupyter Book notebook titles (#9039)

* Fix notebook titles

* Fix navigation links for books

* Added comments

Co-authored-by: Chris LaFreniere <40371649+chlafreniere@users.noreply.github.com>

* PR comment nit

Co-authored-by: Charles Gagnon <chgagnon@microsoft.com>
(cherry picked from commit 2208666eef)
2020-02-04 11:49:01 -08:00
Karl Burtram
41838b7720 Import "fromPromise" so Observables work correctly (#9034) (#9042)
* Import "fromPromise" so Observables work correctly

* Bump distro guid

* Fix unit test missing file error

* Bump distro to pick up latest changes
2020-02-04 11:40:26 -08:00
Charles Gagnon
ee1359fd7c Even more robust handling of extension tree loading (#9007)
* Even more robust handling of extension tree loading

* Fix initial loading and add new test for CMS

* Fix compile errors

* Fix logic

* Remove debug log statements

(cherry picked from commit 62df5359e2)
2020-02-04 08:05:08 -08:00
Alex Ma
189bcf703e Fix for table name not showing up on the header. (#9016)
* fix for edit data table header

* Fix for name, and SQL load

(cherry picked from commit 5b2c14f11a)
2020-01-31 14:32:27 -08:00
47 changed files with 462 additions and 221 deletions

View File

@@ -5,11 +5,6 @@ REPO="$(pwd)"
# ensure drop directories exist
mkdir -p $REPO/.build/darwin/{archive,server}
# remove pkg from archive
if [[ "$SIGNED" == "true" ]]; then
zip -d $REPO/.build/darwin/archive/azuredatastudio-darwin.zip "*.pkg"
fi
# package Remote Extension Host
pushd .. && mv azuredatastudio-reh-darwin azuredatastudio-server-darwin && zip -Xry $REPO/.build/darwin/server/azuredatastudio-server-darwin.zip azuredatastudio-server-darwin && popd

View File

@@ -10,5 +10,13 @@
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>
</plist>

View File

@@ -1,4 +1,9 @@
steps:
- task: InstallAppleCertificate@2
displayName: 'Install developer certificate'
inputs:
certSecureFile: 'osx_signing_key.p12'
- script: |
mkdir -p .build
echo -n $BUILD_SOURCEVERSION > .build/commit
@@ -107,12 +112,48 @@ steps:
displayName: Run unit tests
condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true'))
- script: |
set -e
pushd ../azuredatastudio-darwin
ls
echo "Cleaning the application"
xattr -cr *.app
cd *.app
find . -name '._*' -print0 | xargs -0 rm -rf --
cd ..
echo "Signing the application with deep"
codesign --deep --force --timestamp --options runtime --entitlements $(Build.SourcesDirectory)/build/azure-pipelines/darwin/entitlements.xml -s LPV3BJJYXS *.app
cd *.app
ls
echo "Signing specific components"
find . -type f -print0 | xargs -0 file | grep ': *Mach-O' | sed 's/: *Mach-O.*//' | while read -r file; do codesign --options runtime --timestamp --entitlements $(Build.SourcesDirectory)/build/azure-pipelines/darwin/entitlements.xml -s LPV3BJJYXS --force "$file" || break; done
echo "Signing Electron again..."
codesign --force --timestamp --options runtime --entitlements $(Build.SourcesDirectory)/build/azure-pipelines/darwin/entitlements.xml -s LPV3BJJYXS Contents/Frameworks/Electron\ Framework.framework
cd ..
echo "Signing the entire application one more time"
codesign --force --timestamp --options runtime --entitlements $(Build.SourcesDirectory)/build/azure-pipelines/darwin/entitlements.xml -s LPV3BJJYXS *.app
popd
displayName: 'Manual codesign'
- script: |
set -e
mkdir -p .build/darwin/archive
pushd ../azuredatastudio-darwin && zip -r -X -y $(Build.SourcesDirectory)/.build/darwin/archive/azuredatastudio-darwin.zip * && popd
pushd ../azuredatastudio-darwin
ditto -c -k --keepParent *.app $(Build.SourcesDirectory)/.build/darwin/archive/azuredatastudio-darwin.zip
popd
displayName: 'Archive'
- task: PublishPipelineArtifact@0
displayName: 'Publish SelfSigned'
inputs:
artifactName: darwin-selfsigned
targetPath: $(Build.SourcesDirectory)/.build/darwin/archive/azuredatastudio-darwin.zip
- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1
displayName: 'ESRP CodeSigning'
inputs:
@@ -124,14 +165,57 @@ steps:
[
{
"keyCode": "CP-401337-Apple",
"operationSetCode": "MacAppDeveloperSign",
"parameters": [],
"operationCode": "MacAppDeveloperSign",
"parameters": {
"Hardening": "Enable"
},
"toolName": "sign",
"toolVersion": "1.0"
}
]
SessionTimeout: 125
condition: and(succeeded(), eq(variables['signed'], true))
SessionTimeout: 90
condition: and(succeeded(), eq(variables['signtba'], true))
- script: |
zip -d $(Build.SourcesDirectory)/.build/darwin/archive/azuredatastudio-darwin.zip "*.pkg"
displayName: Clean Archive
condition: and(succeeded(), eq(variables['signtba'], true))
- task: PublishPipelineArtifact@0
displayName: 'Publish Signed'
inputs:
artifactName: darwin-signed
targetPath: $(Build.SourcesDirectory)/.build/darwin/archive/azuredatastudio-darwin.zip
condition: and(succeeded(), eq(variables['signtba'], true))
- task: EsrpCodeSigning@1
displayName: 'ESRP Notarization'
inputs:
ConnectedServiceName: 'Code Signing'
FolderPath: '$(Build.SourcesDirectory)/.build/darwin/archive'
Pattern: 'azuredatastudio-darwin.zip'
signConfigType: inlineSignParams
inlineOperation: |
[
{
"KeyCode": "CP-401337-Apple",
"OperationCode": "MacAppNotarize",
"Parameters": {
"BundleId": "com.microsoft.azuredatastudio-$(VSCODE_QUALITY)"
},
"ToolName": "sign",
"ToolVersion": "1.0"
}
]
SessionTimeout: 120
condition: and(succeeded(), eq(variables['signtba'], true))
- task: PublishPipelineArtifact@0
displayName: 'Publish Notarized'
inputs:
artifactName: darwin-notarized
targetPath: $(Build.SourcesDirectory)/.build/darwin/archive/azuredatastudio-darwin.zip
condition: and(succeeded(), eq(variables['signtba'], true))
- script: |
set -e

View File

@@ -43,6 +43,7 @@ const nodeModules = [
'electron',
'original-fs',
'rxjs/Observable',
'rxjs/add/observable/fromPromise',
'rxjs/Subject',
'rxjs/Observer',
'slickgrid/lib/jquery.event.drag-2.3.0',

View File

@@ -116,7 +116,7 @@ function submitAllStats(productJson, commit) {
}
*/
appInsights.defaultClient.trackEvent({
name: `${productJson.quality !== 'stable' ? 'adsworkbench' : 'monacoworkbench'}/packagemetrics`,
name: 'adsworkbench/packagemetrics',
properties: { commit, size: JSON.stringify(sizes), count: JSON.stringify(counts) }
});
appInsights.defaultClient.flush({

View File

@@ -126,7 +126,7 @@ export function submitAllStats(productJson: any, commit: string): Promise<boolea
}
*/
appInsights.defaultClient.trackEvent({
name: `${productJson.quality !== 'stable' ? 'adsworkbench' : 'monacoworkbench'}/packagemetrics`, // {{SQL CARBON EDIT}}
name: 'adsworkbench/packagemetrics', // {{SQL CARBON EDIT}}
properties: { commit, size: JSON.stringify(sizes), count: JSON.stringify(counts) }
});

View File

@@ -39,6 +39,11 @@
"type": "boolean",
"default": true,
"description": "%config.enablePublicCloudDescription%"
},
"accounts.azure.enableDeviceCodeLogin": {
"type": "boolean",
"default": false,
"description": "%config.enableDeviceCodeLogin%"
}
}
}

View File

@@ -19,5 +19,5 @@
"config.enableUsGovCloudDescription": "Should US Government Azure cloud (Fairfax) integration be enabled",
"config.enableChinaCloudDescription": "Should Azure China integration be enabled",
"config.enableGermanyCloudDescription": "Should Azure Germany integration be enabled",
"config.useNewSignInExperience": "Use the new Azure sign in experience"
"config.enableDeviceCodeLogin": "Enable Azure Active Directory device code login mechanism"
}

View File

@@ -41,7 +41,7 @@ export class AzureAccountProvider implements azdata.AccountProvider {
private isInitialized: boolean = false;
constructor(private metadata: AzureAccountProviderMetadata, private _tokenCache: TokenCache) {
constructor(private metadata: AzureAccountProviderMetadata, private _tokenCache: TokenCache, private _context: vscode.ExtensionContext) {
this.commonAuthorityUrl = url.resolve(this.metadata.settings.host, AzureAccountProvider.AadCommonTenant);
}
@@ -79,7 +79,9 @@ export class AzureAccountProvider implements azdata.AccountProvider {
context.cache.add([response], (err, result) => {
if (err || !result) {
console.log(`Unexpected error adding token to cache ${err}`);
const msg = localize('azure.tokenCacheFail', "Unexpected error adding token to cache: {0}", err.message);
vscode.window.showErrorMessage(msg);
console.log(err);
}
});
@@ -189,6 +191,8 @@ export class AzureAccountProvider implements azdata.AccountProvider {
nonce: string,
authUrl: string) {
const mediaPath = path.join(this._context.extensionPath, 'media');
// Utility function
const sendFile = async (res: http.ServerResponse, filePath: string, contentType: string): Promise<void> => {
let fileContents;
@@ -240,12 +244,13 @@ export class AzureAccountProvider implements azdata.AccountProvider {
return;
}
sendFile(res, path.join(__dirname, 'media/landing.html'), 'text/html; charset=utf-8').catch(console.error);
sendFile(res, path.join(mediaPath, 'landing.html'), 'text/html; charset=utf-8').catch(console.error);
this.handleAuthentication(code).catch((e) => console.error(e));
};
const css = (req: http.IncomingMessage, res: http.ServerResponse, reqUrl: url.UrlWithParsedQuery) => {
sendFile(res, path.join(__dirname, 'media/landing.css'), 'text/css; charset=utf-8').catch(console.error);
sendFile(res, path.join(mediaPath, 'landing.css'), 'text/css; charset=utf-8').catch(console.error);
};
pathMappings.set('/signin', initialSignIn);
@@ -284,7 +289,9 @@ export class AzureAccountProvider implements azdata.AccountProvider {
try {
graphToken = await this.getToken(userId, value.tenantId, this.metadata.settings.graphResource.id);
} catch (ex) {
console.log(`Your authentication to the tenant ${value.tenantId} failed: ${ex}`);
const msg = localize('azure.authFail', "Your authentication to the tenant {0} failed: {1}", value.tenantId, ex);
vscode.window.showErrorMessage(msg);
console.log(msg);
return undefined;
}
@@ -305,7 +312,9 @@ export class AzureAccountProvider implements azdata.AccountProvider {
tenants = tenants.filter(t => t !== undefined);
if (tenants.length === 0) {
throw new Error(localize('azure.noTenants', "No azure tenants found. Failing..."));
const msg = localize('azure.noTenants', "Failed to add account. No Azure tenants.");
vscode.window.showErrorMessage(msg);
throw new Error(msg);
}
if (homeTenant) {

View File

@@ -11,7 +11,8 @@ import * as path from 'path';
import * as vscode from 'vscode';
import CredentialServiceTokenCache from './tokenCache';
import providerSettings from './providerSettings';
import { AzureAccountProvider as AzureAccountProvider } from './azureAccountProvider2';
import { AzureAccountProvider as OAuthAzureAccountProvider } from './azureAccountProvider2';
import { AzureAccountProvider as DeviceCodeAzureAccountProvider } from './azureAccountProvider';
import { AzureAccountProviderMetadata, ProviderSettings } from './interfaces';
let localize = nls.loadMessageBundle();
@@ -133,12 +134,18 @@ export class AzureAccountProviderService implements vscode.Disposable {
return new Promise((resolve, reject) => {
try {
//let config = vscode.workspace.getConfiguration(AzureAccountProviderService.ConfigurationSection);
let config = vscode.workspace.getConfiguration(AzureAccountProviderService.ConfigurationSection);
let tokenCacheKey = `azureTokenCache-${provider.metadata.id}`;
let tokenCachePath = path.join(this._userStoragePath, tokenCacheKey);
let tokenCache = new CredentialServiceTokenCache(self._credentialProvider, tokenCacheKey, tokenCachePath);
let accountProvider = new AzureAccountProvider(provider.metadata as AzureAccountProviderMetadata, tokenCache);
const enableDeviceCode = config.get('enableDeviceCodeLogin');
let accountProvider: azdata.AccountProvider;
if (enableDeviceCode === undefined || enableDeviceCode === false) {
accountProvider = new OAuthAzureAccountProvider(provider.metadata as AzureAccountProviderMetadata, tokenCache, this._context);
} else {
accountProvider = new DeviceCodeAzureAccountProvider(provider.metadata as AzureAccountProviderMetadata, tokenCache);
}
self._accountProviders[provider.metadata.id] = accountProvider;
self._accountDisposals[provider.metadata.id] = azdata.accounts.registerAccountProvider(provider.metadata, accountProvider);
resolve();

View File

@@ -71,13 +71,26 @@ export function registerAzureResourceCommands(appContext: AppContext, tree: Azur
};
}).sort((a, b) => a.label.localeCompare(b.label));
const selectedSubscriptionQuickPickItems = await window.showQuickPick(subscriptionQuickPickItems, { canPickMany: true });
if (selectedSubscriptionQuickPickItems && selectedSubscriptionQuickPickItems.length > 0) {
await tree.refresh(node, false);
const selectedQuickPickItems = subscriptionQuickPickItems.filter(s => s.picked);
selectedSubscriptions = selectedSubscriptionQuickPickItems.map((subscriptionItem) => subscriptionItem.subscription);
await subscriptionFilterService.saveSelectedSubscriptions(accountNode.account, selectedSubscriptions);
}
const quickPick = window.createQuickPick<AzureResourceSubscriptionQuickPickItem>();
quickPick.ok = true;
quickPick.items = subscriptionQuickPickItems;
quickPick.canSelectMany = true;
quickPick.selectedItems = selectedQuickPickItems;
quickPick.show();
quickPick.onDidAccept(async event => {
quickPick.hide();
const selectedSubscriptionQuickPickItems = quickPick.selectedItems;
if (selectedSubscriptionQuickPickItems && selectedSubscriptionQuickPickItems.length > 0) {
await tree.refresh(node, false);
selectedSubscriptions = selectedSubscriptionQuickPickItems.map((subscriptionItem) => subscriptionItem.subscription);
await subscriptionFilterService.saveSelectedSubscriptions(accountNode.account, selectedSubscriptions);
}
});
});
appContext.apiWrapper.registerCommand('azure.resource.refreshall', () => tree.notifyNodeChanged(undefined));

View File

@@ -54,19 +54,21 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
filters.push(...selectedSubscriptionsCache[accountId].map((subcription) => `${accountId}/${subcription.id}/${subcription.name}`));
}
const resourceFilterConfig = this._config.inspect<string[]>(AzureResourceSubscriptionFilterService.filterConfigName);
let configTarget = ConfigurationTarget.Global;
if (resourceFilterConfig) {
if (resourceFilterConfig.workspaceFolderValue) {
configTarget = ConfigurationTarget.WorkspaceFolder;
} else if (resourceFilterConfig.workspaceValue) {
configTarget = ConfigurationTarget.Workspace;
} else if (resourceFilterConfig.globalValue) {
configTarget = ConfigurationTarget.Global;
if (this._config) {
const resourceFilterConfig = this._config.inspect<string[]>(AzureResourceSubscriptionFilterService.filterConfigName);
let configTarget = ConfigurationTarget.Global;
if (resourceFilterConfig) {
if (resourceFilterConfig.workspaceFolderValue) {
configTarget = ConfigurationTarget.WorkspaceFolder;
} else if (resourceFilterConfig.workspaceValue) {
configTarget = ConfigurationTarget.Workspace;
} else if (resourceFilterConfig.globalValue) {
configTarget = ConfigurationTarget.Global;
}
}
}
await this._config.update(AzureResourceSubscriptionFilterService.filterConfigName, filters, configTarget);
await this._config.update(AzureResourceSubscriptionFilterService.filterConfigName, filters, configTarget);
}
}
private _config: WorkspaceConfiguration = undefined;

View File

@@ -15,6 +15,10 @@
"onCommand:bigDataClusters.command.mount",
"onCommand:bigDataClusters.command.refreshmount",
"onCommand:bigDataClusters.command.deletemount",
"onCommand:bigDataClusters.command.addController",
"onCommand:bigDataClusters.command.deleteController",
"onCommand:bigDataClusters.command.manageController",
"onCommand:bigDataClusters.command.refreshController",
"onView:sqlBigDataCluster"
],
"repository": {

View File

@@ -32,9 +32,11 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
public readonly onDidChangeTreeData: vscode.Event<TreeNode> = this._onDidChangeTreeData.event;
private root: ControllerRootNode;
private credentialProvider: azdata.CredentialProvider;
private initialized: boolean = false;
constructor(private memento: vscode.Memento) {
this.root = new ControllerRootNode(this);
this.root.addChild(new LoadingControllerNode());
}
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
@@ -42,14 +44,11 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
return element.getChildren();
}
if (this.root.hasChildren) {
return this.root.getChildren();
if (!this.initialized) {
this.loadSavedControllers().catch(err => { vscode.window.showErrorMessage(localize('bdc.controllerTreeDataProvider.error', "Unexpected error loading saved controllers: {0}", err)); });
}
// Kick off loading the saved controllers but then immediately return the loading node so
// the user isn't left with an empty tree while we load the nodes
this.loadSavedControllers().catch(err => { vscode.window.showErrorMessage(localize('bdc.controllerTreeDataProvider.error', "Unexpected error loading saved controllers: {0}", err)); });
return [new LoadingControllerNode()];
return this.root.getChildren();
}
public getTreeItem(element: TreeNode): vscode.TreeItem | Thenable<vscode.TreeItem> {
@@ -89,12 +88,11 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
}
private removeNonControllerNodes(): void {
this.removePlaceholderNodes();
this.removeDefectiveControllerNodes();
this.removePlaceholderNodes(this.root.children);
this.removeDefectiveControllerNodes(this.root.children);
}
private removePlaceholderNodes(): void {
let nodes = this.root.children;
private removePlaceholderNodes(nodes: TreeNode[]): void {
if (nodes.length > 0) {
for (let i = 0; i < nodes.length; ++i) {
if (nodes[i] instanceof AddControllerNode ||
@@ -106,8 +104,7 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
}
}
private removeDefectiveControllerNodes(): void {
let nodes = this.root.children;
private removeDefectiveControllerNodes(nodes: TreeNode[]): void {
if (nodes.length > 0) {
for (let i = 0; i < nodes.length; ++i) {
if (nodes[i] instanceof ControllerNode) {
@@ -121,31 +118,42 @@ export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeN
}
private async loadSavedControllers(): Promise<void> {
this.root.clearChildren();
let controllers: IControllerInfoSlim[] = this.memento.get('controllers');
if (controllers) {
for (const c of controllers) {
let password = undefined;
if (c.rememberPassword) {
password = await this.getPassword(c.url, c.username);
// Optimistically set to true so we don't double-load the tree
this.initialized = true;
try {
let controllers: IControllerInfoSlim[] = this.memento.get('controllers');
let treeNodes: TreeNode[] = [];
if (controllers) {
for (const c of controllers) {
let password = undefined;
if (c.rememberPassword) {
password = await this.getPassword(c.url, c.username);
}
if (!c.auth) {
// Added before we had added authentication
c.auth = 'basic';
}
treeNodes.push(new ControllerNode(
c.url, c.auth, c.username, password, c.rememberPassword,
undefined, this.root, this, undefined
));
}
if (!c.auth) {
// Added before we had added authentication
c.auth = 'basic';
}
this.root.addChild(new ControllerNode(
c.url, c.auth, c.username, password, c.rememberPassword,
undefined, this.root, this, undefined
));
this.removeDefectiveControllerNodes(treeNodes);
}
this.removeDefectiveControllerNodes();
this.root.clearChildren();
if (treeNodes.length === 0) {
this.root.addChild(new AddControllerNode());
} else {
treeNodes.forEach(node => this.root.addChild(node));
}
this.notifyNodeChanged();
} catch (err) {
// Reset so we can try again if the tree refreshes
this.initialized = false;
throw err;
}
if (!this.root.hasChildren) {
this.root.addChild(new AddControllerNode());
}
this.notifyNodeChanged();
}
public async saveControllers(): Promise<void> {

View File

@@ -17,6 +17,7 @@ import { CmsResourceTreeNode } from './cmsResourceTreeNode';
export class CmsResourceTreeProvider implements TreeDataProvider<TreeNode>, ICmsResourceTreeChangeHandler {
private _appContext: AppContext;
private _children: TreeNode[] = [CmsResourceMessageTreeNode.create(CmsResourceTreeProvider.loadingLabel, undefined)];
public constructor(
public readonly appContext: AppContext
@@ -31,16 +32,14 @@ export class CmsResourceTreeProvider implements TreeDataProvider<TreeNode>, ICms
}
if (!this.isSystemInitialized) {
// Kick off loading the saved servers but then immediately return the loading node so
// the user isn't left with an empty tree while we load the nodes
this.loadSavedServers().catch(err => this._appContext.apiWrapper.showErrorMessage(localize('cms.resource.tree.treeProvider.loadError', "Unexpected error occured while loading saved servers {0}", err)));
return [CmsResourceMessageTreeNode.create(CmsResourceTreeProvider.loadingLabel, undefined)];
this.loadSavedServers().catch(err => this._appContext.apiWrapper.showErrorMessage(localize('cms.resource.tree.treeProvider.loadError', "Unexpected error occurred while loading saved servers {0}", err)));
return this._children;
}
try {
let registeredCmsServers = this.appContext.cmsUtils.registeredCmsServers;
if (registeredCmsServers && registeredCmsServers.length > 0) {
this.isSystemInitialized = true;
return registeredCmsServers.map((server) => {
this._children = registeredCmsServers.map((server) => {
return new CmsResourceTreeNode(
server.name,
server.description,
@@ -49,11 +48,12 @@ export class CmsResourceTreeProvider implements TreeDataProvider<TreeNode>, ICms
this._appContext, this, null);
}).sort((a, b) => a.name.localeCompare(b.name));
} else {
return [new CmsResourceEmptyTreeNode()];
this._children = [new CmsResourceEmptyTreeNode()];
}
} catch (error) {
return [new CmsResourceEmptyTreeNode()];
this._children = [new CmsResourceEmptyTreeNode()];
}
return this._children;
}
public get onDidChangeTreeData(): Event<TreeNode> {
@@ -93,6 +93,10 @@ export class CmsResourceTreeProvider implements TreeDataProvider<TreeNode>, ICms
await this.appContext.cmsUtils.cacheRegisteredCmsServer(server.name, server.description,
server.ownerUri, server.connection);
}
this._children = servers;
} else {
// No saved servers so just show the Add Server node since we're done loading
this._children = [new CmsResourceEmptyTreeNode()];
}
this._onDidChangeTreeData.fire(undefined);
} catch (error) {

View File

@@ -13,6 +13,7 @@ import { CmsResourceTreeProvider } from '../../../cmsResource/tree/treeProvider'
import { CmsResourceMessageTreeNode } from '../../../cmsResource/messageTreeNode';
import { CmsResourceEmptyTreeNode } from '../../../cmsResource/tree/cmsResourceEmptyTreeNode';
import { CmsUtils } from '../../../cmsUtils';
import { sleep } from '../../utils';
// Mock services
let mockAppContext: AppContext;
@@ -29,22 +30,42 @@ describe('CmsResourceTreeProvider.getChildren', function (): void {
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object, mockCmsUtils.object);
});
it('Should not be initialized.', async function (): Promise<void> {
it('Should be loading while waiting for saved servers to load', async function (): Promise<void> {
const treeProvider = new CmsResourceTreeProvider(mockAppContext);
should.notEqual(treeProvider.isSystemInitialized, true);
// We need to return at least one node so the async loading part is hit
mockCmsUtils.setup(x => x.getSavedServers).returns(() => {
return () => [{name: 'name',
description: 'desc',
ownerUri: 'uri',
connection: undefined}];
});
// Set up so loading the servers doesn't return immediately - thus we'll still have the Loading node
mockCmsUtils.setup(x => x.cacheRegisteredCmsServer).returns(() => {
return async () => { await sleep(600000); return undefined; };
});
should.notEqual(treeProvider.isSystemInitialized, true, 'Expected isSystemInitialized not to be true');
const children = await treeProvider.getChildren(undefined);
should.equal(children.length, 1);
should.equal(children[0].parent, undefined);
should.equal(children[0] instanceof CmsResourceMessageTreeNode, true);
should.equal(children.length, 1, 'Expected exactly one child node');
should.equal(children[0].parent, undefined, 'Expected node to not have a parent');
should.equal(children[0] instanceof CmsResourceMessageTreeNode, true, 'Expected node to be a CmsResourceMessageTreeNode');
});
it('Should be empty resource node when no servers to load', async function (): Promise<void> {
const treeProvider = new CmsResourceTreeProvider(mockAppContext);
should.notEqual(treeProvider.isSystemInitialized, true, 'Expected isSystemInitialized not to be true');
const children = await treeProvider.getChildren(undefined);
should.equal(children.length, 1, 'Expected exactly one child node');
should.equal(children[0].parent, undefined, 'Expected node to not have a parent');
should.equal(children[0] instanceof CmsResourceEmptyTreeNode, true, 'Expected node to be a CmsResourceEmptyTreeNode');
});
it('Should not be loading after initialized.', async function (): Promise<void> {
const treeProvider = new CmsResourceTreeProvider(mockAppContext);
treeProvider.isSystemInitialized = true;
should.equal(true, treeProvider.isSystemInitialized);
should.equal(true, treeProvider.isSystemInitialized, 'Expected isSystemInitialized to be true');
mockCmsUtils.setup(x => x.registeredCmsServers).returns(() => []);
const children = await treeProvider.getChildren(undefined);
should.equal(children[0] instanceof CmsResourceEmptyTreeNode, true);
should.equal(children[0] instanceof CmsResourceEmptyTreeNode, true, 'Expected child node to be CmsResourceEmptyTreeNode');
});
it('Should show CMS nodes if there are cached servers', async function (): Promise<void> {
@@ -59,6 +80,6 @@ describe('CmsResourceTreeProvider.getChildren', function (): void {
}];
});
const children = await treeProvider.getChildren(undefined);
should.equal(children[0] !== null, true);
should.exist(children[0], 'Child node did not exist');
});
});

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export async function sleep(ms: number): Promise<{}> {
return new Promise(resolve => setTimeout(resolve, ms));
}

View File

@@ -67,7 +67,8 @@ export class BookTreeItem extends vscode.TreeItem {
private setCommand() {
if (this.book.type === BookTreeItemType.Notebook) {
let pathToNotebook = path.join(this.book.root, 'content', this._uri.concat('.ipynb'));
// The Notebook editor expects a posix path for the resource (it will still resolve to the correct fsPath based on OS)
const pathToNotebook = path.posix.join(this.book.root, 'content', this._uri.concat('.ipynb'));
this.command = { command: this.book.isUntitled ? 'bookTreeView.openUntitledNotebook' : 'bookTreeView.openNotebook', title: loc.openNotebookCommand, arguments: [pathToNotebook], };
} else if (this.book.type === BookTreeItemType.Markdown) {
let pathToMarkdown = path.join(this.book.root, 'content', this._uri.concat('.md'));
@@ -81,8 +82,8 @@ export class BookTreeItem extends vscode.TreeItem {
let i = --index;
while (i > -1) {
if (this.book.tableOfContents.sections[i].url) {
// TODO: Currently only navigating to notebooks. Need to add logic for markdown.
let pathToNotebook = path.join(this.book.root, 'content', this.book.tableOfContents.sections[i].url.concat('.ipynb'));
// The Notebook editor expects a posix path for the resource (it will still resolve to the correct fsPath based on OS)
let pathToNotebook = path.posix.join(this.book.root, 'content', this.book.tableOfContents.sections[i].url.concat('.ipynb'));
// eslint-disable-next-line no-sync
if (fs.existsSync(pathToNotebook)) {
this._previousUri = pathToNotebook;
@@ -97,8 +98,8 @@ export class BookTreeItem extends vscode.TreeItem {
let i = ++index;
while (i < this.book.tableOfContents.sections.length) {
if (this.book.tableOfContents.sections[i].url) {
// TODO: Currently only navigating to notebooks. Need to add logic for markdown.
let pathToNotebook = path.join(this.book.root, 'content', this.book.tableOfContents.sections[i].url.concat('.ipynb'));
// The Notebook editor expects a posix path for the resource (it will still resolve to the correct fsPath based on OS)
let pathToNotebook = path.posix.join(this.book.root, 'content', this.book.tableOfContents.sections[i].url.concat('.ipynb'));
// eslint-disable-next-line no-sync
if (fs.existsSync(pathToNotebook)) {
this._nextUri = pathToNotebook;

View File

@@ -10,7 +10,6 @@ import * as fs from 'fs-extra';
import { IPrompter, QuestionTypes, IQuestion } from '../prompts/question';
import CodeAdapter from '../prompts/adapter';
import { BookTreeItem } from './bookTreeItem';
import { isEditorTitleFree } from '../common/utils';
import { BookModel } from './bookModel';
import { Deferred } from '../common/promise';
import * as loc from '../common/localizedConstants';
@@ -98,7 +97,8 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
const sectionToOpen = bookRoot.findChildSection(urlToOpen);
const urlPath = sectionToOpen ? sectionToOpen.url : bookRoot.tableOfContents.sections[0].url;
const sectionToOpenMarkdown: string = path.join(this.currentBook.bookPath, 'content', urlPath.concat('.md'));
const sectionToOpenNotebook: string = path.join(this.currentBook.bookPath, 'content', urlPath.concat('.ipynb'));
// The Notebook editor expects a posix path for the resource (it will still resolve to the correct fsPath based on OS)
const sectionToOpenNotebook: string = path.posix.join(this.currentBook.bookPath, 'content', urlPath.concat('.ipynb'));
if (await fs.pathExists(sectionToOpenMarkdown)) {
this.openMarkdown(sectionToOpenMarkdown);
}
@@ -297,9 +297,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
}
getUntitledNotebookUri(resource: string): vscode.Uri {
let untitledFileName: vscode.Uri;
let nextTitle: string = this.findNextUntitledFileName(resource);
untitledFileName = vscode.Uri.parse(`untitled:${nextTitle}`);
let untitledFileName = vscode.Uri.parse(`untitled:${resource}`);
if (!this.currentBook.getAllBooks().get(untitledFileName.fsPath) && !this.currentBook.getAllBooks().get(path.basename(untitledFileName.fsPath))) {
let notebook = this.currentBook.getAllBooks().get(resource);
this.currentBook.getAllBooks().set(path.basename(untitledFileName.fsPath), notebook);
@@ -307,18 +305,6 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
return untitledFileName;
}
findNextUntitledFileName(filePath: string): string {
const baseName = path.basename(filePath);
let idx = 0;
let title;
do {
const suffix = idx === 0 ? '' : `-${idx}`;
title = `${baseName}${suffix}`;
idx++;
} while (!isEditorTitleFree(title));
return title;
}
//Confirmation message dialog
private async confirmReplace(): Promise<boolean> {

View File

@@ -11,7 +11,7 @@
},
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
"icon": "images/sqlserver.png",
"aiKey": "AIF-c5594e2d-38b5-4d3b-ab1b-ed5d4fe8ee40",
"aiKey": "AIF-37eefaf0-8022-4671-a3fb-64752724682e",
"activationEvents": [
"onCommand:sqlDatabaseProjects.new",
"onCommand:sqlDatabaseProjects.open",

View File

@@ -1,7 +1,7 @@
{
"name": "azuredatastudio",
"version": "1.15.0",
"distro": "b71b4af5016533d971958cc37e25b43b9c3754a5",
"version": "1.15.1",
"distro": "922d292f69c5d36ab6f0dbbd94a63c81219afaaf",
"author": {
"name": "Microsoft Corporation"
},
@@ -88,7 +88,7 @@
"devDependencies": {
"7zip": "0.0.6",
"@types/applicationinsights": "0.20.0",
"@types/chart.js": "^2.7.31",
"@types/chart.js": "2.7.57",
"@types/chokidar": "2.1.3",
"@types/cookie": "^0.3.3",
"@types/graceful-fs": "4.1.2",

View File

@@ -111,6 +111,7 @@ exports.load = function (modulePaths, resultCallback, options) {
'@angular/platform-browser-dynamic',
'@angular/router',
'rxjs/Observable',
'rxjs/add/observable/fromPromise',
'rxjs/Subject',
'rxjs/Observer',
'slickgrid/lib/jquery.event.drag-2.3.0',

View File

@@ -19,8 +19,8 @@ export interface IConnectionProfileGroup {
export class ConnectionProfileGroup extends Disposable implements IConnectionProfileGroup {
public children: ConnectionProfileGroup[] = [];
public connections: ConnectionProfile[] = [];
private _childGroups: ConnectionProfileGroup[] = [];
private _childConnections: ConnectionProfile[] = [];
public parentId?: string;
private _isRenamed = false;
public constructor(
@@ -42,9 +42,9 @@ export class ConnectionProfileGroup extends Disposable implements IConnectionPro
public toObject(): IConnectionProfileGroup {
let subgroups = undefined;
if (this.children.length > 0) {
if (this._childGroups.length > 0) {
subgroups = [];
this.children.forEach((group) => {
this._childGroups.forEach((group) => {
subgroups.push(group.toObject());
});
}
@@ -75,8 +75,24 @@ export class ConnectionProfileGroup extends Disposable implements IConnectionPro
this._isRenamed = val;
}
public get children(): ConnectionProfileGroup[] | undefined {
return this._childGroups;
}
public set children(children: ConnectionProfileGroup[] | undefined) {
this._childGroups = children ?? [];
}
public get connections(): ConnectionProfile[] | undefined {
return this._childConnections;
}
public set connections(connections: ConnectionProfile[] | undefined) {
this._childConnections = connections ?? [];
}
public hasChildren(): boolean {
if (this.children.length > 0 || this.connections.length > 0) {
if (this._childGroups.length > 0 || this._childConnections.length > 0) {
return true;
}
return false;
@@ -86,12 +102,12 @@ export class ConnectionProfileGroup extends Disposable implements IConnectionPro
* Returns true if all connections in the tree have valid options using the correct capabilities
*/
public get hasValidConnections(): boolean {
let invalidConnections = find(this.connections, c => !c.isConnectionOptionsValid);
let invalidConnections = find(this._childConnections, c => !c.isConnectionOptionsValid);
if (invalidConnections !== undefined) {
return false;
} else {
let childrenAreValid: boolean = true;
this.children.forEach(element => {
this._childGroups.forEach(element => {
let isChildValid = element.hasValidConnections;
if (!isChildValid) {
childrenAreValid = false;
@@ -103,11 +119,11 @@ export class ConnectionProfileGroup extends Disposable implements IConnectionPro
public getChildren(): (ConnectionProfile | ConnectionProfileGroup)[] {
let allChildren: (ConnectionProfile | ConnectionProfileGroup)[] = [];
this.connections.forEach((conn) => {
this._childConnections.forEach((conn) => {
allChildren.push(conn);
});
this.children.forEach((group) => {
this._childGroups.forEach((group) => {
allChildren.push(group);
});
return allChildren;
@@ -120,22 +136,22 @@ export class ConnectionProfileGroup extends Disposable implements IConnectionPro
return other.id === this.id;
}
public addConnections(connections: ConnectionProfile[]): void {
connections.forEach((conn) => {
this.connections = this.connections.filter((curConn) => { return curConn.id !== conn.id; });
public addConnections(connections: ConnectionProfile[] | undefined): void {
connections?.forEach((conn) => {
this._childConnections = this._childConnections.filter((curConn) => { return curConn.id !== conn.id; });
conn.parent = this;
this._register(conn);
this.connections.push(conn);
this._childConnections.push(conn);
});
}
public addGroups(groups: ConnectionProfileGroup[]): void {
groups.forEach((group) => {
this.children = this.children.filter((grp) => { return group.id !== grp.id; });
public addGroups(groups: ConnectionProfileGroup[] | undefined): void {
groups?.forEach((group) => {
this._childGroups = this._childGroups.filter((grp) => { return group.id !== grp.id; });
group.parent = this;
this._register(group);
this.children.push(group);
this._childGroups.push(group);
});
}

View File

@@ -117,14 +117,14 @@ export function generateUriWithPrefix(connection: IConnectionProfile, prefix: st
export function findProfileInGroup(og: IConnectionProfile, groups: ConnectionProfileGroup[]): ConnectionProfile | undefined {
for (let group of groups) {
for (let conn of group.connections) {
for (let conn of group.connections!) {
if (conn.id === og.id) {
return conn;
}
}
if (group.hasChildren()) {
let potentialReturn = findProfileInGroup(og, group.children);
let potentialReturn = findProfileInGroup(og, group.children!);
if (potentialReturn) {
return potentialReturn;
}

View File

@@ -5,6 +5,8 @@
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
import * as assert from 'assert';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService';
suite('SQL ConnectionProfileGroup tests', () => {
let root: ConnectionProfileGroup;
@@ -14,12 +16,17 @@ suite('SQL ConnectionProfileGroup tests', () => {
let group1Node: ConnectionProfileGroup;
let group11Node: ConnectionProfileGroup;
let group2Node: ConnectionProfileGroup;
let emptyGroup: ConnectionProfileGroup;
let connectionProfile: ConnectionProfile;
setup(() => {
root = new ConnectionProfileGroup(ConnectionProfileGroup.RootGroupName, undefined, ConnectionProfileGroup.RootGroupName, undefined, undefined);
group1Node = new ConnectionProfileGroup(Groups1, root, Groups1, undefined, undefined);
group2Node = new ConnectionProfileGroup(Groups2, root, Groups2, undefined, undefined);
group11Node = new ConnectionProfileGroup(Groups11, root, Groups11, undefined, undefined);
emptyGroup = new ConnectionProfileGroup(ConnectionProfileGroup.RootGroupName, undefined, ConnectionProfileGroup.RootGroupName);
connectionProfile = new ConnectionProfile(new TestCapabilitiesService(), undefined!);
root.addGroups([group1Node]);
group1Node.addGroups([group11Node]);
root.addGroups([group2Node]);
@@ -142,4 +149,66 @@ suite('SQL ConnectionProfileGroup tests', () => {
let actual = ConnectionProfileGroup.sameGroupName(name1, name2);
assert.deepEqual(actual, expected);
});
test('test behavior when children is set to undefined', () => {
emptyGroup.children = undefined;
assert(emptyGroup.hasChildren() === false, 'Group should report no children after setting children to undefined');
const obj = emptyGroup.toObject();
assert.equal(obj.id, emptyGroup.id, 'toObject result has wrong id');
assert.equal(obj.name, emptyGroup.name, 'toObject result has wrong name');
assert(emptyGroup.hasValidConnections === true, 'Expected group to have valid connections');
const children = emptyGroup.getChildren();
assert(children.length === 0, 'Expected group to have 0 children');
});
test('test behavior when connections is set to undefined', () => {
emptyGroup.connections = undefined;
assert(emptyGroup.hasChildren() === false, 'Group should report no children after setting connections to undefined');
const obj = emptyGroup.toObject();
assert.equal(obj.id, emptyGroup.id, 'toObject result has wrong id');
assert.equal(obj.name, emptyGroup.name, 'toObject result has wrong name');
assert(emptyGroup.hasValidConnections === true, 'Expected group to have valid connections');
const children = emptyGroup.getChildren();
assert.equal(children.length, 0, 'Expected group to have 0 children');
});
test('test behavior with 1 child group', () => {
emptyGroup.addGroups([group1Node]);
assert(emptyGroup.hasChildren() === true, 'Group should have children if 1 child group is added');
assert(emptyGroup.hasValidConnections === true, 'Expected group to have valid connections');
const children = emptyGroup.getChildren();
assert.equal(children.length, 1, 'Expected group to have 1 child');
assert.equal(children[0].id, group1Node.id, 'Expected group child to be group1Node');
});
test('test behavior with 1 child connection', () => {
emptyGroup.addConnections([connectionProfile]);
assert(emptyGroup.hasChildren(), 'Group should have children if 1 child group is added');
assert(emptyGroup.hasValidConnections === false, 'Expected group not to have valid connections');
const children = emptyGroup.getChildren();
assert.equal(children.length, 1, 'Expected group to have 1 child');
assert.equal(children[0].id, connectionProfile.id, 'Expected group child to be connectionProfile');
});
test('adding undefined groups does nothing', () => {
emptyGroup.addGroups(undefined);
assert(emptyGroup.hasChildren() === false, 'Expected group not to have any children');
// Verify adding undefined doesn't modify existing children
emptyGroup.addGroups([group1Node]);
emptyGroup.addGroups(undefined);
const children = emptyGroup.getChildren();
assert.equal(children.length, 1, 'Expected group to have 1 child still');
assert.equal(children[0].id, group1Node.id, 'Expected group child to be group1Node');
});
test('adding undefined connections does nothing', () => {
emptyGroup.addConnections(undefined);
assert(emptyGroup.hasChildren() === false, 'Expected group not to have any children');
// Verify adding undefined doesn't modify existing children
emptyGroup.addConnections([connectionProfile]);
emptyGroup.addConnections(undefined);
const children = emptyGroup.getChildren();
assert.equal(children.length, 1, 'Expected group to have 1 child still');
assert.equal(children[0].id, connectionProfile.id, 'Expected group child to be connectionProfile');
});
});

View File

@@ -458,8 +458,13 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
};
let isUntitled: boolean = uri.scheme === Schemas.untitled;
const fileInput = isUntitled ? this._untitledEditorService.create({ untitledResource: uri, mode: 'notebook', initialValue: options.initialContent }) :
this._editorService.createInput({ resource: uri, mode: 'notebook' });
let fileInput;
if (isUntitled && path.isAbsolute(uri.fsPath)) {
fileInput = this._untitledEditorService.create({ associatedResource: uri, mode: 'notebook', initialValue: options.initialContent });
} else {
fileInput = isUntitled ? this._untitledEditorService.create({ untitledResource: uri, mode: 'notebook', initialValue: options.initialContent }) :
this._editorService.createInput({ resource: uri, mode: 'notebook' });
}
let input: NotebookInput;
if (isUntitled) {
input = this._instantiationService.createInstance(UntitledNotebookInput, path.basename(uri.fsPath), uri, fileInput as UntitledTextEditorInput);
@@ -720,7 +725,7 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
let result = await this._proxy.$getNavigation(handle, uri);
if (result) {
if (result.next.scheme === Schemas.untitled) {
let untitledNbName: URI = URI.parse(`untitled:${path.basename(result.next.path)}`);
let untitledNbName: URI = URI.parse(`untitled:${result.next.path}`);
let content = await this._fileService.readFile(URI.file(result.next.path));
await this.doOpenEditor(untitledNbName, { initialContent: content.value.toString(), initialDirtyState: false });
}
@@ -733,7 +738,7 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
let result = await this._proxy.$getNavigation(handle, uri);
if (result) {
if (result.previous.scheme === Schemas.untitled) {
let untitledNbName: URI = URI.parse(`untitled:${path.basename(result.previous.path)}`);
let untitledNbName: URI = URI.parse(`untitled:${result.previous.path}`);
let content = await this._fileService.readFile(URI.file(result.previous.path));
await this.doOpenEditor(untitledNbName, { initialContent: content.value.toString(), initialDirtyState: false });
}

View File

@@ -162,8 +162,8 @@ export class Graph implements IInsight {
this.chartjs.config.type = this.options.type;
// we don't want to include lables for timeSeries
this.chartjs.data.labels = this.originalType === 'timeSeries' ? [] : labels;
this.chartjs.config.options = this.transformOptions(this.options);
this.chartjs.update(0);
this.chartjs.options = this.transformOptions(this.options);
this.chartjs.update({ duration: 0 });
} else {
this.chartjs = new chartjs.Chart(this.canvas.getContext('2d'), {
data: {

View File

@@ -8,6 +8,7 @@ import {
ComponentFactoryResolver, ViewChild, ChangeDetectorRef, Injector
} from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromPromise';
import { DashboardWidget, IDashboardWidget, WIDGET_CONFIG, WidgetConfig } from 'sql/workbench/contrib/dashboard/browser/core/dashboardWidget';
import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service';

View File

@@ -33,6 +33,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { deepClone, assign } from 'vs/base/common/objects';
import { Emitter, Event } from 'vs/base/common/event';
import { equals } from 'vs/base/common/arrays';
import * as DOM from 'vs/base/browser/dom';
export class EditDataGridPanel extends GridParentComponent {
// The time(in milliseconds) we wait before refreshing the grid.
@@ -110,6 +111,7 @@ export class EditDataGridPanel extends GridParentComponent {
onInit(): void {
const self = this;
this.baseInit();
this._register(DOM.addDisposableListener(this.nativeElement, DOM.EventType.KEY_DOWN, e => this.tryHandleKeyEvent(new StandardKeyboardEvent(e))));
// Add the subscription to the list of things to be disposed on destroy, or else on a new component init
// may get the "destroyed" object still getting called back.
@@ -392,11 +394,13 @@ export class EditDataGridPanel extends GridParentComponent {
undefinedDataSet.dataRows = undefined;
undefinedDataSet.resized = new Emitter();
self.placeHolderDataSets.push(undefinedDataSet);
if (self.placeHolderDataSets[0]) {
this.refreshDatasets();
}
self.refreshGrid();
// Setup the state of the selected cell
this.resetCurrentCell();
this.currentEditCellValue = undefined;
this.removingNewRow = false;
this.newRowVisible = false;
this.dirtyCells = [];
@@ -522,7 +526,6 @@ export class EditDataGridPanel extends GridParentComponent {
// so clear any existing client-side edit and refresh on-screen data
// do not refresh the whole dataset as it will move the focus away to the first row.
//
this.currentEditCellValue = undefined;
this.dirtyCells = [];
let row = this.currentCell.row;
this.resetCurrentCell();
@@ -761,6 +764,7 @@ export class EditDataGridPanel extends GridParentComponent {
isEditable: false,
isDirty: false
};
this.currentEditCellValue = undefined;
}
private setCurrentCell(row: number, column: number) {
@@ -1066,7 +1070,7 @@ export class EditDataGridPanel extends GridParentComponent {
cellClasses += ' missing-value';
}
else if (Services.DBCellValue.isDBCellValue(value)) {
valueToDisplay = (value.displayValue + '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
valueToDisplay = (value.displayValue + '');
valueToDisplay = escape(valueToDisplay.length > 250 ? valueToDisplay.slice(0, 250) + '...' : valueToDisplay);
}
else if (typeof value === 'string' || (value && value.text)) {

View File

@@ -6,6 +6,7 @@
/* Node Modules */
import { Injectable, Inject } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromPromise';
/* SQL imports */
import { IDefaultComponentParams, IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams';

View File

@@ -52,7 +52,7 @@ export class QueryEditorService implements IQueryEditorService {
let docUri: URI = URI.from({ scheme: Schemas.untitled, path: filePath });
// Create a sql document pane with accoutrements
const fileInput = this._untitledEditorService.create({ associatedResource: docUri, mode: 'sql' });
const fileInput = this._untitledEditorService.create({ untitledResource: docUri, mode: 'sql' });
let untitledEditorModel = await fileInput.resolve();
if (sqlContent) {
untitledEditorModel.textEditorModel.setValue(sqlContent);
@@ -89,6 +89,8 @@ export class QueryEditorService implements IQueryEditorService {
// Create a sql document pane with accoutrements
const fileInput = this._untitledEditorService.create({ untitledResource: docUri, mode: 'sql' });
const m = await fileInput.resolve();
//when associatedResource editor is created it is dirty, this must be set to false to be able to detect changes to the editor.
m.setDirty(false);
// Create an EditDataInput for editing
const resultsInput: EditDataResultsInput = this._instantiationService.createInstance(EditDataResultsInput, docUri.toString());
let editDataInput: EditDataInput = this._instantiationService.createInstance(EditDataInput, docUri, schemaName, tableName, fileInput, sqlContent, resultsInput);

View File

@@ -78,7 +78,7 @@ interface ISharedProcessInitData {
logLevel: LogLevel;
}
const eventPrefix = product.quality !== 'stable' ? 'adsworkbench' : 'monacoworkbench'; // {{ SQL CARBON EDIT }}
const eventPrefix = 'adsworkbench'; // {{ SQL CARBON EDIT }}
class MainProcessService implements IMainProcessService {
constructor(private server: Server, private mainRouter: StaticRouter) { }

View File

@@ -292,7 +292,7 @@ export class Main {
}
}
const eventPrefix = product.quality !== 'stable' ? 'adsworkbench' : 'monacoworkbench'; // {{SQL CARBON EDIT}}
const eventPrefix = 'adsworkbench'; // {{SQL CARBON EDIT}}
export async function main(argv: ParsedArgs): Promise<void> {
const services = new ServiceCollection();

View File

@@ -20,24 +20,12 @@ export async function resolveCommonProperties(
product?: string
): Promise<{ [name: string]: string | boolean | undefined; }> {
const result: { [name: string]: string | boolean | undefined; } = Object.create(null);
// {{SQL CARBON EDIT}} start
if (productObject.quality !== 'stable') {
// __GDPR__COMMON__ "common.machineId" : { "endPoint": "MacAddressHash", "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" }
result['common.machineId'] = machineId;
// __GDPR__COMMON__ "sessionID" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['sessionID'] = uuid.generateUuid() + Date.now();
// __GDPR__COMMON__ "commitHash" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
result['commitHash'] = commit;
} else {
// __GDPR__COMMON__ "common.machineId" : { "endPoint": "MacAddressHash", "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" }
// result['common.machineId'] = machineId;
result['common.machineId'] = '';
// // __GDPR__COMMON__ "sessionID" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
// result['sessionID'] = uuid.generateUuid() + Date.now();
result['sessionID'] = '';
// __GDPR__COMMON__ "commitHash" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['commitHash'] = '';
} // {{SQL CARBON EDIT}} end
// __GDPR__COMMON__ "common.machineId" : { "endPoint": "MacAddressHash", "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" }
result['common.machineId'] = machineId;
// __GDPR__COMMON__ "sessionID" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['sessionID'] = uuid.generateUuid() + Date.now();
// __GDPR__COMMON__ "commitHash" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
result['commitHash'] = commit;
// __GDPR__COMMON__ "version" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['version'] = version;
// __GDPR__COMMON__ "common.platformVersion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }

View File

@@ -24,39 +24,20 @@ export async function resolveWorkbenchCommonProperties(
const firstSessionDate = storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL)!;
const lastSessionDate = storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL)!;
if (product.quality !== 'stable') {
// __GDPR__COMMON__ "common.version.shell" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
result['common.version.shell'] = process.versions && process.versions['electron'];
// __GDPR__COMMON__ "common.version.renderer" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
result['common.version.renderer'] = process.versions && process.versions['chrome'];
// __GDPR__COMMON__ "common.firstSessionDate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.firstSessionDate'] = firstSessionDate;
// __GDPR__COMMON__ "common.lastSessionDate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.lastSessionDate'] = lastSessionDate || '';
// __GDPR__COMMON__ "common.isNewSession" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.isNewSession'] = !lastSessionDate ? '1' : '0';
// __GDPR__COMMON__ "common.instanceId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.instanceId'] = instanceId;
// __GDPR__COMMON__ "common.remoteAuthority" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
result['common.remoteAuthority'] = cleanRemoteAuthority(remoteAuthority);
} else {
// __GDPR__COMMON__ "common.version.shell" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
// result['common.version.shell'] = process.versions && process.versions['electron'];
// __GDPR__COMMON__ "common.version.renderer" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
// result['common.version.renderer'] = process.versions && process.versions['chrome'];
// __GDPR__COMMON__ "common.firstSessionDate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
// result['common.firstSessionDate'] = firstSessionDate;
// __GDPR__COMMON__ "common.lastSessionDate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
// result['common.lastSessionDate'] = lastSessionDate || '';
// __GDPR__COMMON__ "common.isNewSession" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
// result['common.isNewSession'] = !lastSessionDate ? '1' : '0';
// __GDPR__COMMON__ "common.instanceId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
// result['common.instanceId'] = instanceId;
// __GDPR__COMMON__ "common.remoteAuthority" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
// result['common.remoteAuthority'] = cleanRemoteAuthority(remoteAuthority);
result['common.userId'] = ''; // {{SQL CARBON EDIT}}
}
// __GDPR__COMMON__ "common.version.shell" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
result['common.version.shell'] = process.versions && process.versions['electron'];
// __GDPR__COMMON__ "common.version.renderer" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
result['common.version.renderer'] = process.versions && process.versions['chrome'];
// __GDPR__COMMON__ "common.firstSessionDate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.firstSessionDate'] = firstSessionDate;
// __GDPR__COMMON__ "common.lastSessionDate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.lastSessionDate'] = lastSessionDate || '';
// __GDPR__COMMON__ "common.isNewSession" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.isNewSession'] = !lastSessionDate ? '1' : '0';
// __GDPR__COMMON__ "common.instanceId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
result['common.instanceId'] = instanceId;
// __GDPR__COMMON__ "common.remoteAuthority" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
result['common.remoteAuthority'] = cleanRemoteAuthority(remoteAuthority);
result['common.application.name'] = product.nameLong; // {{SQL CARBON EDIT}}
setUsageDates(storageService);

3
src/vs/vscode.d.ts vendored
View File

@@ -7940,6 +7940,9 @@ declare module 'vscode' {
* An event signaling when the selected items have changed.
*/
readonly onDidChangeSelection: Event<T[]>;
// {SQL CARBON EDIT} Temporary change to allow setting the behavior of the QuickPick
ok: boolean;
}
/**

View File

@@ -261,9 +261,22 @@ class ExtHostQuickInput implements QuickInput {
this._onDidChangeValueEmitter
];
constructor(protected _proxy: MainThreadQuickOpenShape, protected _extensionId: ExtensionIdentifier, private _onDidDispose: () => void) {
}
// {SQL CARBON EDIT} START Temporary change for ok button visibility
private _ok = false;
get ok() {
return this._ok;
}
set ok(ok: boolean) {
this._ok = ok;
this.update({ ok });
}
// {SQL CARBON EDIT} END Temporary change for ok button visibility
get title() {
return this._title;
}

View File

@@ -510,15 +510,15 @@ class ResourceLabelWidget extends IconLabel {
}
let label = this.label.name || '';
if (resource?.scheme === Schemas.untitled) {
// Untitled labels are very dynamic because they may change
// whenever the content changes. As such we always ask the
// text file service for the name of the untitled editor
const untitledName = this.textFileService.untitled.get(resource)?.getName();
if (untitledName) {
label = untitledName;
}
}
// if (resource?.scheme === Schemas.untitled) { // {{SQL CARBON EDIT}} - For Query Editor, untitled editor query must use same label.
// // Untitled labels are very dynamic because they may change
// // whenever the content changes. As such we always ask the
// // text file service for the name of the untitled editor
// const untitledName = this.textFileService.untitled.get(resource)?.getName();
// if (untitledName) {
// label = untitledName;
// }
// }
this.setLabel(label, this.label.description, iconLabelOptions);

View File

@@ -426,10 +426,8 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoRows
registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoColumnsBottomAction.ID, EditorLayoutTwoColumnsBottomAction.LABEL), 'View: Two Columns Bottom Editor Layout', category);
// Register Quick Editor Actions including built in quick navigate support for some
const quickOpenNextRecentlyUsedEditorInGroupKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } };
const quickOpenPreviousRecentlyUsedEditorInGroupKeybinding = { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } };
registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousRecentlyUsedEditorAction, QuickOpenPreviousRecentlyUsedEditorAction.ID, QuickOpenPreviousRecentlyUsedEditorAction.LABEL), 'View: Quick Open Previous Recently Used Editor', category);
registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousRecentlyUsedEditorInGroupAction, QuickOpenPreviousRecentlyUsedEditorInGroupAction.ID, QuickOpenPreviousRecentlyUsedEditorInGroupAction.LABEL, quickOpenPreviousRecentlyUsedEditorInGroupKeybinding), 'View: Quick Open Previous Recently Used Editor in Group', category);
registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousRecentlyUsedEditorInGroupAction, QuickOpenPreviousRecentlyUsedEditorInGroupAction.ID, QuickOpenPreviousRecentlyUsedEditorInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }), 'View: Quick Open Previous Recently Used Editor in Group', category);
registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousEditorFromHistoryAction, QuickOpenPreviousEditorFromHistoryAction.ID, QuickOpenPreviousEditorFromHistoryAction.LABEL), 'Quick Open Previous Editor from History');
const quickOpenNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker';
@@ -438,8 +436,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
weight: KeybindingWeight.WorkbenchContrib + 50,
handler: getQuickNavigateHandler(quickOpenNavigateNextInEditorPickerId, true),
when: editorPickerContext,
primary: quickOpenNextRecentlyUsedEditorInGroupKeybinding.primary,
mac: quickOpenNextRecentlyUsedEditorInGroupKeybinding.mac
primary: KeyMod.CtrlCmd | KeyCode.Tab,
mac: { primary: KeyMod.WinCtrl | KeyCode.Tab }
});
const quickOpenNavigatePreviousInEditorPickerId = 'workbench.action.quickOpenNavigatePreviousInEditorPicker';
@@ -448,8 +446,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
weight: KeybindingWeight.WorkbenchContrib + 50,
handler: getQuickNavigateHandler(quickOpenNavigatePreviousInEditorPickerId, false),
when: editorPickerContext,
primary: quickOpenPreviousRecentlyUsedEditorInGroupKeybinding.primary,
mac: quickOpenPreviousRecentlyUsedEditorInGroupKeybinding.mac
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab,
mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab }
});
// Editor Commands

View File

@@ -242,9 +242,9 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt
}
private updateNameFromFirstLine(): void {
if (this.hasAssociatedFilePath) {
return; // not in case of an associated file path
}
// if (this.hasAssociatedFilePath) { {{SQL CARBON EDIT}} - For Query Editor, must not change name even without file path.
return;
// }
// Determine the first words of the model following these rules:
// - cannot be only whitespace (so we trim())

View File

@@ -572,7 +572,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
}
// Finally fallback to suggest just the file name
return toLocalResource(resource.with({ path: suggestedFilename }), remoteAuthority);
// {{SQL CARBON EDIT}} - Rationale: this seems to be a bug we picked up from a vscode merge. This should get fixed with a new merge in the future.
return joinPath(this.fileDialogService.defaultFilePath() || URI.file(this.environmentService.userHome), suggestedFilename);
}
//#endregion

View File

@@ -303,7 +303,7 @@ suite('Workbench untitled text editors', () => {
model.dispose();
});
test('service#onDidChangeLabel', async () => {
test.skip('service#onDidChangeLabel', async () => { // {{SQL CARBON EDIT}} - Disable as untitledTextEditorModel test is disabled.
const service = accessor.untitledTextEditorService;
const input = service.create();
@@ -383,7 +383,7 @@ suite('Workbench untitled text editors', () => {
assert.ok(counter > 1);
});
test('model#onDidChangeName and input name', async function () {
test.skip('model#onDidChangeName and input name', async function () { // {{SQL CARBON EDIT}} - Disable as untitledTextEditorModel test is disabled.
const service = accessor.untitledTextEditorService;
const input = service.create();

View File

@@ -63,6 +63,7 @@ function main() {
'angular2-grid',
'ng2-charts',
'rxjs/add/observable/of',
'rxjs/add/observable/fromPromise',
'rxjs/Observable',
'rxjs/Subject',
'rxjs/Observer'

View File

@@ -51,6 +51,7 @@ function initLoader(opts) {
'angular2-grid',
'ng2-charts',
'rxjs/add/observable/of',
'rxjs/add/observable/fromPromise',
'rxjs/Observable',
'rxjs/Subject',
'rxjs/Observer'

View File

@@ -192,10 +192,10 @@
dependencies:
applicationinsights "*"
"@types/chart.js@^2.7.31":
version "2.7.48"
resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.7.48.tgz#db7b6d6ed33659f97ee49181f22c980bb0790a7b"
integrity sha512-U8paSPZGkW2WrHf8sgJj7s9MhfRgSz7wfU3CN73JVrcGJ13jGiqiXyr3Bg/4UFl4cbN3S8avHTsbtzYBrnWeVg==
"@types/chart.js@2.7.57":
version "2.7.57"
resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.7.57.tgz#6f4f7a588de48c2f82e67608b8e1f01e91be38f7"
integrity sha512-kVYcxX7PdTcUrmd2Q0UjN1U0YDO1FfZ8QCDvahGmI3gvwySZYwtcRANiduOI3st6Va2T/yBBLsoHt+Gl3O7Ztw==
"@types/chokidar@2.1.3":
version "2.1.3"