Add event to track node expansion errors (#19248)

This commit is contained in:
Charles Gagnon
2022-04-29 11:02:58 -07:00
committed by GitHub
parent 52baaf298e
commit 8d45e40901
4 changed files with 44 additions and 27 deletions

View File

@@ -49,7 +49,8 @@ export const enum TelemetryView {
} }
export const enum TelemetryError { export const enum TelemetryError {
DatabaseConnectionError = 'DatabaseConnectionError' DatabaseConnectionError = 'DatabaseConnectionError',
ObjectExplorerExpandError = 'ObjectExplorerExpandError'
} }
export const enum TelemetryAction { export const enum TelemetryAction {

View File

@@ -22,6 +22,7 @@ import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import { ServerTreeActionProvider } from 'sql/workbench/services/objectExplorer/browser/serverTreeActionProvider'; import { ServerTreeActionProvider } from 'sql/workbench/services/objectExplorer/browser/serverTreeActionProvider';
import { ITree } from 'vs/base/parts/tree/browser/tree'; import { ITree } from 'vs/base/parts/tree/browser/tree';
import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree'; import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
export const SERVICE_ID = 'ObjectExplorerService'; export const SERVICE_ID = 'ObjectExplorerService';
@@ -63,9 +64,9 @@ export interface IObjectExplorerService {
closeSession(providerId: string, session: azdata.ObjectExplorerSession): Promise<azdata.ObjectExplorerCloseSessionResponse | undefined>; closeSession(providerId: string, session: azdata.ObjectExplorerSession): Promise<azdata.ObjectExplorerCloseSessionResponse | undefined>;
expandNode(providerId: string, session: azdata.ObjectExplorerSession, nodePath: string): Promise<azdata.ObjectExplorerExpandInfo>; expandNode(providerId: string, session: azdata.ObjectExplorerSession, node: TreeNode): Promise<azdata.ObjectExplorerExpandInfo>;
refreshNode(providerId: string, session: azdata.ObjectExplorerSession, nodePath: string): Promise<azdata.ObjectExplorerExpandInfo | undefined>; refreshNode(providerId: string, session: azdata.ObjectExplorerSession, node: TreeNode): Promise<azdata.ObjectExplorerExpandInfo | undefined>;
resolveTreeNodeChildren(session: azdata.ObjectExplorerSession, parentTree: TreeNode): Promise<TreeNode[]>; resolveTreeNodeChildren(session: azdata.ObjectExplorerSession, parentTree: TreeNode): Promise<TreeNode[]>;
@@ -231,7 +232,6 @@ export class ObjectExplorerService implements IObjectExplorerService {
* Gets called when expanded node response is ready * Gets called when expanded node response is ready
*/ */
public onNodeExpanded(expandResponse: NodeExpandInfoWithProviderId) { public onNodeExpanded(expandResponse: NodeExpandInfoWithProviderId) {
if (expandResponse.errorMessage) { if (expandResponse.errorMessage) {
this.logService.error(expandResponse.errorMessage); this.logService.error(expandResponse.errorMessage);
} }
@@ -358,7 +358,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
} }
} }
public async expandNode(providerId: string, session: azdata.ObjectExplorerSession, nodePath: string): Promise<azdata.ObjectExplorerExpandInfo> { public async expandNode(providerId: string, session: azdata.ObjectExplorerSession, node: TreeNode): Promise<azdata.ObjectExplorerExpandInfo> {
const provider = this._providers[providerId]; const provider = this._providers[providerId];
if (provider) { if (provider) {
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.TelemetryAction.ObjectExplorerExpand) this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.TelemetryAction.ObjectExplorerExpand)
@@ -366,7 +366,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
refresh: false, refresh: false,
provider: providerId provider: providerId
}).send(); }).send();
return await this.expandOrRefreshNode(providerId, session, nodePath); return await this.expandOrRefreshNode(providerId, session, node);
} else { } else {
throw new Error(`Provider doesn't exist. id: ${providerId}`); throw new Error(`Provider doesn't exist. id: ${providerId}`);
} }
@@ -383,14 +383,14 @@ export class ObjectExplorerService implements IObjectExplorerService {
private expandOrRefreshNode( private expandOrRefreshNode(
providerId: string, providerId: string,
session: azdata.ObjectExplorerSession, session: azdata.ObjectExplorerSession,
nodePath: string, node: TreeNode,
refresh: boolean = false): Promise<azdata.ObjectExplorerExpandInfo> { refresh: boolean = false): Promise<azdata.ObjectExplorerExpandInfo> {
let self = this; let self = this;
return new Promise<azdata.ObjectExplorerExpandInfo>((resolve, reject) => { return new Promise<azdata.ObjectExplorerExpandInfo>((resolve, reject) => {
if (session.sessionId! in self._sessions && self._sessions[session.sessionId!]) { if (session.sessionId! in self._sessions && self._sessions[session.sessionId!]) {
let newRequest = false; let newRequest = false;
if (!self._sessions[session.sessionId!].nodes[nodePath]) { if (!self._sessions[session.sessionId!].nodes[node.nodePath]) {
self._sessions[session.sessionId!].nodes[nodePath] = { self._sessions[session.sessionId!].nodes[node.nodePath] = {
expandEmitter: new Emitter<NodeExpandInfoWithProviderId>() expandEmitter: new Emitter<NodeExpandInfoWithProviderId>()
}; };
newRequest = true; newRequest = true;
@@ -406,20 +406,33 @@ export class ObjectExplorerService implements IObjectExplorerService {
allProviders.push(...nodeProviders); allProviders.push(...nodeProviders);
} }
self._sessions[session.sessionId!].nodes[nodePath].expandEmitter.event((expandResult: NodeExpandInfoWithProviderId) => { self._sessions[session.sessionId!].nodes[node.nodePath].expandEmitter.event((expandResult: NodeExpandInfoWithProviderId) => {
if (expandResult && expandResult.providerId) { if (expandResult && expandResult.providerId) {
resultMap.set(expandResult.providerId, expandResult); resultMap.set(expandResult.providerId, expandResult);
// If we got an error result back then send error our error event
// We only do this for the MSSQL provider
if (expandResult.errorMessage && expandResult.providerId === mssqlProviderName) {
const errorType = expandResult.errorMessage.indexOf('Object Explorer task didn\'t complete') !== -1 ? 'Timeout' : 'Other';
// For folders send the actual name of the folder (since the nodeTypeId isn't useful in this case and the names are controlled by us)
const nodeType = node.nodeTypeId === NodeType.Folder ? node.label : node.nodeTypeId;
this._telemetryService.createErrorEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.TelemetryError.ObjectExplorerExpandError, undefined, errorType)
.withAdditionalProperties({
nodeType,
providerId: expandResult.providerId
}).send();
}
} else { } else {
this.logService.error('OE provider returns empty result or providerId'); this.logService.error('OE provider returns empty result or providerId');
} }
// When get all responses from all providers, merge results // When get all responses from all providers, merge results
if (resultMap.size === allProviders.length) { if (resultMap.size === allProviders.length) {
resolve(self.mergeResults(allProviders, resultMap, nodePath)); resolve(self.mergeResults(allProviders, resultMap, node.nodePath));
// Have to delete it after get all reponses otherwise couldn't find session for not the first response // Have to delete it after get all reponses otherwise couldn't find session for not the first response
if (newRequest) { if (newRequest) {
delete self._sessions[session.sessionId!].nodes[nodePath]; delete self._sessions[session.sessionId!].nodes[node.nodePath];
} }
} }
}); });
@@ -427,13 +440,13 @@ export class ObjectExplorerService implements IObjectExplorerService {
allProviders.forEach(provider => { allProviders.forEach(provider => {
self.callExpandOrRefreshFromProvider(provider, { self.callExpandOrRefreshFromProvider(provider, {
sessionId: session.sessionId!, sessionId: session.sessionId!,
nodePath: nodePath nodePath: node.nodePath
}, refresh).then(isExpanding => { }, refresh).then(isExpanding => {
if (!isExpanding) { if (!isExpanding) {
// The provider stated it's not going to expand the node, therefore do not need to track when merging results // The provider stated it's not going to expand the node, therefore do not need to track when merging results
let emptyResult: azdata.ObjectExplorerExpandInfo = { let emptyResult: azdata.ObjectExplorerExpandInfo = {
errorMessage: undefined, errorMessage: undefined,
nodePath: nodePath, nodePath: node.nodePath,
nodes: [], nodes: [],
sessionId: session.sessionId sessionId: session.sessionId
}; };
@@ -446,7 +459,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
} }
} }
} else { } else {
reject(`session cannot find to expand node. id: ${session.sessionId} nodePath: ${nodePath}`); reject(`session cannot find to expand node. id: ${session.sessionId} nodePath: ${node.nodePath}`);
} }
}); });
} }
@@ -495,7 +508,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
return finalResult; return finalResult;
} }
public refreshNode(providerId: string, session: azdata.ObjectExplorerSession, nodePath: string): Promise<azdata.ObjectExplorerExpandInfo | undefined> { public refreshNode(providerId: string, session: azdata.ObjectExplorerSession, node: TreeNode): Promise<azdata.ObjectExplorerExpandInfo | undefined> {
let provider = this._providers[providerId]; let provider = this._providers[providerId];
if (provider) { if (provider) {
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.TelemetryAction.ObjectExplorerExpand) this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.TelemetryAction.ObjectExplorerExpand)
@@ -503,7 +516,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
refresh: true, refresh: true,
provider: providerId provider: providerId
}).send(); }).send();
return this.expandOrRefreshNode(providerId, session, nodePath, true); return this.expandOrRefreshNode(providerId, session, node, true);
} }
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }
@@ -569,11 +582,11 @@ export class ObjectExplorerService implements IObjectExplorerService {
return this.expandOrRefreshTreeNode(session, parentTree, true); return this.expandOrRefreshTreeNode(session, parentTree, true);
} }
private callExpandOrRefreshFromService(providerId: string, session: azdata.ObjectExplorerSession, nodePath: string, refresh: boolean = false): Promise<azdata.ObjectExplorerExpandInfo | undefined> { private callExpandOrRefreshFromService(providerId: string, session: azdata.ObjectExplorerSession, node: TreeNode, refresh: boolean = false): Promise<azdata.ObjectExplorerExpandInfo | undefined> {
if (refresh) { if (refresh) {
return this.refreshNode(providerId, session, nodePath); return this.refreshNode(providerId, session, node);
} else { } else {
return this.expandNode(providerId, session, nodePath); return this.expandNode(providerId, session, node);
} }
} }
@@ -585,7 +598,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
if (!providerName) { if (!providerName) {
throw new Error('Failed to expand node - no provider name'); throw new Error('Failed to expand node - no provider name');
} }
const expandResult = await this.callExpandOrRefreshFromService(providerName, session, parentTree.nodePath, refresh); const expandResult = await this.callExpandOrRefreshFromService(providerName, session, parentTree, refresh);
if (expandResult && expandResult.nodes) { if (expandResult && expandResult.nodes) {
const children = expandResult.nodes.map(node => { const children = expandResult.nodes.map(node => {
return this.toTreeNode(node, parentTree); return this.toTreeNode(node, parentTree);

View File

@@ -325,9 +325,10 @@ suite('SQL Object Explorer Service tests', () => {
}); });
test('expand node should expand node correctly', async () => { test('expand node should expand node correctly', async () => {
const tablesNode = new TreeNode(NodeType.Folder, 'Tables', false, 'testServerName/tables', '', '', null, null, undefined, undefined);
await objectExplorerService.createNewSession(mssqlProviderName, connection); await objectExplorerService.createNewSession(mssqlProviderName, connection);
objectExplorerService.onSessionCreated(1, objectExplorerSession); objectExplorerService.onSessionCreated(1, objectExplorerSession);
const expandInfo = await objectExplorerService.expandNode(mssqlProviderName, objectExplorerSession, 'testServerName/tables'); const expandInfo = await objectExplorerService.expandNode(mssqlProviderName, objectExplorerSession, tablesNode);
assert.strictEqual(expandInfo !== null || expandInfo !== undefined, true); assert.strictEqual(expandInfo !== null || expandInfo !== undefined, true);
assert.strictEqual(expandInfo.sessionId, '1234'); assert.strictEqual(expandInfo.sessionId, '1234');
assert.strictEqual(expandInfo.nodes.length, 2); assert.strictEqual(expandInfo.nodes.length, 2);
@@ -337,9 +338,10 @@ suite('SQL Object Explorer Service tests', () => {
}); });
test('refresh node should refresh node correctly', async () => { test('refresh node should refresh node correctly', async () => {
const tablesNode = new TreeNode(NodeType.Folder, 'Tables', false, 'testServerName/tables', '', '', null, null, undefined, undefined);
await objectExplorerService.createNewSession(mssqlProviderName, connection); await objectExplorerService.createNewSession(mssqlProviderName, connection);
objectExplorerService.onSessionCreated(1, objectExplorerSession); objectExplorerService.onSessionCreated(1, objectExplorerSession);
const expandInfo = await objectExplorerService.refreshNode(mssqlProviderName, objectExplorerSession, 'testServerName/tables'); const expandInfo = await objectExplorerService.refreshNode(mssqlProviderName, objectExplorerSession, tablesNode);
assert.strictEqual(expandInfo !== null || expandInfo !== undefined, true); assert.strictEqual(expandInfo !== null || expandInfo !== undefined, true);
assert.strictEqual(expandInfo.sessionId, '1234'); assert.strictEqual(expandInfo.sessionId, '1234');
assert.strictEqual(expandInfo.nodes.length, 2); assert.strictEqual(expandInfo.nodes.length, 2);
@@ -651,8 +653,9 @@ suite('SQL Object Explorer Service tests', () => {
sqlOEProvider.setup(x => x.expandNode(TypeMoq.It.is(x => x.nodePath === nodePath))).callback(() => { }).returns(() => Promise.resolve(true)); sqlOEProvider.setup(x => x.expandNode(TypeMoq.It.is(x => x.nodePath === nodePath))).callback(() => { }).returns(() => Promise.resolve(true));
// If I queue a second expand request (the first compconstes normally because of the original mock) and then close the session // If I queue a second expand request (the first compconstes normally because of the original mock) and then close the session
await objectExplorerService.expandNode(mssqlProviderName, objectExplorerSession, objectExplorerSession.rootNode.nodePath); const rootNode = new TreeNode(NodeType.Root, '', false, objectExplorerSession.rootNode.nodePath, '', '', null, null, undefined, undefined);
const expandPromise = objectExplorerService.expandNode(mssqlProviderName, objectExplorerSession, objectExplorerSession.rootNode.nodePath); await objectExplorerService.expandNode(mssqlProviderName, objectExplorerSession, rootNode);
const expandPromise = objectExplorerService.expandNode(mssqlProviderName, objectExplorerSession, rootNode);
const closeSessionResult = await objectExplorerService.closeSession(mssqlProviderName, objectExplorerSession); const closeSessionResult = await objectExplorerService.closeSession(mssqlProviderName, objectExplorerSession);
// Then the expand request has compconsted and the session is closed // Then the expand request has compconsted and the session is closed

View File

@@ -71,9 +71,9 @@ export class TestObjectExplorerService implements IObjectExplorerService {
public async createNewSession(providerId: string, connection: ConnectionProfile): Promise<azdata.ObjectExplorerSessionResponse> { throw new Error('Method not implemented'); } public async createNewSession(providerId: string, connection: ConnectionProfile): Promise<azdata.ObjectExplorerSessionResponse> { throw new Error('Method not implemented'); }
public async expandNode(providerId: string, session: azdata.ObjectExplorerSession, nodePath: string): Promise<azdata.ObjectExplorerExpandInfo> { throw new Error('Method not implemented'); } public async expandNode(providerId: string, session: azdata.ObjectExplorerSession, node: TreeNode): Promise<azdata.ObjectExplorerExpandInfo> { throw new Error('Method not implemented'); }
public async refreshNode(providerId: string, session: azdata.ObjectExplorerSession, nodePath: string): Promise<azdata.ObjectExplorerExpandInfo> { throw new Error('Method not implemented'); } public async refreshNode(providerId: string, session: azdata.ObjectExplorerSession, node: TreeNode): Promise<azdata.ObjectExplorerExpandInfo> { throw new Error('Method not implemented'); }
public async closeSession(providerId: string, session: azdata.ObjectExplorerSession): Promise<azdata.ObjectExplorerCloseSessionResponse> { throw new Error('Method not implemented'); } public async closeSession(providerId: string, session: azdata.ObjectExplorerSession): Promise<azdata.ObjectExplorerCloseSessionResponse> { throw new Error('Method not implemented'); }