diff --git a/.env.example b/.env.example index 530e905..5b10716 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,6 @@ REACT_APP_GRAPH_NETWORK='Mainnet' REACT_APP_GRAPH_HTTP='https://api.thegraph.com/subgraphs/name/protofire/makerdao-governance' REACT_APP_MKR_GRAPH_HTTP='https://api.thegraph.com/subgraphs/name/protofire/mkr-registry' -REACT_APP_ETHERSCAN_API_KEY='XQ2QTEM7H4KX7AQTE9JWXD3HWTTZ46TTU9' \ No newline at end of file +REACT_APP_ETHERSCAN_API_KEY='XQ2QTEM7H4KX7AQTE9JWXD3HWTTZ46TTU9' +REACT_APP_LAST_CACHE_UPDATE='1587733935' +REACT_APP_HOME_DATA_TTL='5' # IN MINUTES diff --git a/netlify.toml b/netlify.toml index 92da4c0..7c43b22 100644 --- a/netlify.toml +++ b/netlify.toml @@ -12,15 +12,21 @@ REACT_APP_GRAPH_WS = "wss://api.thegraph.com/subgraphs/name/protofire/makerdao-governance" REACT_APP_MKR_GRAPH_HTTP='https://api.thegraph.com/subgraphs/name/protofire/mkr-registry' REACT_APP_ETHERSCAN_API_KEY='XQ2QTEM7H4KX7AQTE9JWXD3HWTTZ46TTU9' + REACT_APP_LAST_CACHE_UPDATE='1587733935' + REACT_APP_HOME_DATA_TTL='5' # IN MINUTES [context.branch-deploy.environment] REACT_APP_GRAPH_HTTP = "https://api.thegraph.com/subgraphs/name/protofire/makerdao-governance" REACT_APP_GRAPH_WS = "wss://api.thegraph.com/subgraphs/name/protofire/makerdao-governance" REACT_APP_MKR_GRAPH_HTTP='https://api.thegraph.com/subgraphs/name/protofire/mkr-registry' REACT_APP_ETHERSCAN_API_KEY='XQ2QTEM7H4KX7AQTE9JWXD3HWTTZ46TTU9' + REACT_APP_LAST_CACHE_UPDATE='1587733935' + REACT_APP_HOME_DATA_TTL='5' # IN MINUTES [context.deploy-preview.environment] - REACT_APP_GRAPH_HTTP = "https://api.thegraph.com/subgraphs/name/lmcorbalan/makerdao-governance" - REACT_APP_GRAPH_WS = "wss://api.thegraph.com/subgraphs/name/lmcorbalan/makerdao-governance" + REACT_APP_GRAPH_HTTP = "https://api.thegraph.com/subgraphs/name/protofire/makerdao-governance" + REACT_APP_GRAPH_WS = "wss://api.thegraph.com/subgraphs/name/protofire/makerdao-governance" REACT_APP_MKR_GRAPH_HTTP='https://api.thegraph.com/subgraphs/name/protofire/mkr-registry' REACT_APP_ETHERSCAN_API_KEY='XQ2QTEM7H4KX7AQTE9JWXD3HWTTZ46TTU9' + REACT_APP_LAST_CACHE_UPDATE='1587733935' + REACT_APP_HOME_DATA_TTL='5' # IN MINUTES diff --git a/package-lock.json b/package-lock.json index 95135dd..68a4890 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8768,6 +8768,11 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, "immer": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", @@ -11091,6 +11096,14 @@ "type-check": "~0.3.2" } }, + "lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", + "requires": { + "immediate": "~3.0.5" + } + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -11640,6 +11653,14 @@ } } }, + "localforage": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.7.3.tgz", + "integrity": "sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ==", + "requires": { + "lie": "3.1.1" + } + }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -19053,6 +19074,14 @@ } } }, + "save-as-file": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/save-as-file/-/save-as-file-0.2.0.tgz", + "integrity": "sha512-GA9bIaIL9XNjg1Mh8+/oCrej+45znfr/9zp/74q8s4VKmoRLobmYW5YeTvCTyI50vDCgFiBmfdaVvskPl6RUug==", + "requires": { + "tslib": "^1.9.0" + } + }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", diff --git a/package.json b/package.json index 7f2526e..a60902b 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "gray-matter": "^4.0.2", "install": "^0.13.0", "ipfs-only-hash": "^1.0.2", + "localforage": "^1.7.3", "lscache": "^1.3.0", "match-sorter": "^4.0.2", "npm": "^6.11.3", @@ -46,6 +47,7 @@ "react-tooltip": "^3.11.1", "recharts": "^1.7.1", "sanitize.css": "^11.0.0", + "save-as-file": "^0.2.0", "styled-components": "^4.3.2", "subscriptions-transport-ws": "^0.9.16", "typescript": "3.6.2" diff --git a/src/components/Home/HomeDetail.tsx b/src/components/Home/HomeDetail.tsx index 211df28..7ea64f1 100644 --- a/src/components/Home/HomeDetail.tsx +++ b/src/components/Home/HomeDetail.tsx @@ -27,7 +27,7 @@ import { PageTitle, PageSubTitle, } from '../common' -import { getPollsData, getMKRSupply } from '../../utils/makerdao' +import { getPollsMetaData } from '../../utils/makerdao' import { getModalContainer, getPollData, getPollsBalances } from '../../utils' import { getStakedMkrData, @@ -50,6 +50,7 @@ import { //getActivenessBreakdown, getMostVotedPolls, getRecentPolls, + getMKRResponsiveness, } from './helpers' import styled from 'styled-components' import LinkableComponent from '../common/LinkableComponent' @@ -72,6 +73,7 @@ const Loading = () => ( const getParticipation = (data, mkrSupply) => { const totalMkr: BigNumber = data.reduce((acc, value) => acc.plus(new BigNumber(value.mkr)), new BigNumber('0')) + return totalMkr .times(100) .div(mkrSupply) @@ -89,71 +91,75 @@ type Props = { } function HomeDetail(props: Props) { - const { data, gData, history, executivesResponsiveness } = props + const { data, gData, history } = props const { governanceInfo } = gData const [isModalOpen, setModalOpen] = useState(false) - const cachedDataPoll = lscache.get('home-polls') || [] - const cachedMkrSupply = lscache.get('mkr-supply') || undefined - - const cachedDataTopVoters = lscache.get('home-topVoters') || [] - const cachedDataPollsResponsiveness = lscache.get('polls-responsiveness') || [] const [isModalChart, setModalChart] = useState(false) const [chartFilters, setChartFilters] = useState(defaultFilters) - const [mkrSupply, setMkrSupply] = useState(cachedMkrSupply) - const [pollsBalances, setBalances] = useState({}) const [modalData, setModalData] = useState({ type: '', component: '' }) - const [topVoters, setTopVoters] = useState(cachedDataTopVoters) - const [pollsResponsiveness, setPollsResponsiveness] = useState(cachedDataPollsResponsiveness) - //const [activenessBreakdown, setActivenessBreakdown] = useState([]) - // const [mkrActiveness, setMkrActiveness] = useState([]) + + const [pollsResponsiveness, setPollsResponsiveness] = useState([]) + const [topVoters, setTopVoters] = useState([]) + const [polls, setPolls] = useState([]) const [mostVotedPolls, setMostVotedPolls] = useState([]) - const [stakedMkr, setStakedMkr] = useState([]) const [recentPolls, setRecentPolls] = useState([]) - const [polls, setPolls] = useState(cachedDataPoll.length === 0 ? data.polls : cachedDataPoll) - const pollcolumns = expanded => Pollcolumns(expanded) - const votedPollcolumns = () => VotedPollcolumns() - - useEffect(() => { - if (cachedDataPoll.length === 0) getPollsBalances(polls).then(balances => setBalances(balances)) - if (cachedDataPollsResponsiveness.length === 0) - getPollsMKRResponsiveness(polls).then(responsiveness => setPollsResponsiveness(responsiveness)) - }, [polls, cachedDataPoll.length, cachedDataPollsResponsiveness.length]) - - useEffect(() => { - if (!mkrSupply) { - getMKRSupply().then(supply => setMkrSupply(supply)) - } - }, [mkrSupply]) - const executiveColumns = expanded => Executivecolumns(expanded) const topVotersColumns = () => TopVotersColumns() + const votedPollcolumns = () => VotedPollcolumns() const uncastedExecutiveColumns = () => UncastedExecutivecolumns() - //const activenessBreakdownColumns = () => ActivenessBreakdownColumns() - const executives = data.executives + useEffect(() => { + if (data && data.polls.length && data.executives.length && data.mkrSupply) { + getPollsBalances(data.polls).then(votersSnapshots => { + // TODO - improve function naming (snapshots of acctual voting addresses) + getPollsMetaData(data.polls).then(polls => { + Promise.all( + polls.map(poll => { + return getPollData(poll, votersSnapshots).then(pollData => { + return { ...poll, participation: getParticipation(pollData, data.mkrSupply) } + }) + }), + ).then(pollsWithPluralityAndParticipation => { + getPollsMKRResponsiveness(polls).then(responsiveness => { + // TODO - are all this state needed? + setPollsResponsiveness(responsiveness) + setPolls(pollsWithPluralityAndParticipation) + setTopVoters(getTopVoters(data.executives, pollsWithPluralityAndParticipation)) + setMostVotedPolls(getMostVotedPolls(pollsWithPluralityAndParticipation)) + setRecentPolls(getRecentPolls(pollsWithPluralityAndParticipation)) + }) + }) + }) + }) + } + }, [data]) + ////////////////// + const [stakedMkr, setStakedMkr] = useState([]) useEffect(() => { setStakedMkr(getStakedMkrData(data, chartFilters.stakedMkr)) }, [data, chartFilters.stakedMkr]) + const giniData = useMemo(() => getGiniData([...data.free, ...data.lock], chartFilters.gini), [ + data, + chartFilters.gini, + ]) + + const cachedDataExecutivesResponsiveness = lscache.get('executives-responsiveness') || [] + const [executivesResponsiveness, setExecutivesResponsiveness] = useState(cachedDataExecutivesResponsiveness) useEffect(() => { - if (cachedDataTopVoters.length === 0) { - setTopVoters(getTopVoters(executives, polls)) + if (data && data.executives && cachedDataExecutivesResponsiveness.length === 0) { + const responsiveness = getMKRResponsiveness(data.executives) + lscache.set('executives-responsiveness', responsiveness, DEFAULT_CACHE_TTL) + setExecutivesResponsiveness(responsiveness) } - }, [executives, polls, cachedDataTopVoters.length]) + }, [data, cachedDataExecutivesResponsiveness.length]) - useEffect(() => { - setMostVotedPolls(getMostVotedPolls(polls)) - setRecentPolls(getRecentPolls(polls)) - }, [polls]) - useEffect(() => { - //setActivenessBreakdown(getActivenessBreakdown(executives)) - //setMkrActiveness(getMKRActiveness(executives)) - }, [executives]) + ///////////////// const getPoll = row => { if (row.id) history.push(`/poll/${row.id}`) @@ -163,8 +169,6 @@ function HomeDetail(props: Props) { if (row.id) history.push(`/executive/${row.id}`) } - // Data map for building this page - const giniData = getGiniData([...data.free, ...data.lock], chartFilters.gini) const homeMap = { table: { polls: { @@ -315,7 +319,7 @@ function HomeDetail(props: Props) { ), }, mkrDistributionPerExecutive: { - data: getMkrDistributionPerExecutive(executives, governanceInfo ? governanceInfo.hat : null), + data: getMkrDistributionPerExecutive(data.executives, governanceInfo ? governanceInfo.hat : null), component: props => ( { - if (mkrSupply) { - getPollsData(data.polls).then(result => { - const polls = result.filter(Boolean) - setPolls([...polls]) - Promise.all( - polls.map(poll => { - return getPollData(poll, pollsBalances).then(data => { - return { ...poll, participation: getParticipation(data, mkrSupply) } - }) - }), - ).then(pollsWithPluralityAndParticipation => { - setPolls(pollsWithPluralityAndParticipation) - }) - }) - } - }, [data.polls, mkrSupply, pollsBalances]) - - useEffect(() => { - lscache.set('mkr-supply', mkrSupply, DEFAULT_CACHE_TTL) - lscache.set('home-polls', polls, DEFAULT_CACHE_TTL) - lscache.set('home-topVoters', topVoters, DEFAULT_CACHE_TTL) - lscache.set('polls-responsiveness', pollsResponsiveness, DEFAULT_CACHE_TTL) - }, [mkrSupply, polls, topVoters, pollsResponsiveness]) - return ( <> System Statistics diff --git a/src/components/Home/helpers.tsx b/src/components/Home/helpers.tsx index 2306f76..dc470a6 100644 --- a/src/components/Home/helpers.tsx +++ b/src/components/Home/helpers.tsx @@ -15,6 +15,7 @@ import { timeLeft, getPollsBalances, getVotersBalance, + msToSeconds, } from '../../utils' import { LAST_YEAR, @@ -91,7 +92,6 @@ export const getStakedMkrData = (data: any, time: string) => { mkrEvents.map(events => events.filter(({ timestamp }) => timestamp > period.from && timestamp <= period.to)), totalSupply, ) - //console.log(period, totalSupply) // Calculate total MKR staked/voting in the period ;({ totalStaked, totalVoting, voters } = calculateVotingMkr( @@ -513,8 +513,8 @@ export const getPollsMKRResponsiveness = async polls => { const voteEvents = {} const days = Math.max( ...polls.map(poll => { - const start = poll.startDate >= 1e12 ? (poll.startDate / 1e3).toFixed(0) : poll.startDate - const end = poll.endDate >= 1e12 ? (poll.endDate / 1e3).toFixed(0) : poll.endDate + const start = msToSeconds(poll.startDate) + const end = msToSeconds(poll.endDate) const diffDays = differenceInDays(fromUnixTime(end), fromUnixTime(start)) return diffDays diff --git a/src/components/List/helpers.tsx b/src/components/List/helpers.tsx index 060a639..a425eec 100644 --- a/src/components/List/helpers.tsx +++ b/src/components/List/helpers.tsx @@ -18,7 +18,7 @@ export const Pollcolumns = () => { Filter: SelectColumnFilter, filter: 'includes', accessor: row => (timeLeft(row.endDate) === 'Ended' ? 'Ended' : 'Active'), - Cell: ({ row }) => (row.original.fetched ? timeLeft(row.original.endDate) : ), + Cell: ({ row }) => timeLeft(row.original.endDate), width: 100, }, { @@ -80,8 +80,7 @@ export const Pollcolumns = () => { disableFilters: true, id: 'date', sortType: 'datetime', - Cell: ({ row }) => - row.original.fetched ? format(fromUnixTime(row.original.startDate), 'dd MMM yy') : , + Cell: ({ row }) => format(fromUnixTime(row.original.startDate), 'dd MMM yy'), width: 100, }, { @@ -90,7 +89,7 @@ export const Pollcolumns = () => { disableFilters: true, separator: true, sortType: 'datetime', - Cell: ({ row }) => (row.original.fetched ? format(fromUnixTime(row.original.endDate), 'dd MMM yy') : ), + Cell: ({ row }) => format(fromUnixTime(row.original.endDate), 'dd MMM yy'), width: 100, }, ] diff --git a/src/components/PollDetails/data.ts b/src/components/PollDetails/data.ts index a78f52b..2c42962 100644 --- a/src/components/PollDetails/data.ts +++ b/src/components/PollDetails/data.ts @@ -1,5 +1,6 @@ import { request } from 'graphql-request' import { BigNumber } from 'bignumber.js' +import { getPollVotersRegistries, getStakedByPoll, stakedByAddress, msToSeconds, getVotersSnapshots } from '../../utils' const GOVERNANCE_API_URI = process.env.REACT_APP_GRAPH_HTTP const MKR_API_URI = process.env.REACT_APP_MKR_GRAPH_HTTP @@ -16,43 +17,6 @@ export const getPollVotersPerOption = poll => { }, {}) } -export const getVoterRegistries = async (addresses, endDate) => { - // The query can't be called with empty array because it errors - if (!addresses.length) { - return [] - } - - const query = ` - query getVoterRegistries($voters: [Bytes!]!, $endDate: BigInt! ){ - hot: voterRegistries(first: 1000, where: {hotAddress_in: $voters, timestamp_lte: $endDate}) { - id - coldAddress - hotAddress - voteProxies { - id - } - } - cold: voterRegistries(first: 100, where: {coldAddress_in: $voters, timestamp_lte: $endDate}) { - id - coldAddress - hotAddress - voteProxies { - id - } - } - } - ` - const result: any = await fetchQuery(GOVERNANCE_API_URI, query, { - voters: addresses, - endDate, - }) - - // When cold and hot are the same the registry cames twice, so we keep only one of them - const registriesById = new Map([...result.cold, ...result.hot].map(reg => [reg.id, reg])) - - return Array.from(registriesById.values()) -} - export const getVoterAddresses = poll => { const pollVoters = getPollVotersPerOption(poll) return Object.keys(pollVoters).flatMap(option => pollVoters[option]) @@ -62,61 +26,18 @@ export const getVoteProxies = registries => { return Array.from(new Set(registries.flatMap((el: any) => el.voteProxies.flatMap(p => p.id)))) } -export const stakedByAddress = data => { - const result = [...data.free, ...data.lock].reduce((acc, el) => { - let current = acc[el.sender] || new BigNumber('0') - current = el.type === 'FREE' ? current.minus(new BigNumber(el.wad)) : current.plus(new BigNumber(el.wad)) - return { - ...acc, - [el.sender]: current, - } - }, {}) - - return result -} - -export const getStakedByAddress = async (addresses, endDate) => { - // The query can't be called with empty array because it errors - if (!addresses.length) { - return { - free: [], - lock: [], - } - } - - const query = ` - query getStakedByAddress($voters: [Bytes!]!, $endDate: BigInt! ) { - lock: actions(first: 1000, where: {type: LOCK, sender_in: $voters, timestamp_lte: $endDate}) { - sender - type - wad - } - free: actions(first: 1000, where: {type: FREE, sender_in: $voters, timestamp_lte: $endDate}) { - sender - type - wad - } - } - ` - const result: any = await fetchQuery(GOVERNANCE_API_URI, query, { - voters: addresses, - endDate, - }) - - return result -} - export const getPollDataWithoutBalances = async poll => { const votersAddresses = getVoterAddresses(poll) - const voteRegistries = await getVoterRegistries(votersAddresses, poll.endDate) + const voteRegistries = await getPollVotersRegistries(poll) const voteProxies = getVoteProxies(voteRegistries) - const stakedProxies = stakedByAddress(await getStakedByAddress(voteProxies, poll.endDate)) - const stakedVoters = stakedByAddress(await getStakedByAddress(votersAddresses, poll.endDate)) + const stakeByPoll = await getStakedByPoll(voteProxies, votersAddresses, poll) + const stakedProxies = stakedByAddress(stakeByPoll.proxies) + const stakedVoters = stakedByAddress(stakeByPoll.voters) const hotCold = Array.from(new Set(voteRegistries.flatMap((el: any) => [el.coldAddress, el.hotAddress]))) const votersHotCold = Array.from(new Set([...votersAddresses, ...hotCold])) - const balances = getBalanceByAccount(await getAccountBalances(votersHotCold, poll.endDate)) + const balances = getBalanceByAccount(await getVotersSnapshots(votersHotCold, msToSeconds(poll.endDate))) const stakedVotersAndBalances = votersHotCold.reduce((acc, key) => { const staked = stakedVoters[key] || ZERO @@ -169,48 +90,14 @@ export const getPollDataWithoutBalances = async poll => { return ret } -// This is used to get final results for a given poll, so it's ok to get only the last snapshot -const getAccountBalances = async (addresses, endDate) => { - // Query - const query = ` - query getAccountBalances($voter: Bytes!, $endDate: BigInt! ) { - accountBalanceSnapshots( - first: 1, - where:{ - account: $voter, - timestamp_lte: $endDate - }, - orderBy: timestamp, orderDirection: desc - ) { - account { - address - } - amount - timestamp - } - } - ` - - const result: any = await Promise.all( - addresses.map(address => - fetchQuery(MKR_API_URI, query, { - voter: address, - endDate, - }), - ), - ) - - return result -} - const getBalanceByAccount = balances => { - return balances.reduce((acc, el) => { - const snapshot = el.accountBalanceSnapshots[0] + return balances.reduce((acc, accountSnapshots) => { + const lastSnapshot = accountSnapshots[0] - if (snapshot) { + if (lastSnapshot) { return { ...acc, - [snapshot.account.address]: snapshot.amount, + [lastSnapshot.account.address]: lastSnapshot.amount, } } else { return acc diff --git a/src/components/PollDetails/helpers.tsx b/src/components/PollDetails/helpers.tsx index a0a921f..4d2fee3 100644 --- a/src/components/PollDetails/helpers.tsx +++ b/src/components/PollDetails/helpers.tsx @@ -9,7 +9,15 @@ import { differenceInHours, isAfter, } from 'date-fns' -import { shortenAccount, timeLeft, getVoterBalances, getVotersBalance, getPollData, getTimeOpened } from '../../utils' +import { + shortenAccount, + timeLeft, + getVotersSnapshots, + getVotersBalance, + getPollData, + getTimeOpened, + msToSeconds, +} from '../../utils' import { getVoterAddresses, getPollDataWithoutBalances } from './data' import { LAST_YEAR } from '../../constants' import { AddressNav } from '../common' @@ -179,7 +187,7 @@ export const getPollVotersHistogramData = poll => { } const getAllBalances = async poll => { - const endPoll = fromUnixTime(poll.endDate) + const endPoll = fromUnixTime(msToSeconds(poll.endDate)) const now = new Date() const end = isAfter(endPoll, now) ? now : endPoll const allVoters = Array.from( @@ -194,7 +202,7 @@ const getAllBalances = async poll => { ), ) - const allBalances = await Promise.all(allVoters.map(addr => getVoterBalances(addr, getUnixTime(end)))) + const allBalances = await getVotersSnapshots(allVoters, getUnixTime(end)) return allBalances.flat().reduce((lookup, snapshot: any) => { const account = snapshot.account.address const balances = lookup[account] || [] diff --git a/src/components/PollDetails/index.tsx b/src/components/PollDetails/index.tsx index 3729ebb..3ca2f7b 100644 --- a/src/components/PollDetails/index.tsx +++ b/src/components/PollDetails/index.tsx @@ -81,15 +81,22 @@ function PollDetails(props: Props) { }, [poll]) useEffect(() => { - if (pollPerOptionCached.length === 0) getPollPerOptionData(poll).then(data => setPollPerOptionData(data)) - - if (mkrDistributionCached.length === 0) getPollMakerHistogramData(poll).then(data => setMkrDistributionData(data)) - }, [poll, pollPerOptionCached.length, mkrDistributionCached.length]) + if (!pollPerOptionCached.length) { + getPollPerOptionData(poll).then(data => { + lscache.set(`pollPerOption-${poll.id}`, data, DEFAULT_CACHE_TTL) + setPollPerOptionData(data) + }) + } + }, [poll, pollPerOptionCached]) useEffect(() => { - lscache.set(`mkrDistribution-${poll.id}`, mkrDistributionData, DEFAULT_CACHE_TTL) - lscache.set(`pollPerOption-${poll.id}`, pollPerOptionData, DEFAULT_CACHE_TTL) - }, [mkrDistributionData, pollPerOptionData, poll.id]) + if (!mkrDistributionCached.length) { + getPollMakerHistogramData(poll).then(data => { + lscache.set(`mkrDistribution-${poll.id}`, data, DEFAULT_CACHE_TTL) + setMkrDistributionData(data) + }) + } + }, [poll, mkrDistributionCached]) const voteMap = { table: { diff --git a/src/components/common/LinkableComponent/index.tsx b/src/components/common/LinkableComponent/index.tsx index 1f3d8a0..390c39f 100644 --- a/src/components/common/LinkableComponent/index.tsx +++ b/src/components/common/LinkableComponent/index.tsx @@ -13,7 +13,10 @@ export default function LinkableComponent({ children, id }) { (fn: Function) => { if (initialized) return - fn() + if (fn) { + fn() + } + setInitialized(true) }, [initialized], diff --git a/src/constants/index.tsx b/src/constants/index.tsx index a0d7893..9aee6f3 100644 --- a/src/constants/index.tsx +++ b/src/constants/index.tsx @@ -20,3 +20,4 @@ export const DEFAULT_FETCH_ROWS = 300 //Polls list export const DEFAULT_CACHE_TTL = 30 // in minutes +export const INFINITE_CACHE_TTL = 10 * 365 * 24 * 60 // 10 years in minutes diff --git a/src/containers/App/index.tsx b/src/containers/App/index.tsx index afcbfae..d8412cd 100644 --- a/src/containers/App/index.tsx +++ b/src/containers/App/index.tsx @@ -1,8 +1,9 @@ -import React from 'react' +import React, { useState, useEffect } from 'react' import { useQuery } from '@apollo/react-hooks' import { Switch, Route } from 'react-router-dom' import { ThemeProvider } from 'styled-components' import GlobalStyle, { theme } from '../../theme/globalStyle' +import store from '../../utils/cache' // Pages import Home from '../Home' @@ -20,6 +21,9 @@ import Header from '../../components/common/Header' // Queries import { GOVERNANCE_INFO_QUERY } from './queries' +import { fromUnixTime } from 'date-fns' +import { isBefore } from 'date-fns/esm' +import { FullLoading } from '../../components/common' const items = [ { to: '/', label: 'DASHBOARD' }, @@ -28,9 +32,31 @@ const items = [ { to: '/voting-history', label: 'VOTING HISTORY' }, ] +const LAST_CACHE_UPDATE = process.env.REACT_APP_LAST_CACHE_UPDATE || 0 + function App() { + const [cacheInitialized, setCacheInitialized] = useState(false) const { data, ...result } = useQuery(GOVERNANCE_INFO_QUERY) + useEffect(() => { + store.getItem('last-update').then(value => { + if (!value || isBefore(fromUnixTime(value), fromUnixTime(Number(LAST_CACHE_UPDATE)))) { + import(`../../data/maker-governance-${LAST_CACHE_UPDATE}.json`).then(data => { + Promise.all( + Object.keys(data.default).map(key => { + return store.setItem(key, data.default[key]) + }), + ).then(() => { + store.setItem('last-update', (Date.now() / 1000).toFixed(0)) + setCacheInitialized(true) + }) + }) + } else { + setCacheInitialized(true) + } + }) + }, []) + return ( <> @@ -38,15 +64,19 @@ function App() {
- - - - - - - - - + {cacheInitialized ? ( + + + + + + + + + + ) : ( + + )}