From 7055fe38df360574a5e3cdeb3c3a9a3226609ae3 Mon Sep 17 00:00:00 2001 From: Juan Cortes Ross Date: Fri, 13 Dec 2019 16:04:25 +0100 Subject: [PATCH 1/3] Sorting and filtering utils for the managerv2 for mobile and desktop --- src/apps/filtering.js | 105 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 101 insertions(+), 4 deletions(-) diff --git a/src/apps/filtering.js b/src/apps/filtering.js index 46741ae30e..0e1737f702 100644 --- a/src/apps/filtering.js +++ b/src/apps/filtering.js @@ -1,10 +1,107 @@ // @flow import type { App } from "../types/manager"; +import type { InstalledItem } from "./types"; +import { getCryptoCurrencyById, isCurrencySupported } from "../currencies"; -export type SortOptions = *; +export type SortOptions = { + type: "name" | "marketcap", + order: "asc" | "desc", + marketcapTickers?: string[] +}; -export const sortApps = (apps: App[], _options: SortOptions): App[] => apps; +export type FilterOptions = { + query?: string, + installedApps: InstalledItem[], + type: "all" | "installed" | "not_installed" | "supported" | "updatable" +}; -export type FilterOptions = *; +type UpdateAwareInstalledApps = { + [string]: boolean // NB [AppName]: isUpdated +}; -export const filterApps = (apps: App[], _options: FilterOptions): App[] => apps; +type ScoredApps = { + [string]: number // NB [AppName]: computed score for sorting +}; + +const searchFilter = (query?: string) => ({ name, currencyId }) => { + if (!query) return true; + const currency = currencyId ? getCryptoCurrencyById(currencyId) : null; + const terms = `${name} ${ + currency ? `${currency.name} ${currency.ticker}` : "" + }`; + return terms.toLowerCase().includes(query.toLowerCase().trim()); +}; + +const typeFilter = ( + type = "all", + updateAwareInstalledApps: UpdateAwareInstalledApps +) => app => { + switch (type) { + case "installed": + return updateAwareInstalledApps.hasOwnProperty(app.name); + case "not_installed": + return !updateAwareInstalledApps.hasOwnProperty(app.name); + case "updatable": + return ( + updateAwareInstalledApps.hasOwnProperty(app.name) && + !updateAwareInstalledApps[app.name] + ); + case "supported": + return ( + app.currencyId && + isCurrencySupported(getCryptoCurrencyById(app.currencyId)) + ); + default: + return true; + } +}; + +export const sortApps = (apps: App[], _options: SortOptions): App[] => { + const { type, marketcapTickers, order } = _options; + const asc = order === "asc"; + const sortedApps = [...apps]; + const marketcapScoreBase = 10e6; + + const scoredApps: ScoredApps = sortedApps.reduce( + (names, { name, currencyId }) => { + let appScore = 0; + + if (marketcapTickers && type === "marketcap" && currencyId) { + const index = marketcapTickers.indexOf( + getCryptoCurrencyById(currencyId).ticker + ); + + appScore += index >= 0 ? marketcapScoreBase - 1000 * index : 0; + } + + // By name + appScore += name[0].toLowerCase().charCodeAt(0); + + return { ...names, [name]: appScore * (asc ? 1 : -1) }; + }, + {} + ); + + return sortedApps.sort( + (app1, app2) => scoredApps[app1.name] - scoredApps[app2.name] + ); +}; + +export const filterApps = (apps: App[], _options: FilterOptions): App[] => { + const { query, installedApps, type = "all" } = _options; + const updateAwareInstalledApps = installedApps.reduce( + (names, { name }) => ({ ...names, [name]: true }), + {} + ); + return apps + .filter(searchFilter(query)) + .filter(typeFilter(type, updateAwareInstalledApps)); +}; + +export const sortFilterApps = ( + apps: App[], + _filterOptions: FilterOptions, + _sortOptions: SortOptions +): App[] => { + return sortApps(filterApps(apps, _filterOptions), _sortOptions); +}; From 293cd2b02eba648c9d41a68b744c09fb5a3e2027 Mon Sep 17 00:00:00 2001 From: Juan Cortes Ross Date: Mon, 16 Dec 2019 13:42:10 +0100 Subject: [PATCH 2/3] Remove scoring system in favour of indexOfMarketCap Adds use hook and memoized the result, also removes the reduce for @gre --- src/apps/filtering.js | 69 +++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/src/apps/filtering.js b/src/apps/filtering.js index 0e1737f702..5af5977c03 100644 --- a/src/apps/filtering.js +++ b/src/apps/filtering.js @@ -2,11 +2,11 @@ import type { App } from "../types/manager"; import type { InstalledItem } from "./types"; import { getCryptoCurrencyById, isCurrencySupported } from "../currencies"; +import { useMemo } from "react"; export type SortOptions = { - type: "name" | "marketcap", - order: "asc" | "desc", - marketcapTickers?: string[] + type: "name" | "marketcap" | "default", + order: "asc" | "desc" }; export type FilterOptions = { @@ -19,10 +19,6 @@ type UpdateAwareInstalledApps = { [string]: boolean // NB [AppName]: isUpdated }; -type ScoredApps = { - [string]: number // NB [AppName]: computed score for sorting -}; - const searchFilter = (query?: string) => ({ name, currencyId }) => { if (!query) return true; const currency = currencyId ? getCryptoCurrencyById(currencyId) : null; @@ -57,42 +53,29 @@ const typeFilter = ( }; export const sortApps = (apps: App[], _options: SortOptions): App[] => { - const { type, marketcapTickers, order } = _options; + const { type, order } = _options; const asc = order === "asc"; - const sortedApps = [...apps]; - const marketcapScoreBase = 10e6; - - const scoredApps: ScoredApps = sortedApps.reduce( - (names, { name, currencyId }) => { - let appScore = 0; - - if (marketcapTickers && type === "marketcap" && currencyId) { - const index = marketcapTickers.indexOf( - getCryptoCurrencyById(currencyId).ticker - ); - - appScore += index >= 0 ? marketcapScoreBase - 1000 * index : 0; - } + if (type === "default") return apps; - // By name - appScore += name[0].toLowerCase().charCodeAt(0); + const getScore = ({ indexOfMarketCap: i }: App, reverse: boolean) => + i === -1 ? (reverse ? -10e6 : 10e6) : i; - return { ...names, [name]: appScore * (asc ? 1 : -1) }; - }, - {} - ); - - return sortedApps.sort( - (app1, app2) => scoredApps[app1.name] - scoredApps[app2.name] - ); + return [...apps].sort((a1, b1) => { + const [a, b] = asc ? [a1, b1] : [b1, a1]; + let diff = 0; + if (type === "marketcap") diff = getScore(b, asc) - getScore(a, asc); + if (diff === 0) diff = a.name.localeCompare(b.name); + return diff; + }); }; export const filterApps = (apps: App[], _options: FilterOptions): App[] => { const { query, installedApps, type = "all" } = _options; - const updateAwareInstalledApps = installedApps.reduce( - (names, { name }) => ({ ...names, [name]: true }), - {} - ); + const updateAwareInstalledApps: UpdateAwareInstalledApps = {}; + for (let i = 0; i < installedApps.length; i++) { + updateAwareInstalledApps[installedApps[i].name] = installedApps[i].updated; + } + return apps .filter(searchFilter(query)) .filter(typeFilter(type, updateAwareInstalledApps)); @@ -105,3 +88,17 @@ export const sortFilterApps = ( ): App[] => { return sortApps(filterApps(apps, _filterOptions), _sortOptions); }; + +export const useSortedFilteredApps = ( + apps: App[], + _filterOptions: FilterOptions, + _sortOptions: SortOptions +) => { + const { query, installedApps, type: filterType } = _filterOptions; + const { type: sortType, order } = _sortOptions; + + return useMemo( + () => sortApps(filterApps(apps, _filterOptions), _sortOptions), + [apps, query, installedApps, filterType, sortType, order] + ); +}; From 0bce193df0f1b5cbab0f2b284bf2d1cadca47d4f Mon Sep 17 00:00:00 2001 From: Juan Cortes Ross Date: Tue, 17 Dec 2019 15:00:35 +0100 Subject: [PATCH 3/3] Adds tests to the filtering/sorting functionality on managerv2 --- src/apps/filtering.js | 7 ++++++- src/apps/mock.js | 7 ++++++- src/countervalues/mock.js | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/apps/filtering.js b/src/apps/filtering.js index 5af5977c03..ef601ab21f 100644 --- a/src/apps/filtering.js +++ b/src/apps/filtering.js @@ -98,7 +98,12 @@ export const useSortedFilteredApps = ( const { type: sortType, order } = _sortOptions; return useMemo( - () => sortApps(filterApps(apps, _filterOptions), _sortOptions), + () => + sortFilterApps( + apps, + { query, installedApps, type: filterType }, + { type: sortType, order } + ), [apps, query, installedApps, filterType, sortType, order] ); }; diff --git a/src/apps/mock.js b/src/apps/mock.js index 94c35431bc..9bb4e37df2 100644 --- a/src/apps/mock.js +++ b/src/apps/mock.js @@ -8,6 +8,7 @@ import { getDependencies, getDependents } from "./polyfill"; import { findCryptoCurrency } from "../currencies"; import type { ListAppsResult, AppOp, Exec, InstalledItem } from "./types"; import type { App, DeviceInfo, FinalFirmware } from "../types/manager"; +import { tickersByMarketCap } from "../countervalues/mock"; export const deviceInfo155 = { version: "1.5.5", @@ -76,6 +77,10 @@ export function mockListAppsResult( .map((name, i) => { const dependencies = getDependencies(name); const currency = findCryptoCurrency(c => c.managerAppName === name); + const indexOfMarketCap = currency + ? tickersByMarketCap.indexOf(currency.ticker) + : -1; + return { id: i, app: i, @@ -99,7 +104,7 @@ export function mockListAppsResult( dateModified: "", compatibleWallets: [], currencyId: currency ? currency.id : null, - indexOfMarketCap: 0 + indexOfMarketCap }; }); const appByName = {}; diff --git a/src/countervalues/mock.js b/src/countervalues/mock.js index 07cc0936ae..d1fb36aaa1 100644 --- a/src/countervalues/mock.js +++ b/src/countervalues/mock.js @@ -133,7 +133,7 @@ export const fetchExchangesForPairImplementation = async () => { ]; }; -const tickersByMarketCap = [ +export const tickersByMarketCap = [ "BTC", "ETH", "XRP",