Compare commits

...

6 Commits

Author SHA1 Message Date
Karl Burtram
7099922c35 Bump SQL Ops to 0.29.3 (#1349) 2018-05-03 22:00:52 -07:00
Karl Burtram
b54b4a4445 Fix a null ref exception in dashboard layout (#1348) 2018-05-03 21:57:17 -07:00
Aditya Bist
03989a5af0 Fix scrolling in Jobs view page (#1346)
* resize when window resized

* fix scrolling issues
2018-05-03 21:39:22 -07:00
Leila Lali
1847c2e322 added checkbox component (#1330) 2018-05-03 17:11:55 -07:00
Karl Burtram
f7371e9ed5 Update SQL Agent extension readme and version (#1343) 2018-05-03 17:08:04 -07:00
Abbie Petchtes
f696274740 Add details for each chart and update README (#1341)
* add details for each chart and update readme

* update README
2018-05-03 13:19:16 -07:00
22 changed files with 334 additions and 49 deletions

View File

@@ -1,13 +1,15 @@
# Microsoft SQL Server Agent for SQL Operations Studio
Welcome to Microsoft SQL Server Agent for SQL Operations Studio! An extension for managing and troubleshooting
SQL Server Agent jobs and configuration. The current is a very early release of this extension that provides
SQL Server Agent jobs and configuration. The current is an early release of this extension that provides
basic functionality for the following.
* List SQL Server Agent Jobs configured on a SQL Server
* View Job History with job execution results
* Basic Job Control to start and stop jobs
<img src="https://user-images.githubusercontent.com/599935/39215738-d3a52580-47cd-11e8-9788-b290048c270e.png" alt="SQL Agent" style="width:800px;"/>
## Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

View File

@@ -2,7 +2,7 @@
"name": "agent",
"displayName": "SQL Server Agent",
"description": "Manage and troubleshoot SQL Server Agent jobs (early preview)",
"version": "0.28.1",
"version": "0.29.0",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt",
@@ -22,6 +22,9 @@
"type": "git",
"url": "https://github.com/Microsoft/sqlopsstudio.git"
},
"extensionDependencies": [
"Microsoft.mssql"
],
"contributes": {
"outputChannels": [
"sqlagent"

View File

@@ -1,6 +1,6 @@
{
"name": "sqlops",
"version": "0.29.2",
"version": "0.29.3",
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
"author": {
"name": "Microsoft Corporation"

View File

@@ -6,6 +6,11 @@ Welcome to **sp_whoisactive** for SQL Operations Studio! Sp_whoisactive is a pro
<img src="https://github.com/Microsoft/sqlopsstudio/raw/master/samples/sp_whoIsActive/images/insights_section.png" alt="insights" style="width:480px;"/>
Details:
<img src="https://github.com/Microsoft/sqlopsstudio/raw/master/samples/sp_whoIsActive/images/insights_details_section.png" alt="insights" style="width:240px;"/>
## Why use sp_whoisactive?
Here are some quick facts on Who is Active from [Adam Machanics blog]:
@@ -15,9 +20,6 @@ Here are some quick facts on Who is Active from [Adam Machanics blog]:
* Who is Active is **compatible with all versions of SQL Server after SQL Server 2005 RTM**. It does require that the host database (generally master) is not set for SQL Server 2000 compatibility mode
## Documentation:
<img src="https://github.com/Microsoft/sqlopsstudio/raw/master/samples/sp_whoIsActive/images/documentation_section.png" alt="documentation" style="width:480px;"/>
If you haven't installed sp_whoisactive in your server, you can use the "Install sp_whoisactive" task to create the procedure.
See [sp_whoisactive Documentation] for more infomation.
@@ -34,3 +36,8 @@ See [sp_whoisactive extension project] in the SQL Operations Studio for the exte
## Contributions and "thank you"
Special thank to Adam Machanic for partnering with us and make this sp_whoisactive extension possible.
## What's new in Server Reports v1.1?
* Changed CPU usage, CPU delta, memory usage, memory delta to show only top 10 data
* Added details option on each chart to display details of data entries
* Improved "Get plans" and "Find leader of block" tasks. The tasks will open new editor, configure current dashboard connection, and run the query.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -78,10 +78,18 @@
"legendPosition": "none",
"labelFirstColumn": false,
"columnsAsLabels": true,
"showTopNData": 5
"showTopNData": 10
}
},
"queryFile": "./out/src/sql/cpuUsage.sql"
"queryFile": "./out/src/sql/cpuUsage.sql",
"details": {
"queryFile": "./out/src/sql/cpuUsage_details.sql",
"label": {
"column": "session_id",
"state": []
},
"value": "CPU"
}
}
},
{
@@ -94,10 +102,18 @@
"legendPosition": "none",
"labelFirstColumn": false,
"columnsAsLabels": true,
"showTopNData": 5
"showTopNData": 10
}
},
"queryFile": "./out/src/sql/cpuDelta.sql"
"queryFile": "./out/src/sql/cpuDelta.sql",
"details": {
"queryFile": "./out/src/sql/cpuDelta_details.sql",
"label": {
"column": "session_id",
"state": []
},
"value": "CPU_delta"
}
}
},
{
@@ -110,10 +126,18 @@
"legendPosition": "none",
"labelFirstColumn": false,
"columnsAsLabels": true,
"showTopNData": 5
"showTopNData": 10
}
},
"queryFile": "./out/src/sql/memoryUsage.sql"
"queryFile": "./out/src/sql/memoryUsage.sql",
"details": {
"queryFile": "./out/src/sql/memoryUsage_details.sql",
"label": {
"column": "session_id",
"state": []
},
"value": "used_memory"
}
}
},
{
@@ -126,10 +150,18 @@
"legendPosition": "none",
"labelFirstColumn": false,
"columnsAsLabels": true,
"showTopNData": 5
"showTopNData": 10
}
},
"queryFile": "./out/src/sql/memoryDelta.sql"
"queryFile": "./out/src/sql/memoryDelta.sql",
"details": {
"queryFile": "./out/src/sql/memoryDelta_details.sql",
"label": {
"column": "session_id",
"state": []
},
"value": "used_memory_delta"
}
}
},
{
@@ -159,7 +191,7 @@
}
},
{
"name": "Top 5 CPU Usage",
"name": "Top 10 CPU Usage",
"gridItemConfig": {
"sizex": 2,
"sizey": 1
@@ -169,7 +201,7 @@
}
},
{
"name": "Top 5 CPU Delta",
"name": "Top 10 CPU Delta",
"gridItemConfig": {
"sizex": 2,
"sizey": 1
@@ -179,7 +211,7 @@
}
},
{
"name": "Top 5 Memory Usage",
"name": "Top 10 Memory Usage",
"gridItemConfig": {
"sizex": 2,
"sizey": 1
@@ -189,7 +221,7 @@
}
},
{
"name": "Top 5 Memory Delta",
"name": "Top 10 Memory Delta",
"gridItemConfig": {
"sizex": 2,
"sizey": 1
@@ -197,16 +229,6 @@
"widget": {
"sp_whoisactive-memory-delta": {}
}
},
{
"name": "Blocking Sessions",
"gridItemConfig": {
"sizex": 2,
"sizey": 1
},
"widget": {
"sp_whoisactive-blocking_sessions": {}
}
}
]
}

View File

@@ -0,0 +1,8 @@
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = 'sp_WhoIsActive')
EXEC sp_WhoIsActive
@delta_interval = 1,
@get_plans = 1,
@sort_order = '[CPU_delta] DESC'
ELSE
SELECT 0;
GO

View File

@@ -0,0 +1,7 @@
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = 'sp_WhoIsActive')
EXEC sp_WhoIsActive
@get_plans = 1,
@sort_order = '[CPU] DESC'
ELSE
SELECT 0;
GO

View File

@@ -0,0 +1,8 @@
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = 'sp_WhoIsActive')
EXEC sp_WhoIsActive
@delta_interval = 1,
@get_plans = 1,
@sort_order = '[used_memory_delta] DESC'
ELSE
SELECT 0;
GO

View File

@@ -0,0 +1,7 @@
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = 'sp_WhoIsActive')
EXEC sp_WhoIsActive
@get_plans = 1,
@sort_order = '[used_memory] DESC'
ELSE
SELECT 0;
GO

View File

@@ -63,6 +63,8 @@ export class DashboardHomeContainer extends DashboardWidgetContainer {
public layout() {
super.layout();
this._scrollable.layout();
if (this._scrollable) {
this._scrollable.layout();
}
}
}

View File

@@ -32,7 +32,7 @@ jobhistory-component {
}
#jobsDiv .jobview-grid {
height: 96%;
height: 94.7%;
width : 100%;
display: block;
}

View File

@@ -74,6 +74,7 @@ export class JobsViewComponent implements AfterContentChecked {
private _serverName: string;
private _isCloud: boolean;
private _showProgressWheel: boolean;
private _tabHeight: number;
constructor(
@Inject(BOOTSTRAP_SERVICE_ID) private bootstrapService: IBootstrapService,
@@ -202,9 +203,8 @@ export class JobsViewComponent implements AfterContentChecked {
this.dataView.beginUpdate();
this.dataView.setItems(jobViews);
this.dataView.endUpdate();
this._table.resizeCanvas();
this._table.autosizeColumns();
this._table.resizeCanvas();
let expandedJobs = this._agentViewComponent.expanded;
let expansions = 0;
for (let i = 0; i < jobs.length; i++){
@@ -226,8 +226,18 @@ export class JobsViewComponent implements AfterContentChecked {
});
this._showProgressWheel = false;
this._cd.detectChanges();
$(window).resize(() => {
this._table.resizeCanvas();
const self = this;
this._tabHeight = $('agentview-component #jobsDiv .jobview-grid').get(0).clientHeight;
$(window).resize((e) => {
let currentTabHeight = $('agentview-component #jobsDiv .jobview-grid').get(0).clientHeight;
if (currentTabHeight < self._tabHeight) {
$('agentview-component #jobsDiv div.ui-widget').css('height', `${currentTabHeight-22}px`);
self._table.resizeCanvas();
} else {
$('agentview-component #jobsDiv div.ui-widget').css('height', `${currentTabHeight}px`);
self._table.resizeCanvas();
}
self._tabHeight = currentTabHeight;
});
this.loadJobHistories();
}

View File

@@ -0,0 +1,108 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit
} from '@angular/core';
import * as sqlops from 'sqlops';
import Event, { Emitter } from 'vs/base/common/event';
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
import { Checkbox, ICheckboxOptions } from 'sql/base/browser/ui/checkbox/checkbox';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { attachInputBoxStyler, attachListStyler } from 'vs/platform/theme/common/styler';
@Component({
selector: 'inputBox',
template: `
<div #input style="width: 100%"></div>
`
})
export default class CheckBoxComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _input: Checkbox;
@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef) {
super(changeRef);
}
ngOnInit(): void {
this.baseInit();
}
ngAfterViewInit(): void {
if (this._inputContainer) {
let inputOptions: ICheckboxOptions = {
label: ''
};
this._input = new Checkbox(this._inputContainer.nativeElement, inputOptions);
this._register(this._input);
this._register(this._input.onChange(e => {
this.value = this._input.checked;
this._onEventEmitter.fire({
eventType: ComponentEventType.onDidChange,
args: e
});
}));
}
}
ngOnDestroy(): void {
this.baseDestroy();
}
/// IComponent implementation
public layout(): void {
this._changeRef.detectChanges();
}
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
this._input.checked = this.checked;
this._input.label = this.label;
}
// CSS-bound properties
public get checked(): boolean {
return this.getPropertyOrDefault<sqlops.CheckBoxProperties, boolean>((props) => props.value, false);
}
public set value(newValue: boolean) {
this.setPropertyFromUI<sqlops.CheckBoxProperties, boolean>(this.setInputBoxProperties, newValue);
}
private setInputBoxProperties(properties: sqlops.CheckBoxProperties, value: boolean): void {
properties.checked = value;
}
private get label(): string {
return this.getPropertyOrDefault<sqlops.CheckBoxProperties, string>((props) => props.label, '');
}
private set label(newValue: string) {
this.setPropertyFromUI<sqlops.CheckBoxProperties, string>(this.setValueProperties, newValue);
}
private setValueProperties(properties: sqlops.CheckBoxProperties, label: string): void {
properties.label = label;
}
}

View File

@@ -9,6 +9,7 @@ import CardComponent from './card.component';
import InputBoxComponent from './inputbox.component';
import DropDownComponent from './dropdown.component';
import ButtonComponent from './button.component';
import CheckBoxComponent from './checkbox.component';
import { registerComponentType } from 'sql/platform/dashboard/common/modelComponentRegistry';
import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes';
@@ -29,3 +30,7 @@ registerComponentType(DROPDOWN_COMPONENT, ModelComponentTypes.DropDown, DropDown
export const BUTTON_COMPONENT = 'button-component';
registerComponentType(BUTTON_COMPONENT, ModelComponentTypes.Button, ButtonComponent);
export const CHECKBOX_COMPONENT = 'checkbox-component';
registerComponentType(CHECKBOX_COMPONENT, ModelComponentTypes.CheckBox, CheckBoxComponent);

View File

@@ -21,6 +21,7 @@ export interface TitledFormItemLayout {
title: string;
actions?: string[];
isFormComponent: Boolean;
horizontal: boolean;
}
class FormItem {
constructor(public descriptor: IComponentDescriptor, public config: TitledFormItemLayout) { }
@@ -32,17 +33,36 @@ class FormItem {
[style.alignItems]="alignItems" [style.alignContent]="alignContent">
<div *ngFor="let item of items" class="form-row">
<ng-container *ngIf="isFormComponent(item)">
<div class="form-cell">{{getItemTitle(item)}}</div>
<div class="form-cell">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
</model-component-wrapper>
</div>
<div *ngIf="itemHasActions(item)" class="form-cell">
<div *ngFor="let actionItem of getActionComponents(item)" >
<model-component-wrapper [descriptor]="actionItem.descriptor" [modelStore]="modelStore">
<ng-container *ngIf="isHorizontal(item)">
<div class="form-cell">{{getItemTitle(item)}}</div>
<div class="form-cell">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
</model-component-wrapper>
</div>
</div>
<div *ngIf="itemHasActions(item)" class="form-cell">
<div class="form-actions-table">
<div *ngFor="let actionItem of getActionComponents(item)" class="form-cell" >
<model-component-wrapper [descriptor]="actionItem.descriptor" [modelStore]="modelStore">
</model-component-wrapper>
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="isVertical(item)">
<div class="form-item-row form-item-title">{{getItemTitle(item)}}</div>
<div class="form-item-row">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
</model-component-wrapper>
</div>
<div *ngIf="itemHasActions(item)" class="form-actions-table">
<div *ngFor="let actionItem of getActionComponents(item)" class="form-actions-cell" >
<model-component-wrapper [descriptor]="actionItem.descriptor" [modelStore]="modelStore">
</model-component-wrapper>
</div>
</div>
</ng-container>
</ng-container>
</div>
</div>
@@ -58,9 +78,9 @@ export default class FormContainer extends ContainerBase<FormItemLayout> impleme
@ViewChildren(ModelComponentWrapper) private _componentWrappers: QueryList<ModelComponentWrapper>;
@ViewChild('container', { read: ElementRef }) private _container: ElementRef;
constructor (
@Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef) {
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef) {
super(changeRef);
}
@@ -98,7 +118,7 @@ export default class FormContainer extends ContainerBase<FormItemLayout> impleme
return itemConfig ? itemConfig.title : '';
}
private getActionComponents(item: FormItem): FormItem[]{
private getActionComponents(item: FormItem): FormItem[] {
let items = this.items;
let itemConfig = item.config;
if (itemConfig && itemConfig.actions) {
@@ -125,4 +145,12 @@ export default class FormContainer extends ContainerBase<FormItemLayout> impleme
public setLayout(layout: any): void {
this.layout();
}
private isHorizontal(item: FormItem): boolean {
return item && item.config && item.config.horizontal;
}
private isVertical(item: FormItem): boolean {
return item && item.config && !item.config.horizontal;
}
}

View File

@@ -5,13 +5,35 @@
padding: 30px;
}
.form-actions-table {
display:table;
}
.form-row {
display: table-row;
width: 100px;
padding-bottom: 10px;
}
.form-item-row {
padding-bottom: 5px;
width: 300px;
}
.form-item-title {
padding-top: 20px;
}
.form-cell {
padding: 5px;
padding-top: 20px;
padding-right: 5px;
display: table-cell;
}
.form-actions-cell {
padding-top: 5px;
padding-right: 5px;
display: table-cell;
}

View File

@@ -20,6 +20,7 @@ declare module 'sqlops' {
flexContainer(): FlexBuilder;
card(): ComponentBuilder<CardComponent>;
inputBox(): ComponentBuilder<InputBoxComponent>;
checkBox(): ComponentBuilder<CheckBoxComponent>;
button(): ComponentBuilder<ButtonComponent>;
dropDown(): ComponentBuilder<DropDownComponent>;
dashboardWidget(widgetId: string): ComponentBuilder<WidgetComponent>;
@@ -144,7 +145,7 @@ declare module 'sqlops' {
}
export interface FormItemLayout {
horizontal: boolean;
}
export interface FormLayout {
@@ -188,6 +189,11 @@ declare module 'sqlops' {
value?: string;
}
export interface CheckBoxProperties {
checked?: boolean;
label?: string;
}
export interface DropDownProperties {
value?: string;
values?: string[];
@@ -208,6 +214,12 @@ declare module 'sqlops' {
onTextChanged: vscode.Event<any>;
}
export interface CheckBoxComponent extends Component {
checked: boolean;
label: string;
onChanged: vscode.Event<any>;
}
export interface DropDownComponent extends Component {
value: string;
values: string[];

View File

@@ -70,6 +70,7 @@ export enum ModelComponentTypes {
InputBox,
DropDown,
Button,
CheckBox,
DashboardWidget,
DashboardWebview,
Form

View File

@@ -48,6 +48,11 @@ class ModelBuilderImpl implements sqlops.ModelBuilder {
return this.withEventHandler(new InputBoxWrapper(this._proxy, this._handle, id), id);
}
checkBox(): sqlops.ComponentBuilder<sqlops.CheckBoxComponent> {
let id = this.getNextComponentId();
return this.withEventHandler(new CheckBoxWrapper(this._proxy, this._handle, id), id);
}
button(): sqlops.ComponentBuilder<sqlops.ButtonComponent> {
let id = this.getNextComponentId();
return this.withEventHandler(new ButtonWrapper(this._proxy, this._handle, id), id);
@@ -343,6 +348,34 @@ class InputBoxWrapper extends ComponentWrapper implements sqlops.InputBoxCompone
}
}
class CheckBoxWrapper extends ComponentWrapper implements sqlops.CheckBoxComponent {
constructor(proxy: MainThreadModelViewShape, handle: number, id: string) {
super(proxy, handle, ModelComponentTypes.CheckBox, id);
this.properties = {};
this._emitterMap.set(ComponentEventType.onDidChange, new Emitter<any>());
}
public get checked(): boolean {
return this.properties['checked'];
}
public set checked(v: boolean) {
this.setProperty('checked', v);
}
public get label(): string {
return this.properties['label'];
}
public set label(v: string) {
this.setProperty('label', v);
}
public get onChanged(): vscode.Event<any> {
let emitter = this._emitterMap.get(ComponentEventType.onDidChange);
return emitter && emitter.event;
}
}
class DropDownWrapper extends ComponentWrapper implements sqlops.DropDownComponent {
constructor(proxy: MainThreadModelViewShape, handle: number, id: string) {