mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Inital Messages Tab work (#5604)
* inital work * iterate * move messages to tab * revert package changes * remove unused properties * format imports
This commit is contained in:
@@ -6,15 +6,12 @@
|
|||||||
import 'vs/css!./media/messagePanel';
|
import 'vs/css!./media/messagePanel';
|
||||||
import { IMessagesActionContext, CopyMessagesAction, CopyAllMessagesAction } from './actions';
|
import { IMessagesActionContext, CopyMessagesAction, CopyAllMessagesAction } from './actions';
|
||||||
import QueryRunner, { IQueryMessage } from 'sql/platform/query/common/queryRunner';
|
import QueryRunner, { IQueryMessage } from 'sql/platform/query/common/queryRunner';
|
||||||
import { QueryInput } from 'sql/workbench/parts/query/common/queryInput';
|
|
||||||
import { IExpandableTree } from 'sql/workbench/parts/objectExplorer/browser/treeUpdateUtils';
|
import { IExpandableTree } from 'sql/workbench/parts/objectExplorer/browser/treeUpdateUtils';
|
||||||
|
|
||||||
import { ISelectionData } from 'azdata';
|
import { ISelectionData } from 'azdata';
|
||||||
|
|
||||||
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
|
|
||||||
import { IDataSource, ITree, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
|
import { IDataSource, ITree, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
|
||||||
import { generateUuid } from 'vs/base/common/uuid';
|
import { generateUuid } from 'vs/base/common/uuid';
|
||||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
|
||||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||||
@@ -24,12 +21,14 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
|||||||
import { OpenMode, ClickBehavior, ICancelableEvent, IControllerOptions } from 'vs/base/parts/tree/browser/treeDefaults';
|
import { OpenMode, ClickBehavior, ICancelableEvent, IControllerOptions } from 'vs/base/parts/tree/browser/treeDefaults';
|
||||||
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
|
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
|
||||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||||
import { isArray, isUndefinedOrNull } from 'vs/base/common/types';
|
import { isArray } from 'vs/base/common/types';
|
||||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
|
||||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||||
import { $ } from 'vs/base/browser/dom';
|
import { $, Dimension } from 'vs/base/browser/dom';
|
||||||
|
import { QueryEditor } from 'sql/workbench/parts/query/browser/queryEditor';
|
||||||
|
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||||
|
|
||||||
export interface IResultMessageIntern extends IQueryMessage {
|
export interface IResultMessageIntern extends IQueryMessage {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -62,21 +61,13 @@ const TemplateIds = {
|
|||||||
|
|
||||||
export class MessagePanelState {
|
export class MessagePanelState {
|
||||||
public scrollPosition: number;
|
public scrollPosition: number;
|
||||||
public collapsed = false;
|
|
||||||
|
|
||||||
constructor(@IConfigurationService configurationService: IConfigurationService) {
|
|
||||||
let messagesOpenedSettings = configurationService.getValue<boolean>('sql.messagesDefaultOpen');
|
|
||||||
if (!isUndefinedOrNull(messagesOpenedSettings)) {
|
|
||||||
this.collapsed = !messagesOpenedSettings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MessagePanel extends ViewletPanel {
|
export class MessagePanel extends Disposable {
|
||||||
private messageLineCountMap = new Map<IQueryMessage, number>();
|
private messageLineCountMap = new Map<IQueryMessage, number>();
|
||||||
private ds = new MessageDataSource();
|
private ds = new MessageDataSource();
|
||||||
private renderer = new MessageRenderer(this.messageLineCountMap);
|
private renderer = new MessageRenderer(this.messageLineCountMap);
|
||||||
@@ -90,23 +81,19 @@ export class MessagePanel extends ViewletPanel {
|
|||||||
private tree: ITree;
|
private tree: ITree;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
options: IViewletPanelOptions,
|
|
||||||
@IKeybindingService keybindingService: IKeybindingService,
|
|
||||||
@IContextMenuService contextMenuService: IContextMenuService,
|
|
||||||
@IConfigurationService configurationService: IConfigurationService,
|
|
||||||
@IThemeService private themeService: IThemeService,
|
|
||||||
@IInstantiationService instantiationService: IInstantiationService,
|
@IInstantiationService instantiationService: IInstantiationService,
|
||||||
@IClipboardService private clipboardService: IClipboardService
|
@IThemeService private readonly themeService: IThemeService,
|
||||||
|
@IClipboardService private readonly clipboardService: IClipboardService,
|
||||||
|
@IContextMenuService private readonly contextMenuService: IContextMenuService
|
||||||
) {
|
) {
|
||||||
super(options, keybindingService, contextMenuService, configurationService);
|
super();
|
||||||
this.controller = instantiationService.createInstance(MessageController, { openMode: OpenMode.SINGLE_CLICK, clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change, to preserve focus behaviour in input field */ });
|
this.controller = instantiationService.createInstance(MessageController, { openMode: OpenMode.SINGLE_CLICK, clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change, to preserve focus behaviour in input field */ });
|
||||||
this.controller.toFocusOnClick = this.model;
|
this.controller.toFocusOnClick = this.model;
|
||||||
this.tree = new Tree(this.container, {
|
this.tree = this._register(new Tree(this.container, {
|
||||||
dataSource: this.ds,
|
dataSource: this.ds,
|
||||||
renderer: this.renderer,
|
renderer: this.renderer,
|
||||||
controller: this.controller
|
controller: this.controller
|
||||||
}, { keyboardSupport: false, horizontalScrollMode: ScrollbarVisibility.Auto });
|
}, { keyboardSupport: false, horizontalScrollMode: ScrollbarVisibility.Auto }));
|
||||||
this.disposables.push(this.tree);
|
|
||||||
this.tree.onDidScroll(e => {
|
this.tree.onDidScroll(e => {
|
||||||
// convert to old VS Code tree interface with expandable methods
|
// convert to old VS Code tree interface with expandable methods
|
||||||
let expandableTree: IExpandableTree = <IExpandableTree>this.tree;
|
let expandableTree: IExpandableTree = <IExpandableTree>this.tree;
|
||||||
@@ -115,11 +102,6 @@ export class MessagePanel extends ViewletPanel {
|
|||||||
this.state.scrollPosition = expandableTree.getScrollPosition();
|
this.state.scrollPosition = expandableTree.getScrollPosition();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.onDidChange(e => {
|
|
||||||
if (this.state) {
|
|
||||||
this.state.collapsed = !this.isExpanded();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.controller.onKeyDown = (tree, event) => {
|
this.controller.onKeyDown = (tree, event) => {
|
||||||
if (event.ctrlKey) {
|
if (event.ctrlKey) {
|
||||||
let context: IMessagesActionContext = {
|
let context: IMessagesActionContext = {
|
||||||
@@ -171,20 +153,20 @@ export class MessagePanel extends ViewletPanel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected renderBody(container: HTMLElement): void {
|
public render(container: HTMLElement): void {
|
||||||
this.container.style.width = '100%';
|
this.container.style.width = '100%';
|
||||||
this.container.style.height = '100%';
|
this.container.style.height = '100%';
|
||||||
this.disposables.push(attachListStyler(this.tree, this.themeService));
|
this._register(attachListStyler(this.tree, this.themeService));
|
||||||
container.appendChild(this.container);
|
container.appendChild(this.container);
|
||||||
this.tree.setInput(this.model);
|
this.tree.setInput(this.model);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected layoutBody(size: number): void {
|
public layout(size: Dimension): void {
|
||||||
// convert to old VS Code tree interface with expandable methods
|
// convert to old VS Code tree interface with expandable methods
|
||||||
let expandableTree: IExpandableTree = <IExpandableTree>this.tree;
|
let expandableTree: IExpandableTree = <IExpandableTree>this.tree;
|
||||||
|
|
||||||
const previousScrollPosition = expandableTree.getScrollPosition();
|
const previousScrollPosition = expandableTree.getScrollPosition();
|
||||||
this.tree.layout(size);
|
this.tree.layout(size.height);
|
||||||
if (this.state && this.state.scrollPosition) {
|
if (this.state && this.state.scrollPosition) {
|
||||||
expandableTree.setScrollPosition(this.state.scrollPosition);
|
expandableTree.setScrollPosition(this.state.scrollPosition);
|
||||||
} else {
|
} else {
|
||||||
@@ -204,21 +186,11 @@ export class MessagePanel extends ViewletPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onMessage(message: IQueryMessage | IQueryMessage[]) {
|
private onMessage(message: IQueryMessage | IQueryMessage[]) {
|
||||||
let hasError = false;
|
|
||||||
let lines: number;
|
|
||||||
if (isArray(message)) {
|
if (isArray(message)) {
|
||||||
hasError = message.find(e => e.isError) ? true : false;
|
|
||||||
lines = message.reduce((currentTotal, resultMessage) => currentTotal + this.countMessageLines(resultMessage), 0);
|
|
||||||
this.model.messages.push(...message);
|
this.model.messages.push(...message);
|
||||||
} else {
|
} else {
|
||||||
hasError = message.isError;
|
|
||||||
lines = this.countMessageLines(message);
|
|
||||||
this.model.messages.push(message);
|
this.model.messages.push(message);
|
||||||
}
|
}
|
||||||
this.maximumBodySize += lines * 22;
|
|
||||||
if (hasError) {
|
|
||||||
this.setExpanded(true);
|
|
||||||
}
|
|
||||||
// convert to old VS Code tree interface with expandable methods
|
// convert to old VS Code tree interface with expandable methods
|
||||||
let expandableTree: IExpandableTree = <IExpandableTree>this.tree;
|
let expandableTree: IExpandableTree = <IExpandableTree>this.tree;
|
||||||
if (this.state.scrollPosition) {
|
if (this.state.scrollPosition) {
|
||||||
@@ -237,12 +209,6 @@ export class MessagePanel extends ViewletPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private countMessageLines(resultMessage: IQueryMessage): number {
|
|
||||||
let lines = resultMessage.message.split('\n').length;
|
|
||||||
this.messageLineCountMap.set(resultMessage, lines);
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
private reset() {
|
private reset() {
|
||||||
this.model.messages = [];
|
this.model.messages = [];
|
||||||
this.model.totalExecuteMessage = undefined;
|
this.model.totalExecuteMessage = undefined;
|
||||||
@@ -256,7 +222,6 @@ export class MessagePanel extends ViewletPanel {
|
|||||||
if (this.state.scrollPosition) {
|
if (this.state.scrollPosition) {
|
||||||
expandableTree.setScrollPosition(this.state.scrollPosition);
|
expandableTree.setScrollPosition(this.state.scrollPosition);
|
||||||
}
|
}
|
||||||
this.setExpanded(!this.state.collapsed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get state(): MessagePanelState {
|
public get state(): MessagePanelState {
|
||||||
@@ -404,8 +369,10 @@ export class MessageController extends WorkbenchTreeController {
|
|||||||
if (element.selection) {
|
if (element.selection) {
|
||||||
let selection: ISelectionData = element.selection;
|
let selection: ISelectionData = element.selection;
|
||||||
// this is a batch statement
|
// this is a batch statement
|
||||||
let input = this.workbenchEditorService.activeEditor as QueryInput;
|
let editor = this.workbenchEditorService.activeControl as QueryEditor;
|
||||||
input.updateSelection(selection);
|
const codeEditor = <ICodeEditor>editor.getControl();
|
||||||
|
codeEditor.focus();
|
||||||
|
codeEditor.setSelection({ endColumn: selection.endColumn + 1, endLineNumber: selection.endLine + 1, startColumn: selection.startColumn + 1, startLineNumber: selection.startLine + 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -294,11 +294,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
|||||||
|
|
||||||
// Intellisense and other configuration options
|
// Intellisense and other configuration options
|
||||||
let registryProperties = {
|
let registryProperties = {
|
||||||
'sql.messagesDefaultOpen': {
|
|
||||||
'type': 'boolean',
|
|
||||||
'description': localize('sql.messagesDefaultOpen', 'True for the messages pane to be open by default; false for closed'),
|
|
||||||
'default': true
|
|
||||||
},
|
|
||||||
'sql.saveAsCsv.includeHeaders': {
|
'sql.saveAsCsv.includeHeaders': {
|
||||||
'type': 'boolean',
|
'type': 'boolean',
|
||||||
'description': localize('sql.saveAsCsv.includeHeaders', '[Optional] When true, column headers are included when saving results as CSV'),
|
'description': localize('sql.saveAsCsv.includeHeaders', '[Optional] When true, column headers are included when saving results as CSV'),
|
||||||
|
|||||||
@@ -3,74 +3,33 @@
|
|||||||
* 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 { QueryResultsInput, ResultsViewState } from 'sql/workbench/parts/query/common/queryResultsInput';
|
import { QueryResultsInput } from 'sql/workbench/parts/query/common/queryResultsInput';
|
||||||
import { TabbedPanel, IPanelTab, IPanelView } from 'sql/base/browser/ui/panel/panel';
|
import { TabbedPanel, IPanelTab, IPanelView } from 'sql/base/browser/ui/panel/panel';
|
||||||
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
|
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
|
||||||
import QueryRunner from 'sql/platform/query/common/queryRunner';
|
import QueryRunner from 'sql/platform/query/common/queryRunner';
|
||||||
import { MessagePanel } from './messagePanel';
|
import { MessagePanel, MessagePanelState } from 'sql/workbench/parts/query/browser/messagePanel';
|
||||||
import { GridPanel } from '../electron-browser/gridPanel';
|
import { GridPanel, GridPanelState } from 'sql/workbench/parts/query/electron-browser/gridPanel';
|
||||||
import { ChartTab } from '../../charts/browser/chartTab';
|
import { ChartTab } from 'sql/workbench/parts/charts/browser/chartTab';
|
||||||
import { QueryPlanTab } from 'sql/workbench/parts/queryPlan/electron-browser/queryPlan';
|
import { QueryPlanTab } from 'sql/workbench/parts/queryPlan/electron-browser/queryPlan';
|
||||||
import { TopOperationsTab } from 'sql/workbench/parts/queryPlan/browser/topOperations';
|
import { TopOperationsTab } from 'sql/workbench/parts/queryPlan/browser/topOperations';
|
||||||
import { QueryModelViewTab } from 'sql/workbench/parts/query/modelViewTab/queryModelViewTab';
|
import { QueryModelViewTab } from 'sql/workbench/parts/query/modelViewTab/queryModelViewTab';
|
||||||
|
|
||||||
import * as nls from 'vs/nls';
|
import * as nls from 'vs/nls';
|
||||||
import { PanelViewlet } from 'vs/workbench/browser/parts/views/panelViewlet';
|
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
import { Event } from 'vs/base/common/event';
|
|
||||||
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
|
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
|
||||||
import { attachTabbedPanelStyler } from 'sql/platform/theme/common/styler';
|
import { attachTabbedPanelStyler } from 'sql/platform/theme/common/styler';
|
||||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
|
||||||
class ResultsView extends Disposable implements IPanelView {
|
class MessagesView extends Disposable implements IPanelView {
|
||||||
private panelViewlet: PanelViewlet;
|
|
||||||
private gridPanel: GridPanel;
|
|
||||||
private messagePanel: MessagePanel;
|
private messagePanel: MessagePanel;
|
||||||
private container = document.createElement('div');
|
private container = document.createElement('div');
|
||||||
private currentDimension: DOM.Dimension;
|
private _state: MessagePanelState;
|
||||||
private _state: ResultsViewState;
|
|
||||||
|
|
||||||
constructor(private instantiationService: IInstantiationService) {
|
constructor(private instantiationService: IInstantiationService) {
|
||||||
super();
|
super();
|
||||||
this.panelViewlet = this._register(this.instantiationService.createInstance(PanelViewlet, 'resultsView', { showHeaderInTitleWhenSingleView: false }));
|
this.messagePanel = this._register(this.instantiationService.createInstance(MessagePanel));
|
||||||
this.gridPanel = this._register(this.instantiationService.createInstance(GridPanel, { title: nls.localize('gridPanel', 'Results'), id: 'gridPanel' }));
|
this.messagePanel.render(this.container);
|
||||||
this.messagePanel = this._register(this.instantiationService.createInstance(MessagePanel, { title: nls.localize('messagePanel', 'Messages'), minimumBodySize: 0, id: 'messagePanel' }));
|
|
||||||
this.gridPanel.render();
|
|
||||||
this.messagePanel.render();
|
|
||||||
this.panelViewlet.create(this.container);
|
|
||||||
this.gridPanel.setVisible(false);
|
|
||||||
this.panelViewlet.addPanels([
|
|
||||||
{ panel: this.messagePanel, size: this.messagePanel.minimumSize, index: 1 }
|
|
||||||
]);
|
|
||||||
Event.any(this.gridPanel.onDidChange, this.messagePanel.onDidChange)(e => {
|
|
||||||
let size = this.gridPanel.maximumBodySize;
|
|
||||||
if (size < 1 && this.gridPanel.isVisible()) {
|
|
||||||
this.gridPanel.setVisible(false);
|
|
||||||
this.panelViewlet.removePanels([this.gridPanel]);
|
|
||||||
this.gridPanel.layout(0);
|
|
||||||
} else if (size > 0 && !this.gridPanel.isVisible()) {
|
|
||||||
this.gridPanel.setVisible(true);
|
|
||||||
this.panelViewlet.addPanels([{ panel: this.gridPanel, index: 0, size: 200 }]);
|
|
||||||
}
|
|
||||||
if (this.gridPanel.isVisible()) {
|
|
||||||
if (this.state.messagePanelSize) {
|
|
||||||
this.panelViewlet.resizePanel(this.messagePanel, this.state.messagePanelSize);
|
|
||||||
}
|
|
||||||
this.panelViewlet.resizePanel(this.gridPanel, this.getGridPanelSize());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.panelViewlet.onDidSashChange(e => {
|
|
||||||
if (this.state) {
|
|
||||||
if (this.gridPanel.isExpanded()) {
|
|
||||||
this.state.gridPanelSize = this.panelViewlet.getPanelSize(this.gridPanel);
|
|
||||||
}
|
|
||||||
if (this.messagePanel.isExpanded()) {
|
|
||||||
this.state.messagePanelSize = this.panelViewlet.getPanelSize(this.messagePanel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(container: HTMLElement): void {
|
render(container: HTMLElement): void {
|
||||||
@@ -78,29 +37,12 @@ class ResultsView extends Disposable implements IPanelView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
layout(dimension: DOM.Dimension): void {
|
layout(dimension: DOM.Dimension): void {
|
||||||
this.panelViewlet.layout(dimension);
|
this.container.style.width = `${dimension.width}px`;
|
||||||
// the grid won't be resized if the height has not changed so we need to do it manually
|
this.container.style.height = `${dimension.height}px`;
|
||||||
if (this.currentDimension && dimension.height === this.currentDimension.height) {
|
this.messagePanel.layout(dimension);
|
||||||
this.gridPanel.layout(dimension.height);
|
|
||||||
}
|
|
||||||
this.currentDimension = dimension;
|
|
||||||
|
|
||||||
// resize the messages and grid panels
|
|
||||||
this.panelViewlet.resizePanel(this.gridPanel, this.getGridPanelSize());
|
|
||||||
// we have the right scroll position saved as part of gridPanel state, use this to re-position scrollbar
|
|
||||||
this.gridPanel.resetScrollPosition();
|
|
||||||
|
|
||||||
if (this.state.messagePanelSize) {
|
|
||||||
this.panelViewlet.resizePanel(this.messagePanel, this.state.messagePanelSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {
|
|
||||||
super.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public clear() {
|
public clear() {
|
||||||
this.gridPanel.clear();
|
|
||||||
this.messagePanel.clear();
|
this.messagePanel.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,32 +51,59 @@ class ResultsView extends Disposable implements IPanelView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public set queryRunner(runner: QueryRunner) {
|
public set queryRunner(runner: QueryRunner) {
|
||||||
this.gridPanel.queryRunner = runner;
|
|
||||||
this.messagePanel.queryRunner = runner;
|
this.messagePanel.queryRunner = runner;
|
||||||
}
|
}
|
||||||
|
|
||||||
public hideResultHeader() {
|
public set state(val: MessagePanelState) {
|
||||||
this.gridPanel.headerVisible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public set state(val: ResultsViewState) {
|
|
||||||
this._state = val;
|
this._state = val;
|
||||||
this.gridPanel.state = val.gridPanelState;
|
this.messagePanel.state = val;
|
||||||
this.messagePanel.state = val.messagePanelState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get state(): ResultsViewState {
|
public get state(): MessagePanelState {
|
||||||
return this._state;
|
return this._state;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getGridPanelSize(): number {
|
class ResultsView extends Disposable implements IPanelView {
|
||||||
if (this.state && this.state.gridPanelSize) {
|
private gridPanel: GridPanel;
|
||||||
return this.state.gridPanelSize;
|
private container = document.createElement('div');
|
||||||
} else if (this.currentDimension) {
|
private _state: GridPanelState;
|
||||||
return Math.round(Math.max(this.currentDimension.height * 0.7, this.currentDimension.height - 150));
|
|
||||||
} else {
|
constructor(private instantiationService: IInstantiationService) {
|
||||||
return 200;
|
super();
|
||||||
|
this.gridPanel = this._register(this.instantiationService.createInstance(GridPanel));
|
||||||
|
this.gridPanel.render(this.container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render(container: HTMLElement): void {
|
||||||
|
container.appendChild(this.container);
|
||||||
|
}
|
||||||
|
|
||||||
|
layout(dimension: DOM.Dimension): void {
|
||||||
|
this.container.style.width = `${dimension.width}px`;
|
||||||
|
this.container.style.height = `${dimension.height}px`;
|
||||||
|
this.gridPanel.layout(dimension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear() {
|
||||||
|
this.gridPanel.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(): void {
|
||||||
|
this.container.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
public set queryRunner(runner: QueryRunner) {
|
||||||
|
this.gridPanel.queryRunner = runner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set state(val: GridPanelState) {
|
||||||
|
this._state = val;
|
||||||
|
this.gridPanel.state = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get state(): GridPanelState {
|
||||||
|
return this._state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,10 +129,33 @@ class ResultsTab implements IPanelTab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MessagesTab implements IPanelTab {
|
||||||
|
public readonly title = nls.localize('messagesTabTitle', 'Messages');
|
||||||
|
public readonly identifier = 'messagesTab';
|
||||||
|
public readonly view: MessagesView;
|
||||||
|
|
||||||
|
constructor(instantiationService: IInstantiationService) {
|
||||||
|
this.view = new MessagesView(instantiationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
public set queryRunner(runner: QueryRunner) {
|
||||||
|
this.view.queryRunner = runner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() {
|
||||||
|
dispose(this.view);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear() {
|
||||||
|
this.view.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class QueryResultsView extends Disposable {
|
export class QueryResultsView extends Disposable {
|
||||||
private _panelView: TabbedPanel;
|
private _panelView: TabbedPanel;
|
||||||
private _input: QueryResultsInput;
|
private _input: QueryResultsInput;
|
||||||
private resultsTab: ResultsTab;
|
private resultsTab: ResultsTab;
|
||||||
|
private messagesTab: MessagesTab;
|
||||||
private chartTab: ChartTab;
|
private chartTab: ChartTab;
|
||||||
private qpTab: QueryPlanTab;
|
private qpTab: QueryPlanTab;
|
||||||
private topOperationsTab: TopOperationsTab;
|
private topOperationsTab: TopOperationsTab;
|
||||||
@@ -179,6 +171,7 @@ export class QueryResultsView extends Disposable {
|
|||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.resultsTab = this._register(new ResultsTab(instantiationService));
|
this.resultsTab = this._register(new ResultsTab(instantiationService));
|
||||||
|
this.messagesTab = this._register(new MessagesTab(instantiationService));
|
||||||
this.chartTab = this._register(new ChartTab(instantiationService));
|
this.chartTab = this._register(new ChartTab(instantiationService));
|
||||||
this._panelView = this._register(new TabbedPanel(container, { showHeaderWhenSingleView: false }));
|
this._panelView = this._register(new TabbedPanel(container, { showHeaderWhenSingleView: false }));
|
||||||
attachTabbedPanelStyler(this._panelView, themeService);
|
attachTabbedPanelStyler(this._panelView, themeService);
|
||||||
@@ -188,6 +181,7 @@ export class QueryResultsView extends Disposable {
|
|||||||
attachTabbedPanelStyler(this._panelView, themeService);
|
attachTabbedPanelStyler(this._panelView, themeService);
|
||||||
|
|
||||||
this._panelView.pushTab(this.resultsTab);
|
this._panelView.pushTab(this.resultsTab);
|
||||||
|
this._panelView.pushTab(this.messagesTab);
|
||||||
this._register(this._panelView.onTabChange(e => {
|
this._register(this._panelView.onTabChange(e => {
|
||||||
if (this.input) {
|
if (this.input) {
|
||||||
this.input.state.activeTab = e;
|
this.input.state.activeTab = e;
|
||||||
@@ -200,6 +194,7 @@ export class QueryResultsView extends Disposable {
|
|||||||
|
|
||||||
private setQueryRunner(runner: QueryRunner) {
|
private setQueryRunner(runner: QueryRunner) {
|
||||||
this.resultsTab.queryRunner = runner;
|
this.resultsTab.queryRunner = runner;
|
||||||
|
this.messagesTab.queryRunner = runner;
|
||||||
this.chartTab.queryRunner = runner;
|
this.chartTab.queryRunner = runner;
|
||||||
this.runnerDisposables.push(runner.onQueryStart(e => {
|
this.runnerDisposables.push(runner.onQueryStart(e => {
|
||||||
this.hideChart();
|
this.hideChart();
|
||||||
@@ -249,6 +244,8 @@ export class QueryResultsView extends Disposable {
|
|||||||
}));
|
}));
|
||||||
if (this.input.state.activeTab) {
|
if (this.input.state.activeTab) {
|
||||||
this._panelView.showTab(this.input.state.activeTab);
|
this._panelView.showTab(this.input.state.activeTab);
|
||||||
|
} else {
|
||||||
|
this._panelView.showTab(this.resultsTab.identifier); // our default tab is the results view
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +253,8 @@ export class QueryResultsView extends Disposable {
|
|||||||
this._input = input;
|
this._input = input;
|
||||||
dispose(this.runnerDisposables);
|
dispose(this.runnerDisposables);
|
||||||
this.runnerDisposables = [];
|
this.runnerDisposables = [];
|
||||||
this.resultsTab.view.state = this.input.state;
|
this.resultsTab.view.state = this.input.state.gridPanelState;
|
||||||
|
this.messagesTab.view.state = this.input.state.messagePanelState;
|
||||||
this.qpTab.view.state = this.input.state.queryPlanState;
|
this.qpTab.view.state = this.input.state.queryPlanState;
|
||||||
this.topOperationsTab.view.state = this.input.state.topOperationsState;
|
this.topOperationsTab.view.state = this.input.state.topOperationsState;
|
||||||
this.chartTab.view.state = this.input.state.chartState;
|
this.chartTab.view.state = this.input.state.chartState;
|
||||||
@@ -278,6 +276,7 @@ export class QueryResultsView extends Disposable {
|
|||||||
clearInput() {
|
clearInput() {
|
||||||
this._input = undefined;
|
this._input = undefined;
|
||||||
this.resultsTab.clear();
|
this.resultsTab.clear();
|
||||||
|
this.messagesTab.clear();
|
||||||
this.qpTab.clear();
|
this.qpTab.clear();
|
||||||
this.topOperationsTab.clear();
|
this.topOperationsTab.clear();
|
||||||
this.chartTab.clear();
|
this.chartTab.clear();
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { EditorInput } from 'vs/workbench/common/editor';
|
import { EditorInput } from 'vs/workbench/common/editor';
|
||||||
import { Emitter } from 'vs/base/common/event';
|
import { Emitter } from 'vs/base/common/event';
|
||||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
|
||||||
|
|
||||||
import { GridPanelState } from 'sql/workbench/parts/query/electron-browser/gridPanel';
|
import { GridPanelState } from 'sql/workbench/parts/query/electron-browser/gridPanel';
|
||||||
import { MessagePanelState } from 'sql/workbench/parts/query/browser/messagePanel';
|
import { MessagePanelState } from 'sql/workbench/parts/query/browser/messagePanel';
|
||||||
@@ -16,19 +15,13 @@ import { TopOperationsState } from 'sql/workbench/parts/queryPlan/browser/topOpe
|
|||||||
|
|
||||||
export class ResultsViewState {
|
export class ResultsViewState {
|
||||||
public gridPanelState: GridPanelState = new GridPanelState();
|
public gridPanelState: GridPanelState = new GridPanelState();
|
||||||
public messagePanelState: MessagePanelState = new MessagePanelState(this.configurationService);
|
public messagePanelState: MessagePanelState = new MessagePanelState();
|
||||||
public chartState: ChartState = new ChartState();
|
public chartState: ChartState = new ChartState();
|
||||||
public queryPlanState: QueryPlanState = new QueryPlanState();
|
public queryPlanState: QueryPlanState = new QueryPlanState();
|
||||||
public topOperationsState = new TopOperationsState();
|
public topOperationsState = new TopOperationsState();
|
||||||
public gridPanelSize: number;
|
|
||||||
public messagePanelSize: number;
|
|
||||||
public activeTab: string;
|
public activeTab: string;
|
||||||
public visibleTabs: Set<string> = new Set<string>();
|
public visibleTabs: Set<string> = new Set<string>();
|
||||||
|
|
||||||
constructor(@IConfigurationService private configurationService: IConfigurationService) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this.gridPanelState.dispose();
|
this.gridPanelState.dispose();
|
||||||
this.messagePanelState.dispose();
|
this.messagePanelState.dispose();
|
||||||
@@ -56,15 +49,13 @@ export class QueryResultsInput extends EditorInput {
|
|||||||
public readonly onRestoreViewStateEmitter = new Emitter<void>();
|
public readonly onRestoreViewStateEmitter = new Emitter<void>();
|
||||||
public readonly onSaveViewStateEmitter = new Emitter<void>();
|
public readonly onSaveViewStateEmitter = new Emitter<void>();
|
||||||
|
|
||||||
private _state = new ResultsViewState(this.configurationService);
|
private _state = new ResultsViewState();
|
||||||
|
|
||||||
public get state(): ResultsViewState {
|
public get state(): ResultsViewState {
|
||||||
return this._state;
|
return this._state;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private _uri: string,
|
constructor(private _uri: string) {
|
||||||
@IConfigurationService private configurationService: IConfigurationService
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
this._visible = false;
|
this._visible = false;
|
||||||
this._hasBootstrapped = false;
|
this._hasBootstrapped = false;
|
||||||
|
|||||||
@@ -24,19 +24,17 @@ import { ITableStyles, ITableMouseEvent } from 'sql/base/browser/ui/table/interf
|
|||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
|
|
||||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
|
||||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
import { Emitter, Event } from 'vs/base/common/event';
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
|
|
||||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||||
import { range } from 'vs/base/common/arrays';
|
import { range } from 'vs/base/common/arrays';
|
||||||
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
|
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
|
||||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||||
import { generateUuid } from 'vs/base/common/uuid';
|
import { generateUuid } from 'vs/base/common/uuid';
|
||||||
import { Separator, ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
import { Separator, ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||||
import { isInDOM } from 'vs/base/browser/dom';
|
import { isInDOM, Dimension } from 'vs/base/browser/dom';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||||
@@ -60,7 +58,6 @@ const MIN_GRID_HEIGHT = (MIN_GRID_HEIGHT_ROWS * ROW_HEIGHT) + HEADER_HEIGHT + ES
|
|||||||
export class GridPanelState {
|
export class GridPanelState {
|
||||||
public tableStates: GridTableState[] = [];
|
public tableStates: GridTableState[] = [];
|
||||||
public scrollPosition: number;
|
public scrollPosition: number;
|
||||||
public collapsed = false;
|
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
dispose(this.tableStates);
|
dispose(this.tableStates);
|
||||||
@@ -115,7 +112,7 @@ export class GridTableState extends Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GridPanel extends ViewletPanel {
|
export class GridPanel {
|
||||||
private container = document.createElement('div');
|
private container = document.createElement('div');
|
||||||
private splitView: ScrollableSplitView;
|
private splitView: ScrollableSplitView;
|
||||||
private tables: GridTable<any>[] = [];
|
private tables: GridTable<any>[] = [];
|
||||||
@@ -129,42 +126,33 @@ export class GridPanel extends ViewletPanel {
|
|||||||
private _state: GridPanelState;
|
private _state: GridPanelState;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
options: IViewletPanelOptions,
|
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||||
@IKeybindingService keybindingService: IKeybindingService,
|
@IThemeService private readonly themeService: IThemeService,
|
||||||
@IContextMenuService contextMenuService: IContextMenuService,
|
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||||
@IConfigurationService configurationService: IConfigurationService,
|
@ILogService private readonly logService: ILogService
|
||||||
@IThemeService private themeService: IThemeService,
|
|
||||||
@IInstantiationService private instantiationService: IInstantiationService,
|
|
||||||
@ILogService private logService: ILogService
|
|
||||||
) {
|
) {
|
||||||
super(options, keybindingService, contextMenuService, configurationService);
|
|
||||||
this.splitView = new ScrollableSplitView(this.container, { enableResizing: false, verticalScrollbarVisibility: ScrollbarVisibility.Visible });
|
this.splitView = new ScrollableSplitView(this.container, { enableResizing: false, verticalScrollbarVisibility: ScrollbarVisibility.Visible });
|
||||||
this.splitView.onScroll(e => {
|
this.splitView.onScroll(e => {
|
||||||
if (this.state && this.splitView.length !== 0) {
|
if (this.state && this.splitView.length !== 0) {
|
||||||
this.state.scrollPosition = e;
|
this.state.scrollPosition = e;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.onDidChange(e => {
|
|
||||||
if (this.state) {
|
|
||||||
this.state.collapsed = !this.isExpanded();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected renderBody(container: HTMLElement): void {
|
public render(container: HTMLElement): void {
|
||||||
this.container.style.width = '100%';
|
this.container.style.width = '100%';
|
||||||
this.container.style.height = '100%';
|
this.container.style.height = '100%';
|
||||||
|
|
||||||
container.appendChild(this.container);
|
container.appendChild(this.container);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected layoutBody(size: number): void {
|
public layout(size: Dimension): void {
|
||||||
this.splitView.layout(size);
|
this.splitView.layout(size.height);
|
||||||
// if the size hasn't change it won't layout our table so we have to do it manually
|
// if the size hasn't change it won't layout our table so we have to do it manually
|
||||||
if (size === this.currentHeight) {
|
if (size.height === this.currentHeight) {
|
||||||
this.tables.map(e => e.layout());
|
this.tables.map(e => e.layout());
|
||||||
}
|
}
|
||||||
this.currentHeight = size;
|
this.currentHeight = size.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public set queryRunner(runner: QueryRunner) {
|
public set queryRunner(runner: QueryRunner) {
|
||||||
@@ -188,9 +176,6 @@ export class GridPanel extends ViewletPanel {
|
|||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
}, []));
|
}, []));
|
||||||
this.maximumBodySize = this.tables.reduce((p, c) => {
|
|
||||||
return p + c.maximumSize;
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
if (this.state && this.state.scrollPosition) {
|
if (this.state && this.state.scrollPosition) {
|
||||||
this.splitView.setScrollPosition(this.state.scrollPosition);
|
this.splitView.setScrollPosition(this.state.scrollPosition);
|
||||||
@@ -213,10 +198,6 @@ export class GridPanel extends ViewletPanel {
|
|||||||
t.state.canBeMaximized = this.tables.length > 1;
|
t.state.canBeMaximized = this.tables.length > 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.maximumBodySize = this.tables.reduce((p, c) => {
|
|
||||||
return p + c.maximumSize;
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
if (this.state && this.state.scrollPosition) {
|
if (this.state && this.state.scrollPosition) {
|
||||||
this.splitView.setScrollPosition(this.state.scrollPosition);
|
this.splitView.setScrollPosition(this.state.scrollPosition);
|
||||||
}
|
}
|
||||||
@@ -243,10 +224,6 @@ export class GridPanel extends ViewletPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sizeChanges = () => {
|
const sizeChanges = () => {
|
||||||
this.maximumBodySize = this.tables.reduce((p, c) => {
|
|
||||||
return p + c.maximumSize;
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
if (this.state && this.state.scrollPosition) {
|
if (this.state && this.state.scrollPosition) {
|
||||||
this.splitView.setScrollPosition(this.state.scrollPosition);
|
this.splitView.setScrollPosition(this.state.scrollPosition);
|
||||||
}
|
}
|
||||||
@@ -325,10 +302,6 @@ export class GridPanel extends ViewletPanel {
|
|||||||
this.tableDisposable = [];
|
this.tableDisposable = [];
|
||||||
this.tables = [];
|
this.tables = [];
|
||||||
this.maximizedGrid = undefined;
|
this.maximizedGrid = undefined;
|
||||||
|
|
||||||
this.maximumBodySize = this.tables.reduce((p, c) => {
|
|
||||||
return p + c.maximumSize;
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private maximizeTable(tableid: string): void {
|
private maximizeTable(tableid: string): void {
|
||||||
@@ -367,7 +340,6 @@ export class GridPanel extends ViewletPanel {
|
|||||||
t.state = state;
|
t.state = state;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.setExpanded(!this.state.collapsed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get state(): GridPanelState {
|
public get state(): GridPanelState {
|
||||||
@@ -380,7 +352,6 @@ export class GridPanel extends ViewletPanel {
|
|||||||
dispose(this.tables);
|
dispose(this.tables);
|
||||||
this.tableDisposable = undefined;
|
this.tableDisposable = undefined;
|
||||||
this.tables = undefined;
|
this.tables = undefined;
|
||||||
super.dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -816,8 +787,12 @@ class GridTable<T> extends Disposable implements IView {
|
|||||||
|
|
||||||
public dispose() {
|
public dispose() {
|
||||||
this.container.remove();
|
this.container.remove();
|
||||||
|
if (this.table) {
|
||||||
this.table.dispose();
|
this.table.dispose();
|
||||||
|
}
|
||||||
|
if (this.actionBar) {
|
||||||
this.actionBar.dispose();
|
this.actionBar.dispose();
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ suite('SQL QueryEditor Tests', () => {
|
|||||||
assert.equal(queryInput.state.connecting, false, 'query state should be not connecting');
|
assert.equal(queryInput.state.connecting, false, 'query state should be not connecting');
|
||||||
});
|
});
|
||||||
test('Test that we attempt to dispose query when the queryInput is disposed', () => {
|
test('Test that we attempt to dispose query when the queryInput is disposed', () => {
|
||||||
let queryResultsInput = new QueryResultsInput('testUri', configurationService.object);
|
let queryResultsInput = new QueryResultsInput('testUri');
|
||||||
queryInput['_results'] = queryResultsInput;
|
queryInput['_results'] = queryResultsInput;
|
||||||
queryInput.close();
|
queryInput.close();
|
||||||
queryModelService.verify(x => x.disposeQuery(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
queryModelService.verify(x => x.disposeQuery(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
||||||
|
|||||||
Reference in New Issue
Block a user