diff --git a/CHANGES.md b/CHANGES.md index 76e33016..04992275 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ ## Version 1.23 +- Add Query Plan to data export [feature 314](https://github.com/tprouvot/Salesforce-Inspector-reloaded/issues/314) - Align show-all data 'Type' column with Salesforce's 'Data Type' field [issue 312](https://github.com/tprouvot/Salesforce-Inspector-reloaded/issues/312) by [efcdilascio](https://github.com/efcdilascio) - Make data export suggestions scrollable [feature 301](https://github.com/tprouvot/Salesforce-Inspector-reloaded/issues/301) by [Vranisimo](https://github.com/vranisimo) - Show the number of filtered records in data export [feature 300](https://github.com/tprouvot/Salesforce-Inspector-reloaded/issues/300) by [Vranisimo](https://github.com/vranisimo) @@ -48,7 +49,6 @@ ## Version 1.20.1 - Bugfix Delete Button not enabled when only one record is queried/filtered (contribution by [Oscar Gomez Balaguer](https://github.com/ogomezba)) - - Bugfix User selection not displaying information (for orgs without community enabled) [issue 211](https://github.com/tprouvot/Salesforce-Inspector-reloaded/issues/211) ## Version 1.20 diff --git a/addon/data-export-test.js b/addon/data-export-test.js index 97eb5a2f..c5d334ff 100644 --- a/addon/data-export-test.js +++ b/addon/data-export-test.js @@ -147,7 +147,7 @@ export async function dataExportTest(test) { // Autocomplete object setQuery("select Id from OpportunityLi", "", ""); assertEquals("Objects suggestions:", vm.autocompleteResults.title); - assertEquals(["OpportunityLineItem"], getValues(vm.autocompleteResults.results)); + assertEquals(["OpportunityLineItem", "OpportunityLineItemChangeEvent"], getValues(vm.autocompleteResults.results)); // Autocomplete unknown object setQuery("select Id from UnknownObj", "", ""); diff --git a/addon/data-export.js b/addon/data-export.js index cdce565c..0dd7e56e 100644 --- a/addon/data-export.js +++ b/addon/data-export.js @@ -360,19 +360,23 @@ class Model { vm.autocompleteProgress.abort(); } - vm.autocompleteClick = ({value, suffix}) => { - vm.queryInput.focus(); - //handle when selected field is the last one before "FROM" keyword, or if an existing comma is present after selection - let indexFrom = query.toLowerCase().indexOf("from"); - if (suffix.trim() == "," && (query.substring(selEnd + 1, indexFrom).trim().length == 0 || query.substring(selEnd).trim().startsWith(",") || query.substring(selEnd).trim().toLowerCase().startsWith("from"))) { - suffix = ""; - } - vm.queryInput.setRangeText(value + suffix, selStart, selEnd, "end"); - //add query suffix if needed - if (value.startsWith("FIELDS") && !query.toLowerCase().includes("limit")) { - vm.queryInput.value += " LIMIT 200"; + vm.autocompleteClick = ({value, suffix, link}) => { + if (link){ + window.open(link, "_blank"); + } else { + vm.queryInput.focus(); + //handle when selected field is the last one before "FROM" keyword, or if an existing comma is present after selection + let indexFrom = query.toLowerCase().indexOf("from"); + if (suffix.trim() == "," && (query.substring(selEnd + 1, indexFrom).trim().length == 0 || query.substring(selEnd).trim().startsWith(",") || query.substring(selEnd).trim().toLowerCase().startsWith("from"))) { + suffix = ""; + } + vm.queryInput.setRangeText(value + suffix, selStart, selEnd, "end"); + //add query suffix if needed + if (value.startsWith("FIELDS") && !query.toLowerCase().includes("limit")) { + vm.queryInput.value += " LIMIT 200"; + } + vm.queryAutocompleteHandler(); } - vm.queryAutocompleteHandler(); }; // Find the token we want to autocomplete. This is the selected text, or the last word before the cursor. @@ -905,6 +909,28 @@ class Model { stopExport() { this.exportProgress.abort(); } + doQueryPlan(){ + let vm = this; // eslint-disable-line consistent-this + let exportedData = new RecordTable(vm); + + vm.spinFor(sfConn.rest("/services/data/v" + apiVersion + "/query/?explain=" + encodeURIComponent(vm.queryInput.value)).then(res => { + exportedData.addToTable(res.plans); + vm.exportStatus = ""; + vm.performancePoints = []; + vm.exportedData = exportedData; + vm.updatedExportedData(); + vm.didUpdate(); + }, () => { + vm.isWorking = false; + })); + vm.autocompleteResults = { + sobjectName: "", + title: "Query Plan Tool:", + results: [{value: "Developer Console Query Plan Tool FAQ", title: "Developer Console Query Plan Tool FAQ", rank: 1, autocompleteType: "fieldName", dataType: "", link: "https://help.salesforce.com/s/articleView?id=000386864&type=1"}, + {value: "Get Feedback on Query Performance", title: "Get Feedback on Query Performance", suffix: " ", rank: 1, autocompleteType: "fieldName", dataType: "", link: "https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_query_explain.htm"}, + ] + }; + } } function RecordTable(vm) { @@ -1017,6 +1043,7 @@ class App extends React.Component { this.onToggleSavedOptions = this.onToggleSavedOptions.bind(this); this.onExport = this.onExport.bind(this); this.onCopyQuery = this.onCopyQuery.bind(this); + this.onQueryPlan = this.onQueryPlan.bind(this); this.onCopyAsExcel = this.onCopyAsExcel.bind(this); this.onCopyAsCsv = this.onCopyAsCsv.bind(this); this.onCopyAsJson = this.onCopyAsJson.bind(this); @@ -1129,6 +1156,11 @@ class App extends React.Component { navigator.clipboard.writeText(url.toString()); model.didUpdate(); } + onQueryPlan(){ + let {model} = this.props; + model.doQueryPlan(); + model.didUpdate(); + } onCopyAsExcel() { let {model} = this.props; model.copyAsExcel(); @@ -1313,8 +1345,9 @@ class App extends React.Component { h("div", {className: "flex-right"}, h("button", {tabIndex: 1, disabled: model.isWorking, onClick: this.onExport, title: "Ctrl+Enter / F5", className: "highlighted"}, "Run Export"), h("button", {tabIndex: 2, onClick: this.onCopyQuery, title: "Copy query url", className: "copy-id"}, "Export Query"), - h("a", {tabIndex: 3, className: "button", hidden: !model.autocompleteResults.sobjectName, href: model.showDescribeUrl(), target: "_blank", title: "Show field info for the " + model.autocompleteResults.sobjectName + " object"}, model.autocompleteResults.sobjectName + " Field Info"), - h("button", {tabIndex: 4, href: "#", className: model.expandAutocomplete ? "toggle contract" : "toggle expand", onClick: this.onToggleExpand, title: "Show all suggestions or only the first line"}, + h("button", {tabIndex: 3, onClick: this.onQueryPlan, title: "Run Query Plan"}, "Query Plan"), + h("a", {tabIndex: 4, className: "button", hidden: !model.autocompleteResults.sobjectName, href: model.showDescribeUrl(), target: "_blank", title: "Show field info for the " + model.autocompleteResults.sobjectName + " object"}, model.autocompleteResults.sobjectName + " Field Info"), + h("button", {tabIndex: 5, href: "#", className: model.expandAutocomplete ? "toggle contract" : "toggle expand", onClick: this.onToggleExpand, title: "Show all suggestions or only the first line"}, h("div", {className: "button-icon"}), h("div", {className: "button-toggle-icon"}) )