Skip to content

Commit

Permalink
[data-export] Performance display and checkbox toggle common component (
Browse files Browse the repository at this point in the history
#287)

## Describe your changes
- add performance metrics to data export for queries with batch count
and min/max/avg time
- created reusable CheckboxToggle for tab options
- added Display Query Execution Time checkbox toggle to Data Export tab
on options
- minor cleanup/linting
- Replaced data export "record(s)" with plural function to display
"Exported n records" for 0 or more than 1 record, "Exported 1 record"
for 1

## Issue ticket number and link
n/a

## Checklist before requesting a review
- [ x ] I have read and understand the [Contributions
section](https://github.com/tprouvot/Salesforce-Inspector-reloaded#contributions)
- [ x ] Target branch is releaseCandidate and not master
- [ x ] I have performed a self-review of my code
- [ x ] I ran the [unit
tests](https://github.com/tprouvot/Salesforce-Inspector-reloaded#unit-tests)
and my PR does not break any tests
- [ x ] I documented the changes I've made on the
[CHANGES.md](https://github.com/tprouvot/Salesforce-Inspector-reloaded/blob/master/CHANGES.md)
and followed actual conventions
- [ x ] I added a new section on
[how-to.md](https://github.com/tprouvot/Salesforce-Inspector-reloaded/blob/master/docs/how-to.md)
(optional)

---------

Co-authored-by: nshulman <[email protected]>
Co-authored-by: Nathan Shulman <[email protected]>
  • Loading branch information
3 people authored Jan 29, 2024
1 parent d0087f3 commit 815f45e
Show file tree
Hide file tree
Showing 10 changed files with 500 additions and 472 deletions.
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Release Notes

## Version 1.23

- Add the possibility to hide fields API names after users clicked "Show fields API names"
- Add performance On data export with option to disable in Options/Data Export tab (contribution by [Nathan Shulman](https://github.com/nshulman))
- Clean up popup header and footer (contribution by [Nathan Shulman](https://github.com/nshulman))

## Version 1.22

- Fix double "Show all data" button [issue 63](https://github.com/tprouvot/Salesforce-Inspector-reloaded/issues/63) (contribution by [Luca Bassani](https://github.com/baslu93))
Expand Down
12 changes: 6 additions & 6 deletions addon/data-export-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ export async function dataExportTest(test) {
await waitForSpinner();

assertEquals(false, vm.isWorking);
assertEquals("Exported 4 record(s)", vm.exportStatus);
assertEquals("Exported 4 records", vm.exportStatus);
assertEquals([
["_", "Name", "Checkbox__c", "Number__c"],
[{type: "Inspector_Test__c"}, "test1", false, 100.01],
Expand Down Expand Up @@ -308,7 +308,7 @@ export async function dataExportTest(test) {
// Filter results
vm.setResultsFilter("TRU");
assertEquals(false, vm.isWorking);
assertEquals("Exported 4 record(s)", vm.exportStatus);
assertEquals("Exported 4 records", vm.exportStatus);
assertEquals([
["_", "Name", "Checkbox__c", "Number__c"],
[{type: "Inspector_Test__c"}, "test1", false, 100.01],
Expand All @@ -323,7 +323,7 @@ export async function dataExportTest(test) {
// Clear filter
vm.setResultsFilter("");
assertEquals(false, vm.isWorking);
assertEquals("Exported 4 record(s)", vm.exportStatus);
assertEquals("Exported 4 records", vm.exportStatus);
assertEquals([
["_", "Name", "Checkbox__c", "Number__c"],
[{type: "Inspector_Test__c"}, "test1", false, 100.01],
Expand All @@ -350,7 +350,7 @@ export async function dataExportTest(test) {
await waitForSpinner();

assertEquals(false, vm.isWorking);
assertEquals("Exported 4 record(s)", vm.exportStatus);
assertEquals("Exported 4 records", vm.exportStatus);
assertEquals([
["_", "Name", "Lookup__r", "Lookup__r.Name"],
[{type: "Inspector_Test__c"}, "test1", null, null],
Expand Down Expand Up @@ -407,7 +407,7 @@ export async function dataExportTest(test) {
await waitForSpinner();

assertEquals(false, vm.isWorking);
assertEquals("No data exported. 4 record(s).", vm.exportStatus);
assertEquals("No data exported. 4 records.", vm.exportStatus);
assertEquals([], vm.exportedData.table);
assertEquals([], vm.exportedData.rowVisibilities);
assertEquals([true], vm.exportedData.colVisibilities);
Expand Down Expand Up @@ -460,7 +460,7 @@ export async function dataExportTest(test) {
await waitForSpinner();

assertEquals(false, vm.isWorking);
assertEquals("Exported 3000 record(s)", vm.exportStatus);
assertEquals("Exported 3000 records", vm.exportStatus);
assertEquals(3001, vm.exportedData.table.length);
assertEquals(3001, vm.exportedData.rowVisibilities.length);
assertEquals(2, vm.exportedData.colVisibilities.length);
Expand Down
6 changes: 6 additions & 0 deletions addon/data-export.css
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ textarea[hidden] {
padding: 8px 12px;
}

.result-info {
font-style: italic;
margin-left: 9px;
color: #8c8c8c;
}

#result-text {
flex: 1 1 0;
resize: none;
Expand Down
77 changes: 66 additions & 11 deletions addon/data-export.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* global React ReactDOM */
import {sfConn, apiVersion} from "./inspector.js";
/* global initButton */
import {Enumerable, DescribeInfo, copyToClipboard, initScrollTable} from "./data-load.js";
import {Enumerable, DescribeInfo, copyToClipboard, initScrollTable, s} from "./data-load.js";

class QueryHistory {
constructor(storageKey, max) {
Expand Down Expand Up @@ -93,6 +93,11 @@ class Model {
this.expandAutocomplete = false;
this.expandSavedOptions = false;
this.resultsFilter = "";
this.displayPerformance = localStorage.getItem("displayQueryPerformance") !== "false"; // default to true
this.performancePoints = [];
this.startTime = null;
this.lastStartTime = null;
this.totalTime = 0;
this.autocompleteState = "";
this.autocompleteProgress = {};
this.exportProgress = {};
Expand Down Expand Up @@ -124,8 +129,8 @@ class Model {
if (args.has("error")) {
this.exportError = args.get("error") + " " + args.get("error_description");
}

}

updatedExportedData() {
this.resultTableCallback(this.exportedData);
}
Expand Down Expand Up @@ -183,6 +188,43 @@ class Model {
this.queryInput.setRangeText("", indexPos + 5, indexPos + 5, "end");
}
}
initPerf() {
if (!this.displayPerformance) {
return;
}
this.performancePoints = [];
this.startTime = performance.now();
this.lastStartTime = this.startTime;
}
markPerf() {
if (!this.displayPerformance) {
return;
}
const now = performance.now();
const perfPoint = now - this.lastStartTime;
this.lastStartTime = now;
this.performancePoints.push(perfPoint);
this.totalTime = now - this.startTime;
}
perfStatus() {
if (!this.displayPerformance || !this.startTime || this.performancePoints.length === 0) {
return null;
}
const batches = this.performancePoints.length;
let batchStats = "";
let batchCount = "";
if (batches > 1) {
const avgTime = this.performancePoints.reduce((a, b) => a + b, 0) / batches;
const maxTime = Math.max(...this.performancePoints);
const minTime = Math.min(...this.performancePoints);
const avg = `Avg ${avgTime.toFixed(1)}ms`;
const max = `Max ${maxTime.toFixed(1)}ms`;
const min = `Min ${minTime.toFixed(1)}ms`;
batchStats = `Batch Performance: ${avg}, ${min}, ${max}`;
batchCount = `${batches} Batches / `;
}
return {text: `${batchCount}${this.totalTime.toFixed(1)}ms`, batchStats};
}
clearHistory() {
this.queryHistory.clear();
}
Expand Down Expand Up @@ -248,7 +290,7 @@ class Model {
let args = new URLSearchParams();
args.set("host", this.sfHost);
args.set("data", encodedData);
if (this.queryTooling) args.set("apitype", 'Tooling');
if (this.queryTooling) args.set("apitype", "Tooling");

window.open("data-import.html?" + args, getLinkTarget(e));
}
Expand Down Expand Up @@ -774,6 +816,7 @@ class Model {
exportedData.isTooling = vm.queryTooling;
exportedData.describeInfo = vm.describeInfo;
exportedData.sfHost = vm.sfHost;
vm.initPerf();
let query = vm.queryInput.value;
let queryMethod = exportedData.isTooling ? "tooling/query" : vm.queryAll ? "queryAll" : "query";
function batchHandler(batch) {
Expand All @@ -784,45 +827,54 @@ class Model {
throw err;
}).then(data => {
exportedData.addToTable(data.records);
let recs = exportedData.records.length;
let total = exportedData.totalSize;
if (data.totalSize != -1) {
exportedData.totalSize = data.totalSize;
total = data.totalSize;
}
if (!data.done) {
let pr = batchHandler(sfConn.rest(data.nextRecordsUrl, {progressHandler: vm.exportProgress}));
vm.isWorking = true;
vm.exportStatus = "Exporting... Completed " + exportedData.records.length + " of " + exportedData.totalSize + " record(s)";
vm.exportStatus = `Exporting... Completed ${recs} of ${total} record${s(total)}.`;
vm.exportError = null;
vm.exportedData = exportedData;
vm.markPerf();
vm.updatedExportedData();
vm.didUpdate();
return pr;
}
vm.queryHistory.add({query, useToolingApi: exportedData.isTooling});
if (exportedData.records.length == 0) {
if (recs == 0) {
vm.isWorking = false;
vm.exportStatus = data.totalSize > 0 ? "No data exported. " + data.totalSize + " record(s)." : "No data exported.";
vm.exportStatus = "No data exported." + (total > 0 ? ` ${total} record${s(total)}.` : "");
vm.exportError = null;
vm.exportedData = exportedData;
vm.markPerf();
vm.updatedExportedData();
return null;
}
vm.isWorking = false;
vm.exportStatus = "Exported " + exportedData.records.length + (exportedData.records.length != exportedData.totalSize ? " of " + exportedData.totalSize : "") + " record(s)";
vm.exportStatus = `Exported ${recs}${recs !== total ? (" of " + total) : ""} record${s(recs)}`;
vm.exportError = null;
vm.exportedData = exportedData;
vm.markPerf();
vm.updatedExportedData();
return null;
}, err => {
if (err.name != "SalesforceRestError") {
throw err; // not a SalesforceRestError
}
if (exportedData.totalSize != -1) {
let recs = exportedData.records.length;
let total = exportedData.totalSize;
if (total != -1) {
// We already got some data. Show it, and indicate that not all data was exported
vm.isWorking = false;
vm.exportStatus = "Exported " + exportedData.records.length + " of " + exportedData.totalSize + " record(s). Stopped by error.";
vm.exportStatus = `Exported ${recs} of ${total} record${s(total)}. Stopped by error.`;
vm.exportError = null;
vm.exportedData = exportedData;
vm.updatedExportedData();
vm.markPerf();
return null;
}
vm.isWorking = false;
Expand All @@ -840,6 +892,7 @@ class Model {
vm.exportStatus = "Error";
vm.exportError = "UNEXPECTED EXCEPTION:" + error;
vm.exportedData = null;
vm.markPerf();
vm.updatedExportedData();
}));
vm.setResultsFilter("");
Expand Down Expand Up @@ -1145,7 +1198,7 @@ class App extends React.Component {
}
});
addEventListener("keydown", e => {
if (e.ctrlKey && e.key == "Enter" || e.key == "F5") {
if ((e.ctrlKey && e.key == "Enter") || e.key == "F5") {
e.preventDefault();
model.doExport();
model.didUpdate();
Expand Down Expand Up @@ -1184,6 +1237,7 @@ class App extends React.Component {
}
render() {
let {model} = this.props;
const perf = model.perfStatus();
return h("div", {},
h("div", {id: "user-info"},
h("a", {href: model.sfLink, className: "sf-link"},
Expand Down Expand Up @@ -1291,8 +1345,9 @@ class App extends React.Component {
h("input", {placeholder: "Filter Results", type: "search", value: model.resultsFilter, onInput: this.onResultsFilterInput}),
h("span", {className: "result-status flex-right"},
h("span", {}, model.exportStatus),
perf && h("span", {className: "result-info", title: perf.batchStats}, perf.text),
h("button", {className: "cancel-btn", disabled: !model.isWorking, onClick: this.onStopExport}, "Stop"),
)
),
),
h("textarea", {id: "result-text", readOnly: true, value: model.exportError || "", hidden: model.exportError == null}),
h("div", {id: "result-table", ref: "scroller", hidden: model.exportError != null}
Expand Down
5 changes: 5 additions & 0 deletions addon/data-load.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ export function DescribeInfo(spinFor, didUpdate) {
};
}

// Pluralize a numeric value by adding an s (or optional suffix) if it is not 1
export function s(num, suffix = "s") {
return num == 1 ? "" : suffix;
}

// Copy text to the clipboard, without rendering it, since rendering is slow.
export function copyToClipboard(value) {
if (parent && parent.isUnitTest) { // for unit tests
Expand Down
Loading

0 comments on commit 815f45e

Please sign in to comment.