diff --git a/resources/xlf/sqlops-core/sql.xlf b/resources/xlf/sqlops-core/sql.xlf index a1791f64c6..8d786fc087 100644 --- a/resources/xlf/sqlops-core/sql.xlf +++ b/resources/xlf/sqlops-core/sql.xlf @@ -62,6 +62,12 @@ [Optional] File encoding used when saving results as CSV + + [Optional] When true, XML output will be formatted when saving results as XML + + + [Optional] File encoding used when saving results as XML + [Optional] Configuration options for copying results from the Results View @@ -596,6 +602,9 @@ Save As Excel + + Save As XML + Copy @@ -1142,6 +1151,9 @@ Save as Excel + + Save as XML + View as Chart diff --git a/src/sql/parts/grid/common/gridContentEvents.ts b/src/sql/parts/grid/common/gridContentEvents.ts index 9b2f0eea8f..265cc4f2e9 100644 --- a/src/sql/parts/grid/common/gridContentEvents.ts +++ b/src/sql/parts/grid/common/gridContentEvents.ts @@ -17,6 +17,7 @@ export let SelectAllMessages = 'SelectAllMessages'; export let SaveAsCsv = 'SaveAsCSV'; export let SaveAsJSON = 'SaveAsJSON'; export let SaveAsExcel = 'SaveAsExcel'; +export let SaveAsXML = 'SaveAsXML'; export let ViewAsChart = 'ViewAsChart'; export let GoToNextQueryOutputTab = 'GoToNextQueryOutputTab'; export let GoToNextGrid = 'GoToNextGrid'; diff --git a/src/sql/parts/grid/media/saveXml.svg b/src/sql/parts/grid/media/saveXml.svg new file mode 100644 index 0000000000..d6682e3606 --- /dev/null +++ b/src/sql/parts/grid/media/saveXml.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/sql/parts/grid/media/saveXml_inverse.svg b/src/sql/parts/grid/media/saveXml_inverse.svg new file mode 100644 index 0000000000..c48e6aaed1 --- /dev/null +++ b/src/sql/parts/grid/media/saveXml_inverse.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/sql/parts/grid/media/slickColorTheme.css b/src/sql/parts/grid/media/slickColorTheme.css index a5d8ee1b8b..24e7b8d966 100644 --- a/src/sql/parts/grid/media/slickColorTheme.css +++ b/src/sql/parts/grid/media/slickColorTheme.css @@ -112,6 +112,11 @@ background-image: url("saveExcel.svg"); } +.vs .icon.saveXml { + /* ResultToXML_16x_vscode */ + background-image: url("saveXml.svg"); +} + .vs .icon.viewChart { /* ResultToXlsx_16x_vscode */ background-image: url("viewChart.svg"); @@ -243,6 +248,12 @@ background-image: url("saveExcel_inverse.svg"); } +.vs-dark .icon.saveXml, +.hc-black .icon.saveXml { + /* ResultToXml_16x_vscode_inverse.svg */ + background-image: url("saveXml_inverse.svg"); +} + .vs-dark .icon.viewChart, .hc-black .icon.viewChart { /* ResultToXlsx_16x_vscode */ diff --git a/src/sql/parts/grid/views/gridActions.ts b/src/sql/parts/grid/views/gridActions.ts index 7cc7d319d7..6099b01b32 100644 --- a/src/sql/parts/grid/views/gridActions.ts +++ b/src/sql/parts/grid/views/gridActions.ts @@ -17,6 +17,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti export const GRID_SAVECSV_ID = 'grid.saveAsCsv'; export const GRID_SAVEJSON_ID = 'grid.saveAsJson'; export const GRID_SAVEEXCEL_ID = 'grid.saveAsExcel'; +export const GRID_SAVEXML_ID = 'grid.saveAsXml'; export const GRID_COPY_ID = 'grid.copySelection'; export const GRID_COPYWITHHEADERS_ID = 'grid.copyWithHeaders'; export const GRID_SELECTALL_ID = 'grid.selectAll'; @@ -46,6 +47,7 @@ export class GridActionProvider { actions.push(new SaveResultAction(SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveFormat.CSV, this._dataService)); actions.push(new SaveResultAction(SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveFormat.JSON, this._dataService)); actions.push(new SaveResultAction(SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveFormat.EXCEL, this._dataService)); + actions.push(new SaveResultAction(SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveFormat.XML, this._dataService)); actions.push(new SelectAllGridAction(SelectAllGridAction.ID, SelectAllGridAction.LABEL, this._selectAllCallback)); actions.push(new CopyResultAction(CopyResultAction.COPY_ID, CopyResultAction.COPY_LABEL, false, this._dataService)); actions.push(new CopyResultAction(CopyResultAction.COPYWITHHEADERS_ID, CopyResultAction.COPYWITHHEADERS_LABEL, true, this._dataService)); @@ -74,6 +76,9 @@ export class SaveResultAction extends Action { public static SAVEEXCEL_ID = GRID_SAVEEXCEL_ID; public static SAVEEXCEL_LABEL = localize('saveAsExcel', 'Save As Excel'); + public static SAVEXML_ID = GRID_SAVEXML_ID; + public static SAVEXML_LABEL = localize('saveAsXml', 'Save As XML'); + constructor( id: string, label: string, diff --git a/src/sql/parts/grid/views/gridCommands.ts b/src/sql/parts/grid/views/gridCommands.ts index 2a987c61ed..1723d50887 100644 --- a/src/sql/parts/grid/views/gridCommands.ts +++ b/src/sql/parts/grid/views/gridCommands.ts @@ -69,6 +69,10 @@ export const saveAsExcel = (accessor: ServicesAccessor) => { runActionOnActiveResultsEditor(accessor, GridContentEvents.SaveAsExcel); }; +export const saveAsXml = (accessor: ServicesAccessor) => { + runActionOnActiveResultsEditor(accessor, GridContentEvents.SaveAsXML); +}; + export const selectAll = (accessor: ServicesAccessor) => { runActionOnActiveResultsEditor(accessor, GridContentEvents.SelectAll); }; diff --git a/src/sql/parts/grid/views/gridParentComponent.ts b/src/sql/parts/grid/views/gridParentComponent.ts index 596ccc67bd..c7f43c3c5f 100644 --- a/src/sql/parts/grid/views/gridParentComponent.ts +++ b/src/sql/parts/grid/views/gridParentComponent.ts @@ -154,6 +154,9 @@ export abstract class GridParentComponent { case GridContentEvents.SaveAsExcel: self.sendSaveRequest(SaveFormat.EXCEL); break; + case GridContentEvents.SaveAsXML: + self.sendSaveRequest(SaveFormat.XML); + break; case GridContentEvents.GoToNextQueryOutputTab: self.goToNextQueryOutputTab(); break; @@ -320,6 +323,9 @@ export abstract class GridParentComponent { 'SaveAsExcel': () => { this.sendSaveRequest(SaveFormat.EXCEL); }, + 'SaveAsXML': () => { + this.sendSaveRequest(SaveFormat.XML); + }, 'GoToNextQueryOutputTab': () => { this.goToNextQueryOutputTab(); } @@ -345,6 +351,9 @@ export abstract class GridParentComponent { case 'saveexcel': this.dataService.sendSaveRequest({ batchIndex: event.batchId, resultSetNumber: event.resultId, format: SaveFormat.EXCEL, selection: event.selection }); break; + case 'savexml': + this.dataService.sendSaveRequest({ batchIndex: event.batchId, resultSetNumber: event.resultId, format: SaveFormat.XML, selection: event.selection }); + break; case 'selectall': this.activeGrid = event.index; this.onSelectAllForActiveGrid(); diff --git a/src/sql/parts/grid/views/query/query.component.ts b/src/sql/parts/grid/views/query/query.component.ts index b20606c921..db9a1bf78b 100644 --- a/src/sql/parts/grid/views/query/query.component.ts +++ b/src/sql/parts/grid/views/query/query.component.ts @@ -127,6 +127,19 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes } } }, + { + showCondition: () => { return true; }, + icon: () => { return 'saveXml'; }, + hoverText: () => { return LocalizedConstants.saveXMLLabel; }, + functionality: (batchId, resultId, index) => { + let selection = this.getSelection(index); + if (selection.length <= 1) { + this.handleContextClick({ type: 'savexml', batchId: batchId, resultId: resultId, index: index, selection: selection }); + } else { + this.dataService.showWarning(LocalizedConstants.msgCannotSaveMultipleSelections); + } + } + }, { showCondition: () => { return this.configurationService.getValue('workbench')['enablePreviewFeatures']; diff --git a/src/sql/parts/query/common/constants.ts b/src/sql/parts/query/common/constants.ts index 5a083d300b..6d85f7d7b2 100644 --- a/src/sql/parts/query/common/constants.ts +++ b/src/sql/parts/query/common/constants.ts @@ -5,6 +5,7 @@ export const copyIncludeHeaders = 'copyIncludeHeaders'; export const configSaveAsCsv = 'saveAsCsv'; +export const configSaveAsXml = 'saveAsXml'; export const configCopyRemoveNewLine = 'copyRemoveNewLine'; export const configShowBatchTime = 'showBatchTime'; export const querySection = 'query'; diff --git a/src/sql/parts/query/common/localizedConstants.ts b/src/sql/parts/query/common/localizedConstants.ts index 84e7fbd73c..1cc2dfd422 100644 --- a/src/sql/parts/query/common/localizedConstants.ts +++ b/src/sql/parts/query/common/localizedConstants.ts @@ -24,6 +24,7 @@ export const restoreLabel = localize('resultsPane.restoreLabel', 'Restore'); export const saveCSVLabel = localize('saveCSVLabel', 'Save as CSV'); export const saveJSONLabel = localize('saveJSONLabel', 'Save as JSON'); export const saveExcelLabel = localize('saveExcelLabel', 'Save as Excel'); +export const saveXMLLabel = localize('saveXMLLabel', 'Save as XML'); export const viewChartLabel = localize('viewChartLabel', 'View as Chart'); export const resultPaneLabel = localize('resultPaneLabel', 'Results'); diff --git a/src/sql/parts/query/common/query.contribution.ts b/src/sql/parts/query/common/query.contribution.ts index a650c239db..9ab111ffdc 100644 --- a/src/sql/parts/query/common/query.contribution.ts +++ b/src/sql/parts/query/common/query.contribution.ts @@ -233,6 +233,14 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: gridCommands.saveAsExcel }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: gridActions.GRID_SAVEXML_ID, + weight: KeybindingWeight.EditorContrib, + when: ResultsGridFocusCondition, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_R, KeyMod.CtrlCmd | KeyCode.KEY_X), + handler: gridCommands.saveAsXml +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: gridActions.GRID_VIEWASCHART_ID, weight: KeybindingWeight.EditorContrib, @@ -310,6 +318,16 @@ let registryProperties = { 'description': localize('sql.results.streaming', 'Enable results streaming; contains few minor visual issues'), 'default': false }, + 'sql.saveAsXml.formatted': { + 'type': 'string', + 'description': localize('sql.saveAsXml.formatted', '[Optional] When true, XML output will be formatted when saving results as XML'), + 'default': true + }, + 'sql.saveAsXml.encoding': { + 'type': 'string', + 'description': localize('sql.saveAsXml.encoding', '[Optional] File encoding used when saving results as XML'), + 'default': 'utf-8' + }, 'sql.copyIncludeHeaders': { 'type': 'boolean', 'description': localize('sql.copyIncludeHeaders', '[Optional] Configuration options for copying results from the Results View'), diff --git a/src/sql/parts/query/common/resultSerializer.ts b/src/sql/parts/query/common/resultSerializer.ts index 4b9c7b089e..dd0417d2d6 100644 --- a/src/sql/parts/query/common/resultSerializer.ts +++ b/src/sql/parts/query/common/resultSerializer.ts @@ -254,6 +254,24 @@ export class ResultSerializer { return config; } + private getConfigForXml(): SaveResultsRequestParams { + let saveResultsParams = { resultFormat: SaveFormat.XML as string }; + + // get save results config from vscode config + let saveConfig = WorkbenchUtils.getSqlConfigSection(this._workspaceConfigurationService, Constants.configSaveAsXml); + // if user entered config, set options + if (saveConfig) { + if (saveConfig.formatted !== undefined) { + saveResultsParams.formatted = saveConfig.formatted; + } + if (saveConfig.encoding !== undefined) { + saveResultsParams.encoding = saveConfig.encoding; + } + } + + return saveResultsParams; + } + private getParameters(filePath: string, batchIndex: number, resultSetNo: number, format: string, selection: Slick.Range): SaveResultsRequestParams { let saveResultsParams: SaveResultsRequestParams; if (!path.isAbsolute(filePath)) { @@ -268,6 +286,8 @@ export class ResultSerializer { saveResultsParams = this.getConfigForJson(); } else if (format === SaveFormat.EXCEL) { saveResultsParams = this.getConfigForExcel(); + } else if (format === SaveFormat.XML) { + saveResultsParams = this.getConfigForXml(); } saveResultsParams.filePath = this._filePath; diff --git a/src/sql/parts/query/editor/actions.ts b/src/sql/parts/query/editor/actions.ts index 0db77835e9..ce68aa0846 100644 --- a/src/sql/parts/query/editor/actions.ts +++ b/src/sql/parts/query/editor/actions.ts @@ -57,6 +57,10 @@ export class SaveResultAction extends Action { public static SAVEEXCEL_LABEL = localize('saveAsExcel', 'Save As Excel'); public static SAVEEXCEL_ICON = 'saveExcel'; + public static SAVEXML_ID = 'grid.saveAsXml'; + public static SAVEXML_LABEL = localize('saveAsXml', 'Save As XML'); + public static SAVEXML_ICON = 'saveXml'; + constructor( id: string, label: string, diff --git a/src/sql/parts/query/editor/gridPanel.ts b/src/sql/parts/query/editor/gridPanel.ts index d79b342dcc..9b365f955c 100644 --- a/src/sql/parts/query/editor/gridPanel.ts +++ b/src/sql/parts/query/editor/gridPanel.ts @@ -695,6 +695,7 @@ class GridTable extends Disposable implements IView { new SaveResultAction(SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV), new SaveResultAction(SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL), new SaveResultAction(SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON), + new SaveResultAction(SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveResultAction.SAVEXML_ICON, SaveFormat.XML), this.instantiationService.createInstance(ChartDataAction) ); @@ -753,6 +754,7 @@ class GridTable extends Disposable implements IView { new SaveResultAction(SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV), new SaveResultAction(SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL), new SaveResultAction(SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON), + new SaveResultAction(SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveResultAction.SAVEXML_ICON, SaveFormat.XML), new Separator(), new CopyResultAction(CopyResultAction.COPY_ID, CopyResultAction.COPY_LABEL, false), new CopyResultAction(CopyResultAction.COPYWITHHEADERS_ID, CopyResultAction.COPYWITHHEADERS_LABEL, true) diff --git a/src/sql/sqlops.d.ts b/src/sql/sqlops.d.ts index cf40c14bd4..8964aca9c5 100644 --- a/src/sql/sqlops.d.ts +++ b/src/sql/sqlops.d.ts @@ -882,7 +882,7 @@ declare module 'sqlops' { // Save Results =============================================================================== export interface SaveResultsRequestParams { /** - * 'csv', 'json', 'excel' + * 'csv', 'json', 'excel', 'xml' */ resultFormat: string; ownerUri: string; @@ -898,6 +898,7 @@ declare module 'sqlops' { lineSeperator?: string; textIdentifier?: string; encoding?: string; + formatted?: boolean; } export interface SaveResultRequestResult {