From 620f4a8bfb7931271aec16ea648299b1b3c02751 Mon Sep 17 00:00:00 2001 From: Matt Irvine Date: Fri, 12 Oct 2018 16:22:36 -0700 Subject: [PATCH 01/11] Bring in tools service fix for expanding columns being slow (#2844) --- extensions/mssql/src/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/mssql/src/config.json b/extensions/mssql/src/config.json index 27028603aa..a809c622ea 100644 --- a/extensions/mssql/src/config.json +++ b/extensions/mssql/src/config.json @@ -1,6 +1,6 @@ { "downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}", - "version": "1.5.0-alpha.41", + "version": "1.5.0-alpha.43", "downloadFileNames": { "Windows_86": "win-x86-netcoreapp2.1.zip", "Windows_64": "win-x64-netcoreapp2.1.zip", From a1341ba50359bf4cf7b09175cacbd6a8ac77dabc Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Fri, 12 Oct 2018 16:39:08 -0700 Subject: [PATCH 02/11] revert row edit and dirty row fix (#2845) * revert row edit and dirty row fix * undo the dirtyRow change --- .../grid/views/editData/editData.component.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/sql/parts/grid/views/editData/editData.component.ts b/src/sql/parts/grid/views/editData/editData.component.ts index 82107bc5c2..f6b96129a7 100644 --- a/src/sql/parts/grid/views/editData/editData.component.ts +++ b/src/sql/parts/grid/views/editData/editData.component.ts @@ -192,9 +192,9 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On this.onBeforeAppendCell = (row: number, column: number): string => { let cellClass = undefined; if (this.isRowDirty(row) && column === 0) { - cellClass = ' dirtyCell '; - } else if (this.isCellDirty(row, column)) { cellClass = ' dirtyRowHeader '; + } else if (this.isCellDirty(row, column)) { + cellClass = ' dirtyCell '; } return cellClass; @@ -279,8 +279,8 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On let cellSelectTasks: Promise = this.submitCurrentCellChange( (result: EditUpdateCellResult) => { // Cell update was successful, update the flags - self.setCellDirtyState(row, self.currentCell.column, result.cell.isDirty); - self.setRowDirtyState(row, result.isRowDirty); + self.setCellDirtyState(self.currentCell.row, self.currentCell.column, result.cell.isDirty); + self.setRowDirtyState(self.currentCell.row, result.isRowDirty); return Promise.resolve(); }, (error) => { @@ -474,10 +474,11 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On // this.currentEditCellValue = undefined; this.dirtyCells = []; + let row = this.currentCell.row; this.resetCurrentCell(); - if (this.currentCell.row !== undefined) { - this.dataSet.dataRows.resetWindowsAroundIndex(this.currentCell.row); + if (row !== undefined) { + this.dataSet.dataRows.resetWindowsAroundIndex(row); } } } @@ -540,6 +541,9 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On } } else { $(grid.getCellNode(row, column)).removeClass('dirtyCell'); + if (this.dirtyCells.indexOf(column) !== -1) { + this.dirtyCells.splice(this.dirtyCells.indexOf(column), 1); + } } } From 58f950ffbde24e26691f38685a507743684b4be2 Mon Sep 17 00:00:00 2001 From: Karl Burtram Date: Fri, 12 Oct 2018 16:43:03 -0700 Subject: [PATCH 03/11] Bump version to 1.1.2 for next build. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5415c57d2f..77dd1d733b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azuredatastudio", - "version": "1.0.1", + "version": "1.1.2", "distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee", "author": { "name": "Microsoft Corporation" From c50941ac7d6241492376184326d1c419673e24e9 Mon Sep 17 00:00:00 2001 From: Karl Burtram Date: Mon, 15 Oct 2018 13:15:36 -0700 Subject: [PATCH 04/11] Update Azure Data Studio to 1.1.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 77dd1d733b..82af714725 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azuredatastudio", - "version": "1.1.2", + "version": "1.1.3", "distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee", "author": { "name": "Microsoft Corporation" From c9c8e30ced51f9af676f646dfc3b5b7d5794d29e Mon Sep 17 00:00:00 2001 From: Kevin Cunnane Date: Mon, 15 Oct 2018 13:41:54 -0700 Subject: [PATCH 05/11] Remove os.tmpDir deprecation warning (#2855) - Import extension used very old version of telemetry. Updating avoids sending deprecation warning to console which CSS requested we fix. --- extensions/import/package.json | 5 ++--- extensions/import/yarn.lock | 41 ++++++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/extensions/import/package.json b/extensions/import/package.json index 833a016b14..1c16667ad3 100644 --- a/extensions/import/package.json +++ b/extensions/import/package.json @@ -83,9 +83,8 @@ "dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.7", "opener": "^1.4.3", "service-downloader": "github:anthonydresser/service-downloader#0.1.5", - "vscode-extension-telemetry": "^0.0.5", + "vscode-extension-telemetry": "0.0.18", "vscode-nls": "^3.2.1" }, - "devDependencies": { - } + "devDependencies": {} } diff --git a/extensions/import/yarn.lock b/extensions/import/yarn.lock index 0c598b3817..07141241bc 100644 --- a/extensions/import/yarn.lock +++ b/extensions/import/yarn.lock @@ -8,9 +8,13 @@ agent-base@4, agent-base@^4.1.0: dependencies: es6-promisify "^5.0.0" -applicationinsights@0.15.6: - version "0.15.6" - resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-0.15.6.tgz#201a0682c0704fe4bdd9a92d0b2cbe34d2ae5972" +applicationinsights@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927" + dependencies: + diagnostic-channel "0.2.0" + diagnostic-channel-publishers "0.2.1" + zone.js "0.7.6" base64-js@0.0.8: version "0.0.8" @@ -120,6 +124,16 @@ decompress@^4.2.0: pify "^2.3.0" strip-dirs "^2.0.0" +diagnostic-channel-publishers@0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" + +diagnostic-channel@0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17" + dependencies: + semver "^5.3.0" + end-of-stream@^1.0.0: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" @@ -297,6 +311,10 @@ seek-bzip@^1.0.5: dependencies: commander "~2.8.1" +semver@^5.3.0: + version "5.6.0" + resolved "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + "service-downloader@github:anthonydresser/service-downloader#0.1.5": version "0.1.5" resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/6ebb0465573cc140e461a22f334260f55ef45546" @@ -357,12 +375,11 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -vscode-extension-telemetry@^0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.5.tgz#21e2abb4cbce3326e469ddbb322123b3702f3f85" +vscode-extension-telemetry@0.0.18: + version "0.0.18" + resolved "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327" dependencies: - applicationinsights "0.15.6" - winreg "0.0.13" + applicationinsights "1.0.1" vscode-jsonrpc@3.5.0: version "3.5.0" @@ -389,10 +406,6 @@ vscode-nls@^3.2.1: version "3.2.4" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" -winreg@0.0.13: - version "0.0.13" - resolved "https://registry.yarnpkg.com/winreg/-/winreg-0.0.13.tgz#76bfe02e1dd0c9c8275fb9fdf17a9f36846e3483" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -407,3 +420,7 @@ yauzl@^2.4.2: dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" + +zone.js@0.7.6: + version "0.7.6" + resolved "https://registry.npmjs.org/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009" From 1cb366d822380b7f39116d2ce05a11214b3b422f Mon Sep 17 00:00:00 2001 From: Karl Burtram Date: Mon, 15 Oct 2018 13:55:02 -0700 Subject: [PATCH 06/11] Update product name in Register Files setup checkbox (#2857) --- build/win32/code.iss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/win32/code.iss b/build/win32/code.iss index ceab15a7f7..eee7863a65 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -68,7 +68,7 @@ Type: filesandordirs; Name: "{app}\_" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce; Check: IsNotUpdate Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1 -Name: "associatewithfiles"; Description: "{cm:AssociateWithFiles,{#NameShort}}"; GroupDescription: "{cm:Other}"; Flags: unchecked +Name: "associatewithfiles"; Description: "{cm:AssociateWithFiles,{#NameLong}}"; GroupDescription: "{cm:Other}"; Flags: unchecked Name: "addtopath"; Description: "{cm:AddToPath}"; GroupDescription: "{cm:Other}" Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{cm:Other}"; Check: WizardSilent From e073b2cf4275d27fa68ed6756dffd593104cff36 Mon Sep 17 00:00:00 2001 From: Karl Burtram Date: Mon, 15 Oct 2018 16:04:36 -0700 Subject: [PATCH 07/11] Change 'Clear All' to 'Show All Connections' (#2865) --- src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts b/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts index 9c68a963c5..821e2e18e3 100644 --- a/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts +++ b/src/sql/parts/objectExplorer/viewlet/connectionTreeAction.ts @@ -218,7 +218,7 @@ export class ActiveConnectionsFilterAction extends Action { public static LABEL = localize('activeConnections', 'Show Active Connections'); private static enabledClass = 'active-connections-action'; private static disabledClass = 'icon server-page'; - private static clearAllLabel = localize('clearAll', 'Clear All'); + private static showAllConnectionsLabel = localize('showAllConnections', 'Show All Connections'); private _isSet: boolean; public static readonly ACTIVE = 'active'; public get isSet(): boolean { @@ -249,7 +249,7 @@ export class ActiveConnectionsFilterAction extends Action { // show active connections in the tree this.view.showFilteredTree(ActiveConnectionsFilterAction.ACTIVE); this.isSet = true; - this.label = ActiveConnectionsFilterAction.clearAllLabel; + this.label = ActiveConnectionsFilterAction.showAllConnectionsLabel; } else { // show full tree this.view.refreshTree(); From 225d168fdde5a072b66ed187e0a66476e5b938be Mon Sep 17 00:00:00 2001 From: Karl Burtram Date: Mon, 15 Oct 2018 16:10:00 -0700 Subject: [PATCH 08/11] Add SQL vNext to recommended extensions list (#2858) --- product.json | 1 + 1 file changed, 1 insertion(+) diff --git a/product.json b/product.json index 2aab58a012..7ef3eaaf49 100644 --- a/product.json +++ b/product.json @@ -40,6 +40,7 @@ "Microsoft.import", "Microsoft.profiler", "Microsoft.server-report", + "Microsoft.sql-vnext", "Microsoft.whoisactive", "Redgate.sql-search" ], From ac1f7542a930d7bad71f3621cbe4d6c338241a68 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Mon, 15 Oct 2018 17:18:14 -0700 Subject: [PATCH 09/11] change way we show query plan (#2866) --- src/sql/parts/query/editor/queryResultsView.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sql/parts/query/editor/queryResultsView.ts b/src/sql/parts/query/editor/queryResultsView.ts index 67eb615482..ed973a7363 100644 --- a/src/sql/parts/query/editor/queryResultsView.ts +++ b/src/sql/parts/query/editor/queryResultsView.ts @@ -203,12 +203,12 @@ export class QueryResultsView { if (!this._panelView.contains(this.qpTab)) { this._panelView.pushTab(this.qpTab); } - } else if (queryRunner.isQueryPlan) { - let disp = queryRunner.onResultSet(() => { - this.showPlan(queryRunner.planXml); - disp.dispose(); - }); } + this.runnerDisposables.push(queryRunner.onResultSet(() => { + if (queryRunner.isQueryPlan) { + this.showPlan(queryRunner.planXml); + } + })); if (this.input.state.activeTab) { this._panelView.showTab(this.input.state.activeTab); } From 425eecf6927b8d554e70c30105d988a7d94468ff Mon Sep 17 00:00:00 2001 From: Aditya Bist Date: Tue, 16 Oct 2018 13:24:43 -0700 Subject: [PATCH 10/11] Agent/edit steps (#2846) * steps can be now edited * edit jobs now works with steps, alerts, schedules etc * fixed bug when new step in new dialog would fail --- extensions/agent/src/agentUtils.ts | 1 + extensions/agent/src/data/jobStepData.ts | 126 +++++++++++++----- extensions/agent/src/dialogs/jobDialog.ts | 18 +-- extensions/agent/src/dialogs/jobStepDialog.ts | 53 ++++++-- extensions/agent/src/mainController.ts | 5 +- extensions/agent/src/test/testAgentService.ts | 2 +- extensions/mssql/src/contracts.ts | 1 + extensions/mssql/src/features.ts | 4 +- .../parts/jobManagement/common/interfaces.ts | 2 +- .../common/jobManagementService.ts | 31 ++++- .../views/jobHistory.component.ts | 11 +- .../jobManagement/views/jobManagementView.ts | 1 + .../jobManagement/views/jobsView.component.ts | 52 ++++---- src/sql/sqlops.d.ts | 19 ++- .../workbench/api/common/sqlExtHostTypes.ts | 10 ++ .../workbench/api/node/extHostDataProtocol.ts | 4 +- .../api/node/mainThreadDataProtocol.ts | 4 +- .../workbench/api/node/sqlExtHost.api.impl.ts | 1 + .../workbench/api/node/sqlExtHost.protocol.ts | 2 +- 19 files changed, 242 insertions(+), 105 deletions(-) diff --git a/extensions/agent/src/agentUtils.ts b/extensions/agent/src/agentUtils.ts index d3187ef5bb..f032ef545d 100644 --- a/extensions/agent/src/agentUtils.ts +++ b/extensions/agent/src/agentUtils.ts @@ -35,4 +35,5 @@ export class AgentUtils { } return this._queryProvider; } + } \ No newline at end of file diff --git a/extensions/agent/src/data/jobStepData.ts b/extensions/agent/src/data/jobStepData.ts index 79efe4bd39..b84c73f566 100644 --- a/extensions/agent/src/data/jobStepData.ts +++ b/extensions/agent/src/data/jobStepData.ts @@ -4,20 +4,22 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import * as sqlops from 'sqlops'; +import * as nls from 'vscode-nls'; +import * as vscode from 'vscode'; import { AgentUtils } from '../agentUtils'; import { IAgentDialogData, AgentDialogMode } from '../interfaces'; import { JobData } from './jobData'; -import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); export class JobStepData implements IAgentDialogData { // Error Messages - private readonly CreateStepErrorMessage_JobNameIsEmpty = localize('stepData.jobNameRequired', 'Job name must be provided'); - private readonly CreateStepErrorMessage_StepNameIsEmpty = localize('stepData.stepNameRequired', 'Step name must be provided'); + private static readonly CreateStepErrorMessage_JobNameIsEmpty = localize('stepData.jobNameRequired', 'Job name must be provided'); + private static readonly CreateStepErrorMessage_StepNameIsEmpty = localize('stepData.stepNameRequired', 'Step name must be provided'); - public dialogMode: AgentDialogMode = AgentDialogMode.CREATE; + public dialogMode: AgentDialogMode; public ownerUri: string; public jobId: string; public jobName: string; @@ -28,8 +30,8 @@ export class JobStepData implements IAgentDialogData { public id: number; public failureAction: string; public successAction: string; - public failStepId: number; public successStepId: number; + public failStepId: number; public command: string; public commandExecutionSuccessCode: number; public databaseName: string; @@ -43,10 +45,12 @@ export class JobStepData implements IAgentDialogData { public retryAttempts: number; public retryInterval: number; public proxyName: string; + private jobModel: JobData; constructor(ownerUri:string, jobModel?: JobData) { this.ownerUri = ownerUri; this.jobName = jobModel.name; + this.jobModel = jobModel; } public async initialize() { @@ -54,47 +58,35 @@ export class JobStepData implements IAgentDialogData { public async save() { let agentService = await AgentUtils.getAgentService(); - agentService.createJobStep(this.ownerUri, { - jobId: this.jobId, - jobName: this.jobName, - script: this.script, - scriptName: this.scriptName, - stepName: this.stepName, - subSystem: this.subSystem, - id: this.id, - failureAction: this.failureAction, - successAction: this.successAction, - failStepId: this.failStepId, - successStepId: this.successStepId, - command: this.command, - commandExecutionSuccessCode: this.commandExecutionSuccessCode, - databaseName: this.databaseName, - databaseUserName: this.databaseUserName, - server: this.server, - outputFileName: this.outputFileName, - appendToLogFile: this.appendToLogFile, - appendToStepHist: this.appendToStepHist, - writeLogToTable: this.writeLogToTable, - appendLogToTable: this.appendLogToTable, - retryAttempts: this.retryAttempts, - retryInterval: this.retryInterval, - proxyName: this.proxyName - }).then(result => { - if (result && result.success) { - console.info(result); + let result: any; + if (this.dialogMode === AgentDialogMode.CREATE) { + if (this.jobModel && this.jobModel.dialogMode === AgentDialogMode.CREATE) { + // create job -> create step + Promise.resolve(this); + return; + } else { + // edit job -> create step + result = await agentService.createJobStep(this.ownerUri, JobStepData.convertToAgentJobStepInfo(this)); } - }); + } else if (this.jobModel && this.jobModel.dialogMode === AgentDialogMode.EDIT) { + // edit job -> edit step + result = await agentService.updateJobStep(this.ownerUri, this.stepName, JobStepData.convertToAgentJobStepInfo(this)); + } + if (!result || !result.success) { + vscode.window.showErrorMessage( + localize('jobStepData.saveErrorMessage', "Step update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown')); + } } public validate(): { valid: boolean, errorMessages: string[] } { let validationErrors: string[] = []; if (!(this.stepName && this.stepName.trim())) { - validationErrors.push(this.CreateStepErrorMessage_StepNameIsEmpty); + validationErrors.push(JobStepData.CreateStepErrorMessage_StepNameIsEmpty); } if (!(this.jobName && this.jobName.trim())) { - validationErrors.push(this.CreateStepErrorMessage_JobNameIsEmpty); + validationErrors.push(JobStepData.CreateStepErrorMessage_JobNameIsEmpty); } return { @@ -102,4 +94,66 @@ export class JobStepData implements IAgentDialogData { errorMessages: validationErrors }; } + + public static convertToJobStepData(jobStepInfo: sqlops.AgentJobStepInfo, jobData: JobData) { + let stepData = new JobStepData(jobData.ownerUri, jobData); + stepData.ownerUri = jobData.ownerUri; + stepData.jobId = jobStepInfo.jobId; + stepData.jobName = jobStepInfo.jobName; + stepData.script = jobStepInfo.script; + stepData.scriptName = jobStepInfo.scriptName, + stepData.stepName = jobStepInfo.stepName, + stepData.subSystem = jobStepInfo.subSystem, + stepData.id = jobStepInfo.id, + stepData.failureAction = jobStepInfo.failureAction, + stepData.successAction = jobStepInfo.successAction, + stepData.failStepId = jobStepInfo.failStepId, + stepData.successStepId = jobStepInfo.successStepId, + stepData.command = jobStepInfo.command, + stepData.commandExecutionSuccessCode = jobStepInfo.commandExecutionSuccessCode, + stepData.databaseName = jobStepInfo.databaseName, + stepData.databaseUserName = jobStepInfo.databaseUserName, + stepData.server = jobStepInfo.server, + stepData.outputFileName = jobStepInfo.outputFileName, + stepData.appendToLogFile = jobStepInfo.appendToLogFile, + stepData.appendToStepHist = jobStepInfo.appendToStepHist, + stepData.writeLogToTable = jobStepInfo.writeLogToTable, + stepData.appendLogToTable = jobStepInfo.appendLogToTable, + stepData.retryAttempts = jobStepInfo.retryAttempts, + stepData.retryInterval = jobStepInfo.retryInterval, + stepData.proxyName = jobStepInfo.proxyName; + stepData.dialogMode = AgentDialogMode.EDIT; + return stepData; + } + + public static convertToAgentJobStepInfo(jobStepData: JobStepData): sqlops.AgentJobStepInfo { + let result: sqlops.AgentJobStepInfo = { + jobId: jobStepData.jobId, + jobName: jobStepData.jobName, + script: jobStepData.script, + scriptName: jobStepData.scriptName, + stepName: jobStepData.stepName, + subSystem: jobStepData.subSystem, + id: jobStepData.id, + failureAction: jobStepData.failureAction, + successAction: jobStepData.successAction, + failStepId: jobStepData.failStepId, + successStepId: jobStepData.successStepId, + command: jobStepData.command, + commandExecutionSuccessCode: jobStepData.commandExecutionSuccessCode, + databaseName: jobStepData.databaseName, + databaseUserName: jobStepData.databaseUserName, + server: jobStepData.server, + outputFileName: jobStepData.outputFileName, + appendToLogFile: jobStepData.appendToLogFile, + appendToStepHist: jobStepData.appendToStepHist, + writeLogToTable: jobStepData.writeLogToTable, + appendLogToTable: jobStepData.appendLogToTable, + retryAttempts: jobStepData.retryAttempts, + retryInterval: jobStepData.retryInterval, + proxyName: jobStepData.proxyName + }; + return result; + } + } \ No newline at end of file diff --git a/extensions/agent/src/dialogs/jobDialog.ts b/extensions/agent/src/dialogs/jobDialog.ts index 8bd76509ee..e77baa2261 100644 --- a/extensions/agent/src/dialogs/jobDialog.ts +++ b/extensions/agent/src/dialogs/jobDialog.ts @@ -235,13 +235,17 @@ export class JobDialog extends AgentDialog { width: 80 }).component(); - let stepDialog = new JobStepDialog(this.model.ownerUri, '' , data.length + 1, this.model); + let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model); stepDialog.onSuccess((step) => { + if (!this.model.jobSteps) { + this.model.jobSteps = []; + } this.model.jobSteps.push(step); this.stepsTable.data = this.convertStepsToData(this.model.jobSteps); }); this.newStepButton.onDidClick((e)=>{ if (this.nameTextBox.value && this.nameTextBox.value.length > 0) { + stepDialog.jobName = this.nameTextBox.value; stepDialog.openDialog(); } else { this.dialog.message = { text: this.BlankJobNameErrorText }; @@ -267,17 +271,15 @@ export class JobDialog extends AgentDialog { // one step selection if (this.stepsTable.selectedRows.length === 1) { let rowNumber = this.stepsTable.selectedRows[0]; - let stepData = steps[rowNumber]; + let stepData = this.model.jobSteps[rowNumber]; this.deleteStepButton.enabled = true; this.editStepButton.enabled = true; - this.editStepButton.onDidClick((e) => { - // implement edit steps - - // let stepDialog = new JobStepDialog(this.model.ownerUri, this.nameTextBox.value, '' , 1, this.model); - // stepDialog.openNewStepDialog(); + this.editStepButton.onDidClick(() => { + let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model, stepData); + stepDialog.openDialog(); }); - this.deleteStepButton.onDidClick((e) => { + this.deleteStepButton.onDidClick(() => { AgentUtils.getAgentService().then((agentService) => { let steps = this.model.jobSteps ? this.model.jobSteps : []; agentService.deleteJobStep(this.ownerUri, stepData).then((result) => { diff --git a/extensions/agent/src/dialogs/jobStepDialog.ts b/extensions/agent/src/dialogs/jobStepDialog.ts index 737851d448..da0ec4028b 100644 --- a/extensions/agent/src/dialogs/jobStepDialog.ts +++ b/extensions/agent/src/dialogs/jobStepDialog.ts @@ -10,6 +10,7 @@ import { JobStepData } from '../data/jobStepData'; import { AgentUtils } from '../agentUtils'; import { JobData } from '../data/jobData'; import { AgentDialog } from './agentDialog'; +import { AgentDialogMode } from '../interfaces'; const path = require('path'); const localize = nls.loadMessageBundle(); @@ -19,7 +20,8 @@ export class JobStepDialog extends AgentDialog { // TODO: localize // Top level // - private readonly DialogTitle: string = localize('jobStepDialog.newJobStep', 'New Job Step'); + private static readonly NewDialogTitle: string = localize('jobStepDialog.newJobStep', 'New Job Step'); + private static readonly EditDialogTitle: string = localize('jobStepDialog.editJobStep', 'Edit Job Step'); private readonly FileBrowserDialogTitle: string = localize('jobStepDialog.fileBrowserTitle', 'Locate Database Files - '); private readonly OkButtonText: string = localize('jobStepDialog.ok', 'OK'); private readonly CancelButtonText: string = localize('jobStepDialog.cancel', 'Cancel'); @@ -102,24 +104,32 @@ export class JobStepDialog extends AgentDialog { // Checkbox private appendToExistingFileCheckbox: sqlops.CheckBoxComponent; private logToTableCheckbox: sqlops.CheckBoxComponent; + private logStepOutputHistoryCheckbox: sqlops.CheckBoxComponent; private fileBrowserTree: sqlops.FileBrowserTreeComponent; private jobModel: JobData; - private jobName: string; + public jobName: string; private server: string; private stepId: number; + private isEdit: boolean; constructor( ownerUri: string, server: string, - stepId: number, - jobModel?: JobData + jobModel: JobData, + jobStepInfo?: sqlops.AgentJobStepInfo, ) { - super(ownerUri, new JobStepData(ownerUri, jobModel), 'New Step'); - this.stepId = stepId; - this.jobName = jobModel.name; - this.server = server; + super(ownerUri, + jobStepInfo ? JobStepData.convertToJobStepData(jobStepInfo, jobModel) : new JobStepData(ownerUri, jobModel), + jobStepInfo ? JobStepDialog.EditDialogTitle : JobStepDialog.NewDialogTitle); + this.stepId = jobStepInfo ? + jobStepInfo.id : jobModel.jobSteps ? + jobModel.jobSteps.length + 1 : 1; + this.isEdit = jobStepInfo ? true : false; + this.model.dialogMode = this.isEdit ? AgentDialogMode.EDIT : AgentDialogMode.CREATE; this.jobModel = jobModel; + this.jobName = this.jobName ? this.jobName : this.jobModel.name; + this.server = server; } private initializeUIComponents() { @@ -254,6 +264,14 @@ export class JobStepDialog extends AgentDialog { let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component(); formWrapper.loading = false; await view.initializeModel(formWrapper); + + // Load values for edit scenario + if (this.isEdit) { + this.nameTextBox.value = this.model.stepName; + this.typeDropdown.value = this.model.subSystem; + this.databaseDropdown.value = this.model.databaseName; + this.commandTextBox.value = this.model.command; + } }); } @@ -290,7 +308,7 @@ export class JobStepDialog extends AgentDialog { let logToTableContainer = view.modelBuilder.flexContainer() .withLayout({ flexFlow: 'row', justifyContent: 'space-between', width: 300 }) .withItems([this.logToTableCheckbox]).component(); - let logStepOutputHistoryCheckbox = view.modelBuilder.checkBox() + this.logStepOutputHistoryCheckbox = view.modelBuilder.checkBox() .withProperties({ label: this.IncludeStepOutputHistoryLabel }).component(); this.userInputBox = view.modelBuilder.inputBox() .withProperties({ inputType: 'text', width: '100%' }).component(); @@ -315,7 +333,7 @@ export class JobStepDialog extends AgentDialog { component: appendCheckboxContainer, title: ' ' }, { - component: logStepOutputHistoryCheckbox, + component: this.logStepOutputHistoryCheckbox, title: '' }, { component: this.userInputBox, @@ -326,7 +344,19 @@ export class JobStepDialog extends AgentDialog { let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component(); formWrapper.loading = false; - view.initializeModel(formWrapper); + await view.initializeModel(formWrapper); + + if (this.isEdit) { + this.successActionDropdown.value = this.model.successAction; + this.retryAttemptsBox.value = this.model.retryAttempts.toString(); + this.retryIntervalBox.value = this.model.retryInterval.toString(); + this.failureActionDropdown.value = this.model.failureAction; + this.outputFileNameBox.value = this.model.outputFileName; + this.appendToExistingFileCheckbox.checked = this.model.appendToLogFile; + this.logToTableCheckbox.checked = this.model.appendLogToTable; + this.logStepOutputHistoryCheckbox.checked = this.model.appendToStepHist; + this.userInputBox.value = this.model.databaseUserName; + } }); } @@ -479,7 +509,6 @@ export class JobStepDialog extends AgentDialog { this.model.jobName = this.jobName; this.model.id = this.stepId; this.model.server = this.server; - this.model.stepName = this.nameTextBox.value; this.model.subSystem = this.typeDropdown.value as string; this.model.databaseName = this.databaseDropdown.value as string; this.model.script = this.commandTextBox.value; diff --git a/extensions/agent/src/mainController.ts b/extensions/agent/src/mainController.ts index 2797e4512c..d7f6b3fde4 100644 --- a/extensions/agent/src/mainController.ts +++ b/extensions/agent/src/mainController.ts @@ -13,6 +13,7 @@ import { OperatorDialog } from './dialogs/operatorDialog'; import { ProxyDialog } from './dialogs/proxyDialog'; import { JobStepDialog } from './dialogs/jobStepDialog'; import { PickScheduleDialog } from './dialogs/pickScheduleDialog'; +import { JobData } from './data/jobData'; const localize = nls.loadMessageBundle(); @@ -40,8 +41,8 @@ export class MainController { let dialog = new JobDialog(ownerUri, jobInfo); dialog.openDialog(); }); - vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, server: string, stepId: number) => { - let dialog = new JobStepDialog(ownerUri, server, stepId); + vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, server: string, jobData: JobData) => { + let dialog = new JobStepDialog(ownerUri, server, jobData); dialog.openDialog(); }); vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string) => { diff --git a/extensions/agent/src/test/testAgentService.ts b/extensions/agent/src/test/testAgentService.ts index 443fff76ce..ae68cf12bf 100644 --- a/extensions/agent/src/test/testAgentService.ts +++ b/extensions/agent/src/test/testAgentService.ts @@ -14,7 +14,7 @@ export class TestAgentService implements sqlops.AgentServicesProvider { getJobs(ownerUri: string): Thenable { return undefined; } - getJobHistory(ownerUri: string, jobId: string): Thenable { + getJobHistory(ownerUri: string, jobId: string, jobName: string): Thenable { return undefined; } jobAction(ownerUri: string, jobName: string, action: string): Thenable { diff --git a/extensions/mssql/src/contracts.ts b/extensions/mssql/src/contracts.ts index eab6339bc3..4b2b7bba86 100644 --- a/extensions/mssql/src/contracts.ts +++ b/extensions/mssql/src/contracts.ts @@ -40,6 +40,7 @@ export interface AgentJobsParams { export interface AgentJobHistoryParams { ownerUri: string; jobId: string; + jobName: string; } export interface AgentJobActionParams { diff --git a/extensions/mssql/src/features.ts b/extensions/mssql/src/features.ts index 57db9f1f33..a108d16cc0 100644 --- a/extensions/mssql/src/features.ts +++ b/extensions/mssql/src/features.ts @@ -80,8 +80,8 @@ export class AgentServicesFeature extends SqlOpsFeature { ); }; - let getJobHistory = (ownerUri: string, jobID: string): Thenable => { - let params: contracts.AgentJobHistoryParams = { ownerUri: ownerUri, jobId: jobID }; + let getJobHistory = (ownerUri: string, jobID: string, jobName: string): Thenable => { + let params: contracts.AgentJobHistoryParams = { ownerUri: ownerUri, jobId: jobID, jobName: jobName }; return client.sendRequest(contracts.AgentJobHistoryRequest.type, params).then( r => r, diff --git a/src/sql/parts/jobManagement/common/interfaces.ts b/src/sql/parts/jobManagement/common/interfaces.ts index 2074d9454a..19fbbe2088 100644 --- a/src/sql/parts/jobManagement/common/interfaces.ts +++ b/src/sql/parts/jobManagement/common/interfaces.ts @@ -22,7 +22,7 @@ export interface IJobManagementService { fireOnDidChange(): void; getJobs(connectionUri: string): Thenable; - getJobHistory(connectionUri: string, jobID: string): Thenable; + getJobHistory(connectionUri: string, jobID: string, jobName: string): Thenable; deleteJob(connectionUri: string, job: sqlops.AgentJobInfo): Thenable; deleteJobStep(connectionUri: string, step: sqlops.AgentJobStepInfo): Thenable; diff --git a/src/sql/parts/jobManagement/common/jobManagementService.ts b/src/sql/parts/jobManagement/common/jobManagementService.ts index eaa4ceea2e..cb8de9e61c 100644 --- a/src/sql/parts/jobManagement/common/jobManagementService.ts +++ b/src/sql/parts/jobManagement/common/jobManagementService.ts @@ -43,9 +43,9 @@ export class JobManagementService implements IJobManagementService { }); } - public getJobHistory(connectionUri: string, jobID: string): Thenable { + public getJobHistory(connectionUri: string, jobID: string, jobName: string): Thenable { return this._runAction(connectionUri, (runner) => { - return runner.getJobHistory(connectionUri, jobID); + return runner.getJobHistory(connectionUri, jobID, jobName); }); } @@ -142,6 +142,9 @@ export class JobCacheObject { _serviceBrand: any; private _jobs: sqlops.AgentJobInfo[] = []; private _jobHistories: { [jobID: string]: sqlops.AgentJobHistoryInfo[]; } = {}; + private _jobSteps: { [jobID: string]: sqlops.AgentJobStepInfo[]; } = {}; + private _jobAlerts: { [jobID: string]: sqlops.AgentAlertInfo[]; } = {}; + private _jobSchedules: { [jobID: string]: sqlops.AgentJobScheduleInfo[]; } = {}; private _runCharts: { [jobID: string]: string[]; } = {}; private _prevJobID: string; private _serverName: string; @@ -176,6 +179,18 @@ export class JobCacheObject { return this._runCharts[jobID]; } + public getJobSteps(jobID: string): sqlops.AgentJobStepInfo[] { + return this._jobSteps[jobID]; + } + + public getJobAlerts(jobID: string): sqlops.AgentAlertInfo[] { + return this._jobAlerts[jobID]; + } + + public getJobSchedules(jobID: string): sqlops.AgentJobScheduleInfo[] { + return this._jobSchedules[jobID]; + } + /* Setters */ public set jobs(value: sqlops.AgentJobInfo[]) { this._jobs = value; @@ -204,4 +219,16 @@ export class JobCacheObject { public set dataView(value: Slick.Data.DataView) { this._dataView = value; } + + public setJobSteps(jobID: string, value: sqlops.AgentJobStepInfo[]) { + this._jobSteps[jobID] = value; + } + + public setJobAlerts(jobID: string, value: sqlops.AgentAlertInfo[]) { + this._jobAlerts[jobID] = value; + } + + public setJobSchedules(jobID: string, value: sqlops.AgentJobScheduleInfo[]) { + this._jobSchedules[jobID] = value; + } } \ No newline at end of file diff --git a/src/sql/parts/jobManagement/views/jobHistory.component.ts b/src/sql/parts/jobManagement/views/jobHistory.component.ts index 7fe8d1e5ab..57662d387e 100644 --- a/src/sql/parts/jobManagement/views/jobHistory.component.ts +++ b/src/sql/parts/jobManagement/views/jobHistory.component.ts @@ -150,16 +150,17 @@ export class JobHistoryComponent extends JobManagementView implements OnInit { private loadHistory() { const self = this; let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri; - this._jobManagementService.getJobHistory(ownerUri, this._agentViewComponent.jobId).then((result) => { - if (result && result.jobs) { - if (result.jobs.length > 0) { + let jobName = this._agentViewComponent.agentJobInfo.name; + this._jobManagementService.getJobHistory(ownerUri, this._agentViewComponent.jobId, jobName).then((result) => { + if (result && result.histories) { + if (result.histories.length > 0) { self._showPreviousRuns = true; - self.buildHistoryTree(self, result.jobs); + self.buildHistoryTree(self, result.histories); if (self._agentViewComponent.showHistory) { self._cd.detectChanges(); } } else { - self._jobCacheObject.setJobHistory(self._agentViewComponent.jobId, result.jobs); + self._jobCacheObject.setJobHistory(self._agentViewComponent.jobId, result.histories); self._showPreviousRuns = false; } } else { diff --git a/src/sql/parts/jobManagement/views/jobManagementView.ts b/src/sql/parts/jobManagement/views/jobManagementView.ts index 8277ac3b50..e1b04e2b08 100644 --- a/src/sql/parts/jobManagement/views/jobManagementView.ts +++ b/src/sql/parts/jobManagement/views/jobManagementView.ts @@ -53,6 +53,7 @@ export abstract class JobManagementView extends TabChild implements AfterContent if (!this.isInitialized) { this._showProgressWheel = true; this.onFirstVisible(); + this.layout(); this.isInitialized = true; } } else if (this.isVisible === true && this._parentComponent.refresh === true) { diff --git a/src/sql/parts/jobManagement/views/jobsView.component.ts b/src/sql/parts/jobManagement/views/jobsView.component.ts index a6646460ca..eb96c1830e 100644 --- a/src/sql/parts/jobManagement/views/jobsView.component.ts +++ b/src/sql/parts/jobManagement/views/jobsView.component.ts @@ -36,8 +36,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IDashboardService } from 'sql/services/dashboard/common/dashboardService'; import { escape } from 'sql/base/common/strings'; import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { tableBackground, cellBackground, tableHoverBackground, jobsHeadingBackground, cellBorderColor } from 'sql/common/theme/colors'; -import { JobStepsViewRow } from 'sql/parts/jobManagement/views/jobStepsViewTree'; +import { tableBackground, cellBackground, cellBorderColor } from 'sql/common/theme/colors'; export const JOBSVIEW_SELECTOR: string = 'jobsview-component'; export const ROW_HEIGHT: number = 45; @@ -87,7 +86,10 @@ export class JobsViewComponent extends JobManagementView implements OnInit { private sortingStylingMap: { [columnName: string]: any; } = {}; public jobs: sqlops.AgentJobInfo[]; - public jobHistories: { [jobId: string]: sqlops.AgentJobHistoryInfo[]; } = Object.create(null); + private jobHistories: { [jobId: string]: sqlops.AgentJobHistoryInfo[]; } = Object.create(null); + private jobSteps: { [jobId: string]: sqlops.AgentJobStepInfo[]; } = Object.create(null); + private jobAlerts: { [jobId: string]: sqlops.AgentAlertInfo[]; } = Object.create(null); + private jobSchedules: { [jobId: string]: sqlops.AgentJobScheduleInfo[]; } = Object.create(null); public contextAction = NewJobAction; @ViewChild('jobsgrid') _gridEl: ElementRef; @@ -579,10 +581,14 @@ export class JobsViewComponent extends JobManagementView implements OnInit { private async curateJobHistory(jobs: sqlops.AgentJobInfo[], ownerUri: string) { const self = this; jobs.forEach(async (job) => { - await this._jobManagementService.getJobHistory(ownerUri, job.jobId).then((result) => { - if (result && result.jobs) { - self.jobHistories[job.jobId] = result.jobs; - self._jobCacheObject.setJobHistory(job.jobId, result.jobs); + await this._jobManagementService.getJobHistory(ownerUri, job.jobId, job.name).then((result) => { + if (result) { + self.jobSteps[job.jobId] = result.steps ? result.steps : []; + self.jobAlerts[job.jobId] = result.alerts ? result.alerts : []; + self.jobSchedules[job.jobId] = result.schedules ? result.schedules : []; + self.jobHistories[job.jobId] = result.histories ? result.histories : []; + self._jobCacheObject.setJobSteps(job.jobId, self.jobSteps[job.jobId]); + self._jobCacheObject.setJobHistory(job.jobId, self.jobHistories[job.jobId]); let jobHistories = self._jobCacheObject.getJobHistory(job.jobId); let previousRuns: sqlops.AgentJobHistoryInfo[]; if (jobHistories.length >= 5) { @@ -592,7 +598,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit { } self.createJobChart(job.jobId, previousRuns); if (self._agentViewComponent.expanded.has(job.jobId)) { - let lastJobHistory = jobHistories[result.jobs.length - 1]; + let lastJobHistory = jobHistories[jobHistories.length - 1]; let item = self.dataView.getItemById(job.jobId + '.error'); let noStepsMessage = nls.localize('jobsView.noSteps', 'No Steps available for this job.'); let errorMessage = lastJobHistory ? lastJobHistory.message : noStepsMessage; @@ -909,29 +915,22 @@ export class JobsViewComponent extends JobManagementView implements OnInit { jobId = data.getItem(rowIndex - 1).jobId; } } + let job: sqlops.AgentJobInfo[] = this.jobs.filter(job => { return job.jobId === jobId; }); - let jobHistories = this.jobHistories[jobId]; - let steps: sqlops.AgentJobStep[] = undefined; - let schedules: sqlops.AgentJobScheduleInfo[] = undefined; - let alerts: sqlops.AgentAlertInfo[] = undefined; + + // add steps + if (this.jobSteps && this.jobSteps[jobId]) { + let steps = this.jobSteps[jobId]; + job[0].JobSteps = steps; + } + let jobHistories = this.jobHistories[job[0].jobId]; + let schedules: sqlops.AgentJobScheduleInfo[] = this.jobSchedules[job[0].jobId]; + let alerts: sqlops.AgentAlertInfo[] = this.jobAlerts[job[0].jobId]; if (jobHistories && jobHistories[jobHistories.length-1]) { - // add steps - steps = jobHistories[jobHistories.length-1].steps; - if (steps && steps.length > 0) { - if (!job[0].JobSteps) { - job[0].JobSteps = []; - } - if (job[0].JobSteps.length !== steps.length) { - job[0].JobSteps = []; - steps.forEach(step => { - job[0].JobSteps.push(step.stepDetails); - }); - } - } + // add schedules - schedules = jobHistories[jobHistories.length-1].schedules; if (schedules && schedules.length > 0) { if (!job[0].JobSchedules) { job[0].JobSchedules = []; @@ -944,7 +943,6 @@ export class JobsViewComponent extends JobManagementView implements OnInit { } } // add alerts - alerts = jobHistories[jobHistories.length-1].alerts; if (!job[0].Alerts) { job[0].Alerts = []; } diff --git a/src/sql/sqlops.d.ts b/src/sql/sqlops.d.ts index a34e97b8dc..1726ac7bad 100644 --- a/src/sql/sqlops.d.ts +++ b/src/sql/sqlops.d.ts @@ -1266,6 +1266,16 @@ declare module 'sqlops' { Last = 16 } + export enum JobExecutionStatus { + Executing = 1, + WaitingForWorkerThread = 2, + BetweenRetries = 3, + Idle = 4, + Suspended = 5, + WaitingForStepToFinish = 6, + PerformingCompletionAction = 7 + } + export interface AgentJobInfo { name: string; owner: string; @@ -1371,8 +1381,6 @@ declare module 'sqlops' { retriesAttempted: string; server: string; steps: AgentJobStep[]; - schedules: AgentJobScheduleInfo[]; - alerts: AgentAlertInfo[]; } export interface AgentProxyInfo { @@ -1441,7 +1449,10 @@ declare module 'sqlops' { } export interface AgentJobHistoryResult extends ResultStatus { - jobs: AgentJobHistoryInfo[]; + histories: AgentJobHistoryInfo[]; + steps: AgentJobStepInfo[]; + schedules: AgentJobScheduleInfo[]; + alerts: AgentAlertInfo[]; } export interface CreateAgentJobResult extends ResultStatus { @@ -1529,7 +1540,7 @@ declare module 'sqlops' { export interface AgentServicesProvider extends DataProvider { // Job management methods getJobs(ownerUri: string): Thenable; - getJobHistory(ownerUri: string, jobId: string): Thenable; + getJobHistory(ownerUri: string, jobId: string, jobName: string): Thenable; jobAction(ownerUri: string, jobName: string, action: string): Thenable; createJob(ownerUri: string, jobInfo: AgentJobInfo): Thenable; updateJob(ownerUri: string, originalJobName: string, jobInfo: AgentJobInfo): Thenable; diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts index 4fc3230da0..a02bbe84b4 100644 --- a/src/sql/workbench/api/common/sqlExtHostTypes.ts +++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts @@ -95,6 +95,16 @@ export enum JobCompletionActionCondition { Always = 3 } +export enum JobExecutionStatus { + Executing = 1, + WaitingForWorkerThread = 2, + BetweenRetries = 3, + Idle = 4, + Suspended = 5, + WaitingForStepToFinish = 6, + PerformingCompletionAction = 7 +} + export enum AlertType { sqlServerEvent = 1, sqlServerPerformanceCondition = 2, diff --git a/src/sql/workbench/api/node/extHostDataProtocol.ts b/src/sql/workbench/api/node/extHostDataProtocol.ts index 0d430c383e..2e83aad1f3 100644 --- a/src/sql/workbench/api/node/extHostDataProtocol.ts +++ b/src/sql/workbench/api/node/extHostDataProtocol.ts @@ -578,8 +578,8 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape { /** * Get a Agent Job's history */ - public $getJobHistory(handle: number, ownerUri: string, jobID: string): Thenable { - return this._resolveProvider(handle).getJobHistory(ownerUri, jobID); + public $getJobHistory(handle: number, ownerUri: string, jobID: string, jobName: string): Thenable { + return this._resolveProvider(handle).getJobHistory(ownerUri, jobID, jobName); } /** diff --git a/src/sql/workbench/api/node/mainThreadDataProtocol.ts b/src/sql/workbench/api/node/mainThreadDataProtocol.ts index bed01823a2..1c76fc25a6 100644 --- a/src/sql/workbench/api/node/mainThreadDataProtocol.ts +++ b/src/sql/workbench/api/node/mainThreadDataProtocol.ts @@ -350,8 +350,8 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape { getJobs(connectionUri: string): Thenable { return self._proxy.$getJobs(handle, connectionUri); }, - getJobHistory(connectionUri: string, jobID: string): Thenable { - return self._proxy.$getJobHistory(handle, connectionUri, jobID); + getJobHistory(connectionUri: string, jobID: string, jobName: string): Thenable { + return self._proxy.$getJobHistory(handle, connectionUri, jobID, jobName); }, jobAction(connectionUri: string, jobName: string, action: string): Thenable { return self._proxy.$jobAction(handle, connectionUri, jobName, action); diff --git a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts index efdd7e06a0..3c99d11f94 100644 --- a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts @@ -422,6 +422,7 @@ export function createApiFactory( WeekDays: sqlExtHostTypes.WeekDays, NotifyMethods: sqlExtHostTypes.NotifyMethods, JobCompletionActionCondition: sqlExtHostTypes.JobCompletionActionCondition, + JobExecutionStatus: sqlExtHostTypes.JobExecutionStatus, AlertType: sqlExtHostTypes.AlertType, FrequencyTypes: sqlExtHostTypes.FrequencyTypes, FrequencySubDayTypes: sqlExtHostTypes.FrequencySubDayTypes, diff --git a/src/sql/workbench/api/node/sqlExtHost.protocol.ts b/src/sql/workbench/api/node/sqlExtHost.protocol.ts index 7b7d94bda3..6694a5e502 100644 --- a/src/sql/workbench/api/node/sqlExtHost.protocol.ts +++ b/src/sql/workbench/api/node/sqlExtHost.protocol.ts @@ -358,7 +358,7 @@ export abstract class ExtHostDataProtocolShape { /** * Get a Agent Job's history */ - $getJobHistory(handle: number, ownerUri: string, jobID: string): Thenable { throw ni(); } + $getJobHistory(handle: number, ownerUri: string, jobID: string, jobName: string): Thenable { throw ni(); } /** * Run an action on a Job From bfa9e8c495d22ac44a284eb4db5f63afce981a69 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Tue, 16 Oct 2018 16:15:47 -0700 Subject: [PATCH 11/11] Handle query plan flow problems (#2918) * modify the query plan work flow to account for some errors * formatting --- src/sql/base/browser/ui/panel/panel.ts | 108 +++++++++--------- .../parts/query/editor/queryResultsView.ts | 8 +- src/sql/parts/query/execution/queryRunner.ts | 9 +- 3 files changed, 63 insertions(+), 62 deletions(-) diff --git a/src/sql/base/browser/ui/panel/panel.ts b/src/sql/base/browser/ui/panel/panel.ts index 73d8af93fc..aa5b608994 100644 --- a/src/sql/base/browser/ui/panel/panel.ts +++ b/src/sql/base/browser/ui/panel/panel.ts @@ -5,8 +5,8 @@ import { IThemable } from 'vs/platform/theme/common/styler'; import { Event, Emitter } from 'vs/base/common/event'; -import { Dimension, EventType } from 'vs/base/browser/dom'; -import { $, Builder } from 'vs/base/browser/builder'; +import { Dimension, EventType, $, addDisposableListener } from 'vs/base/browser/dom'; +import { $ as quickBuilder } from 'vs/base/browser/builder'; import { IAction } from 'vs/base/common/actions'; import { IActionOptions, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -34,8 +34,8 @@ export interface IPanelTab { } interface IInternalPanelTab extends IPanelTab { - header: Builder; - label: Builder; + header: HTMLElement; + label: HTMLElement; dispose(): void; } @@ -49,10 +49,10 @@ export class TabbedPanel extends Disposable implements IThemable { private _tabMap = new Map(); private _shownTab: PanelTabIdentifier; public readonly headersize = 35; - private $header: Builder; - private $tabList: Builder; - private $body: Builder; - private $parent: Builder; + private header: HTMLElement; + private tabList: HTMLElement; + private body: HTMLElement; + private parent: HTMLElement; private _actionbar: ActionBar; private _currentDimensions: Dimension; private _collapsed = false; @@ -65,26 +65,26 @@ export class TabbedPanel extends Disposable implements IThemable { constructor(private container: HTMLElement, private options: IPanelOptions = defaultOptions) { super(); - this.$parent = this._register($('.tabbedPanel')); - this.$parent.appendTo(container); - this.$header = $('.composite.title'); - this.$tabList = $('.tabList'); - this.$tabList.attr('role', 'tablist'); - this.$tabList.style('height', this.headersize + 'px'); - this.$header.append(this.$tabList); + this.parent = $('.tabbedPanel'); + container.appendChild(this.parent); + this.header = $('.composite.title'); + this.tabList = $('.tabList'); + this.tabList.setAttribute('role', 'tablist'); + this.tabList.style.height = this.headersize + 'px'; + this.header.appendChild(this.tabList); let actionbarcontainer = $('.title-actions'); - this._actionbar = new ActionBar(actionbarcontainer.getHTMLElement()); - this.$header.append(actionbarcontainer); + this._actionbar = new ActionBar(actionbarcontainer); + this.header.appendChild(actionbarcontainer); if (options.showHeaderWhenSingleView) { this._headerVisible = true; - this.$parent.append(this.$header); + this.parent.appendChild(this.header); } else { this._headerVisible = false; } - this.$body = $('.tabBody'); - this.$body.attr('role', 'tabpanel'); - this.$body.attr('tabindex', '0'); - this.$parent.append(this.$body); + this.body = $('.tabBody'); + this.body.setAttribute('role', 'tabpanel'); + this.body.setAttribute('tabindex', '0'); + this.parent.appendChild(this.body); } public contains(tab: IPanelTab): boolean { @@ -99,7 +99,7 @@ export class TabbedPanel extends Disposable implements IThemable { this.showTab(tab.identifier); } if (this._tabMap.size > 1 && !this._headerVisible) { - this.$parent.append(this.$header, 0); + this.parent.insertBefore(this.header, this.parent.firstChild); this._headerVisible = true; this.layout(this._currentDimensions); } @@ -116,30 +116,27 @@ export class TabbedPanel extends Disposable implements IThemable { private _createTab(tab: IInternalPanelTab): void { let tabHeaderElement = $('.tab-header'); - tabHeaderElement.attr('tabindex', '0'); - tabHeaderElement.attr('role', 'tab'); - tabHeaderElement.attr('aria-selected', 'false'); - tabHeaderElement.attr('aria-controls', tab.identifier); + tabHeaderElement.setAttribute('tabindex', '0'); + tabHeaderElement.setAttribute('role', 'tab'); + tabHeaderElement.setAttribute('aria-selected', 'false'); + tabHeaderElement.setAttribute('aria-controls', tab.identifier); let tabElement = $('.tab'); - tabHeaderElement.append(tabElement); + tabHeaderElement.appendChild(tabElement); let tabLabel = $('a.tabLabel'); - tabLabel.safeInnerHtml(tab.title); - tabElement.append(tabLabel); - tabHeaderElement.on(EventType.CLICK, e => this.showTab(tab.identifier)); - tabHeaderElement.on(EventType.KEY_DOWN, (e: KeyboardEvent) => { + tabLabel.innerText = tab.title; + tabElement.appendChild(tabLabel); + addDisposableListener(tabHeaderElement, EventType.CLICK, e => this.showTab(tab.identifier)); + addDisposableListener(tabHeaderElement, EventType.KEY_DOWN, (e: KeyboardEvent) => { let event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.Enter)) { this.showTab(tab.identifier); e.stopImmediatePropagation(); } }); - this.$tabList.append(tabHeaderElement); + this.tabList.appendChild(tabHeaderElement); tab.header = tabHeaderElement; tab.label = tabLabel; - tab.dispose = () => { - tab.header.dispose(); - tab.label.dispose(); - }; + tab.dispose = () => { }; this._register(tab); } @@ -149,19 +146,20 @@ export class TabbedPanel extends Disposable implements IThemable { } if (this._shownTab) { - this._tabMap.get(this._shownTab).label.removeClass('active'); - this._tabMap.get(this._shownTab).header.removeClass('active').attr('aria-selected', 'false'); + this._tabMap.get(this._shownTab).label.classList.remove('active'); + this._tabMap.get(this._shownTab).header.classList.remove('active'); + this._tabMap.get(this._shownTab).header.setAttribute('aria-selected', 'false'); } this._shownTab = id; this.tabHistory.push(id); - this.$body.clearChildren(); + quickBuilder(this.body).empty(); let tab = this._tabMap.get(this._shownTab); - this.$body.attr('aria-labelledby', tab.identifier); - tab.label.addClass('active'); - tab.header.addClass('active'); - tab.header.attr('aria-selected', 'true'); - tab.view.render(this.$body.getHTMLElement()); + this.body.setAttribute('aria-labelledby', tab.identifier); + tab.label.classList.add('active'); + tab.header.classList.add('active'); + tab.header.setAttribute('aria-selected', 'true'); + tab.view.render(this.body); this._onTabChange.fire(id); if (this._currentDimensions) { this._layoutCurrentTab(new Dimension(this._currentDimensions.width, this._currentDimensions.height - this.headersize)); @@ -170,11 +168,11 @@ export class TabbedPanel extends Disposable implements IThemable { public removeTab(tab: PanelTabIdentifier) { let actualTab = this._tabMap.get(tab); - actualTab.header.destroy(); + quickBuilder(actualTab.header).destroy(); if (actualTab.view.remove) { actualTab.view.remove(); } - this._tabMap.get(tab).header.destroy(); + quickBuilder(this._tabMap.get(tab).header).destroy(); this._tabMap.delete(tab); if (this._shownTab === tab) { this._shownTab = undefined; @@ -192,7 +190,7 @@ export class TabbedPanel extends Disposable implements IThemable { } if (!this.options.showHeaderWhenSingleView && this._tabMap.size === 1 && this._headerVisible) { - this.$header.offDOM(); + this.header.remove(); this._headerVisible = false; this.layout(this._currentDimensions); } @@ -205,12 +203,12 @@ export class TabbedPanel extends Disposable implements IThemable { public layout(dimension: Dimension): void { if (dimension) { this._currentDimensions = dimension; - this.$parent.style('height', dimension.height + 'px'); - this.$parent.style('width', dimension.width + 'px'); - this.$header.style('width', dimension.width + 'px'); - this.$body.style('width', dimension.width + 'px'); + this.parent.style.height = dimension.height + 'px'; + this.parent.style.height = dimension.width + 'px'; + this.header.style.width = dimension.width + 'px'; + this.body.style.width = dimension.width + 'px'; const bodyHeight = dimension.height - (this._headerVisible ? this.headersize : 0); - this.$body.style('height', bodyHeight + 'px'); + this.body.style.height = bodyHeight + 'px'; this._layoutCurrentTab(new Dimension(dimension.width, bodyHeight)); } } @@ -232,9 +230,9 @@ export class TabbedPanel extends Disposable implements IThemable { this._collapsed = val === false ? false : true; if (this.collapsed) { - this.$body.offDOM(); + this.body.remove(); } else { - this.$parent.append(this.$body); + this.parent.appendChild(this.body); } } diff --git a/src/sql/parts/query/editor/queryResultsView.ts b/src/sql/parts/query/editor/queryResultsView.ts index ed973a7363..0bcbec8fee 100644 --- a/src/sql/parts/query/editor/queryResultsView.ts +++ b/src/sql/parts/query/editor/queryResultsView.ts @@ -76,7 +76,7 @@ class ResultsView implements IPanelView { this.panelViewlet.resizePanel(this.gridPanel, this.state.messagePanelSize); } this.panelViewlet.resizePanel(this.gridPanel, panelSize); - }) + }); // once the user changes the sash we should stop trying to resize the grid once(this.panelViewlet.onDidSashChange)(e => { this.needsGridResize = false; @@ -204,9 +204,11 @@ export class QueryResultsView { this._panelView.pushTab(this.qpTab); } } - this.runnerDisposables.push(queryRunner.onResultSet(() => { + this.runnerDisposables.push(queryRunner.onQueryEnd(() => { if (queryRunner.isQueryPlan) { - this.showPlan(queryRunner.planXml); + queryRunner.planXml.then(e => { + this.showPlan(e); + }); } })); if (this.input.state.activeTab) { diff --git a/src/sql/parts/query/execution/queryRunner.ts b/src/sql/parts/query/execution/queryRunner.ts index ba1530b5a2..b0450ce27b 100644 --- a/src/sql/parts/query/execution/queryRunner.ts +++ b/src/sql/parts/query/execution/queryRunner.ts @@ -26,6 +26,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResultSerializer } from 'sql/parts/query/common/resultSerializer'; import { TPromise } from 'vs/base/common/winjs.base'; +import { Deferred } from 'sql/base/common/promise'; export interface IEditSessionReadyEvent { ownerUri: string; @@ -69,11 +70,11 @@ export default class QueryRunner { private _hasCompleted: boolean = false; private _batchSets: sqlops.BatchSummary[] = []; private _eventEmitter = new EventEmitter(); - private _isQueryPlan: boolean; + private _isQueryPlan: boolean; public get isQueryPlan(): boolean { return this._isQueryPlan; } - private _planXml: string; - public get planXml(): string { return this._planXml; } + private _planXml = new Deferred(); + public get planXml(): Thenable { return this._planXml.promise; } private _onMessage = new Emitter(); private _debouncedMessage = debounceEvent(this._onMessage.event, (l, e) => { @@ -342,7 +343,7 @@ export default class QueryRunner { } // handle getting queryPlanxml if we need too if (this.isQueryPlan) { - this.getQueryRows(0, 1, 0, 0).then(e => this._planXml = e.resultSubset.rows[0][0].displayValue); + this.getQueryRows(0, 1, 0, 0).then(e => this._planXml.resolve(e.resultSubset.rows[0][0].displayValue)); } if (batchSet) { // Store the result set in the batch and emit that a result set has completed