@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
351 lines • 12.9 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.csvReports = csvReports;
exports.finalMarkdownReport = finalMarkdownReport;
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const groupBy_1 = __importDefault(require("lodash/groupBy"));
const currencies_1 = require("../../currencies");
const explorers_1 = require("../../explorers");
function round(n) {
if (n === undefined)
return undefined;
return Math.round(n);
}
function makeCSV(data) {
return data
.map(row => row.map(cell => `"${typeof cell === "number" ? cell : cell || ""}"`).join(","))
.join("\n");
}
function csvReports(reports, specsPerBots) {
const data = reports.map((report, i) => ({
report,
...specsPerBots[i],
}));
const columns = [
{
label: "seed",
get: (d) => d.seed,
},
{
label: "family",
get: (d) => d.family,
},
{
label: "coin",
get: (d) => d.key,
},
{
label: "Accounts count",
get: (d) => d.report.accountBalances?.length,
},
{
label: "Operations count",
get: (d) => d.report.accountOperationsLength?.reduce((a, b) => a + b, 0),
},
{
label: "Sync Time (ms)",
get: (d) => d.report.auditResult?.totalTime,
},
{
label: "cpuUserTime (ms)",
get: (d) => round(d.report.auditResult?.cpuUserTime),
},
{
label: "networkBandwidth (bytes)",
get: (d) => d.report.auditResult?.network.totalResponseSize,
},
{
label: "networkCount",
get: (d) => d.report.auditResult?.network.totalCount,
},
{
label: "accountsJSONSize (bytes)",
get: (d) => d.report.auditResult?.accountsJSONSize,
},
];
const csvs = [];
csvs.push({
filename: "csvs/all.csv",
content: makeCSV([columns.map(c => c.label), ...data.map(d => columns.map(c => c.get(d)))]),
});
const byFamily = (0, groupBy_1.default)(data, d => d.family);
for (const [family, data] of Object.entries(byFamily)) {
csvs.push({
filename: `csvs/by-family/${family}.csv`,
content: makeCSV([columns.map(c => c.label), ...data.map(d => columns.map(c => c.get(d)))]),
});
}
const byCurrency = (0, groupBy_1.default)(data, d => d.spec.currency.id);
for (const [currencyId, data] of Object.entries(byCurrency)) {
csvs.push({
filename: `csvs/by-currency/${currencyId}.csv`,
content: makeCSV([columns.map(c => c.label), ...data.map(d => columns.map(c => c.get(d)))]),
});
}
return csvs;
}
function finalMarkdownReport(reports, specsPerBots) {
const data = reports.map((report, i) => ({ report, ...specsPerBots[i] }));
const { table, title } = markdownHelpers(data);
let md = "";
md += title("Portfolio");
md += table({
title: "Funds status",
formatValue: d => {
const { spec } = d;
if (!spec)
return;
const viableAccounts = (d.report?.accountBalances || []).filter(b => (0, bignumber_js_1.default)(b).gt(spec.minViableAmount || 0)).length;
if (viableAccounts > 0)
return "✅";
const explorerView = (0, explorers_1.getDefaultExplorerView)(d.spec.currency);
const refillAddress = d.report.refillAddress || "";
const url = !refillAddress
? ""
: explorerView && refillAddress
? (0, explorers_1.getAddressExplorer)(explorerView, refillAddress)
: "data:text," + refillAddress;
const value = url;
if (url) {
return `[🙏](${url})`;
}
return value || "⚠️";
},
});
md += table({
title: "Balances",
lenseValue: d => (d.report?.accountBalances || []).reduce((acc, r) => acc.plus(r), (0, bignumber_js_1.default)(0)),
reduce: (d, map) => d.reduce((acc, r) => acc.plus(map(r)), (0, bignumber_js_1.default)(0)),
formatValue: (v, ctx) => (0, currencies_1.formatCurrencyUnit)(ctx.spec.currency.units[0], v, { showCode: true }),
totalPerCurrency: true,
});
md += table({
title: "Accounts count",
lenseValue: d => d.report?.accountBalances?.length || 0,
totalPerCurrency: true,
totalPerSeed: true,
totalPerFamily: true,
});
md += table({
title: "Operations count",
lenseValue: d => d.report?.accountOperationsLength?.reduce((a, b) => a + b || 0, 0) || 0,
totalPerCurrency: true,
totalPerSeed: true,
totalPerFamily: true,
});
md += title("Performance");
md += table({
title: "Sync Times",
lenseValue: d => d.report?.auditResult?.totalTime,
formatValue: v => formatTime(v),
totalPerCurrency: true,
totalPerSeed: true,
totalPerFamily: true,
});
md += table({
title: "HTTP Calls Count",
lenseValue: d => d.report?.auditResult?.network?.totalCount || 0,
totalPerCurrency: true,
totalPerSeed: true,
totalPerFamily: true,
});
md += table({
title: "HTTP Bandwidth",
lenseValue: d => d.report?.auditResult?.network?.totalResponseSize || 0,
formatValue: v => formatSize(v),
totalPerCurrency: true,
totalPerSeed: true,
totalPerFamily: true,
});
md += table({
title: "Duplicate HTTP Calls",
lenseValue: d => d.report?.auditResult?.network?.totalDuplicateRequests || 0,
totalPerCurrency: true,
totalPerSeed: true,
totalPerFamily: true,
});
md += table({
title: "Accounts Data Size",
lenseValue: d => d.report?.auditResult?.accountsJSONSize,
formatValue: v => formatSize(v),
totalPerCurrency: true,
totalPerSeed: true,
totalPerFamily: true,
});
md += table({
title: "Currency Preloaded Data Size",
lenseValue: d => d.report?.auditResult?.preloadJSONSize,
formatValue: v => formatSize(v),
totalPerCurrency: true,
totalPerSeed: true,
totalPerFamily: true,
});
md += table({
title: "CPU user time",
lenseValue: d => d.report?.auditResult?.cpuUserTime,
formatValue: v => formatTime(v),
totalPerCurrency: true,
totalPerSeed: true,
totalPerFamily: true,
});
/*
md += table({
title: "CPU system time",
lenseValue: (d) => d.report?.auditResult?.cpuSystemTime,
formatValue: (v) => formatTime(v),
totalPerCurrency: true,
totalPerSeed: true,
totalPerFamily: true,
});
*/
md += table({
title: "Memory RSS",
lenseValue: d => d.report?.auditResult?.memoryEnd.rss,
formatValue: v => formatSize(v),
});
md += table({
title: "JS Boot Time",
lenseValue: d => d.report?.auditResult?.jsBootTime,
formatValue: v => formatTime(v),
});
md += table({
title: "JS Slow Frames",
reduce: (d, map) => d.reduce((acc, r) => ({
count: acc.count + (map(r)?.count || 0),
duration: acc.duration + (map(r)?.duration || 0),
}), { count: 0, duration: 0 }),
lenseValue: d => d.report?.auditResult?.slowFrames,
formatValue: v => (v ? (v.count === 0 ? "✅" : `${v.count} (${formatTime(v.duration)})`) : ""),
totalPerCurrency: true,
totalPerSeed: true,
totalPerFamily: true,
});
const errors = data.filter(d => d.report.error);
if (errors.length) {
md += "\n\n# Errors\n";
const sortKey = d => `${d.spec.name}: ${d.report.error}`;
md += errors
.slice(0)
.sort((a, b) => sortKey(a).localeCompare(sortKey(b)))
.map(d => `- ${d.seed}: ${d.spec.name}: ${d.report.error}`)
.join("\n");
}
return md;
}
function formatTime(ms) {
if (!ms)
return;
const s = ms / 1000;
return s < 9 ? `${s.toFixed(2)}s` : `${s.toFixed(0)}s`;
}
function formatSize(bytes) {
if (!bytes)
return;
const kb = bytes / 1024;
return kb < 1024 ? `${kb.toFixed(0)}kb` : `${(kb / 1024).toFixed(0)}mb`;
}
function markdownHelpers(data) {
const seedNames = Array.from(new Set(data.map(d => d.seed)));
seedNames.sort();
// specs are assumed to be grouped by family already
const specs = Array.from(new Set(data.map(d => d.spec)));
function strong(txt) {
return "**" + txt + "**";
}
function title(txt) {
return "\n# " + txt + "\n\n";
}
function genTable(f, opts = {}) {
const { totalPerFamily, totalPerCurrency, totalPerSeed } = opts;
let md = "\n\n";
md += "| Currency |" + seedNames.join(" | ");
if (totalPerCurrency) {
md += "| Total ";
}
md += "|\n";
md += "|--|" + Array.from(seedNames).fill("--|").join("");
if (totalPerCurrency) {
md += "--|";
}
md += "\n";
let lastFamily = "";
for (const spec of specs) {
let soloInFamilyFactorisation = false;
if (totalPerFamily && spec.currency.family !== lastFamily) {
lastFamily = spec.currency.family;
soloInFamilyFactorisation = !specs.some(s => s !== spec && s.currency.family === lastFamily);
if (!soloInFamilyFactorisation) {
const familyName = (0, currencies_1.findCryptoCurrencyById)(spec.currency.family)?.name || spec.currency.family;
md += "| " + strong(familyName) + " family | ";
md += seedNames
.map(seed => {
const all = data.filter(d => d.seed === seed && d.spec.currency.family === lastFamily);
return (totalPerFamily(all) || "") + " |";
})
.join("");
if (totalPerCurrency) {
const all = data.filter(d => d.spec.currency.family === lastFamily);
md += (totalPerCurrency(all) || "") + " |";
}
md += "\n";
}
}
const name = spec.currency.name;
md += "| " + (soloInFamilyFactorisation ? strong(name) : name) + " | ";
md +=
seedNames
.map(seed => {
const d = data.find(d => d.seed === seed && d.spec === spec);
if (!d)
return "?";
if (d.report.error)
return "❌";
return f(d) || "";
})
.join(" | ") + " |";
if (totalPerCurrency) {
md += (totalPerCurrency(data.filter(d => d.spec === spec)) || "") + " |";
}
md += "\n";
}
if (totalPerSeed) {
md += "| **Total** | ";
md += seedNames
.map(seed => {
const all = data.filter(d => d.seed === seed);
return (totalPerSeed(all) || "") + " |";
})
.join("");
if (totalPerCurrency) {
const all = data;
md += (totalPerCurrency(all) || "") + " |";
}
md += "\n";
}
md += "\n";
return md;
}
// as any is a hack to make a "defaults" that would work for most case. we will need to see how to make typing better
const defaults = {
lenseValue: (d) => d,
formatValue: (v) => String(v),
reduce: (v, map) => v.reduce((sum, v) => sum + Number(map(v)), 0),
};
const table = opts => {
const { title, lenseValue, formatValue, totalPerFamily, totalPerCurrency, totalPerSeed, reduce, } = { ...defaults, ...opts };
const total = d => formatValue(reduce(d, lenseValue), d[0]);
let md = `\n<details><summary><b>${title}</b></summary>\n`;
md += genTable(d => formatValue(lenseValue(d), d), {
totalPerFamily: totalPerFamily ? total : undefined,
totalPerCurrency: totalPerCurrency ? total : undefined,
totalPerSeed: totalPerSeed ? total : undefined,
});
md += "</details>\n\n";
return md;
};
return { table, strong, title };
}
//# sourceMappingURL=formatter.js.map