UNPKG

@foreverrbum/ethsign

Version:

This package will allow you to electronically sign documents within your application

735 lines (635 loc) 30.6 kB
/** * @author pzhu, jbettenc */ import React,{ useState, useEffect } from "react"; import { Chart } from '../charts'; import { loadAllContractHistoryData, network, loadAllAddressCount, loadDocumentsSignedByNetwork, loadSignatureSignedByNetwork, loadAllSignatureSignedData, loadAllDocumentsSignedData } from '../../helpers/graphql'; import moment from "moment"; import { CSVLink } from 'react-csv'; import _ from 'lodash'; import { parseContractHistoryData } from "./utils"; const statisticalData = { 'totalDocumentsSigned': 'Documents Signed', 'totalSignaturesSigned': 'Signatures Signed' }; const CHART_COLOR = { 'totalDocumentsSigned': '#7cb5ec', 'totalSignaturesSigned': "#434348" } const TOTAL_DOCUMENTS_SIGNED = 'totalDocumentsSigned'; const TOTAL_SIGNATURES_SIGNED = 'totalSignaturesSigned'; export const DataDashboard = () => { const [historyData, handleHistoryData] = useState(); const [allSignaturesSignedData, handleAllSignaturesSignedData] = useState(); const [allDocumentSignedData, handleAllDocumentSignedData] = useState(); const [allOptions, handleAllOptions] = useState(); const [timeOptions, handleTimeOptions] = useState(); const [aggregateOptions, handleAggregateOptions] = useState(); const [addressData, handleAddressData] = useState(); const [addressTotalOptions, handleAddressTotalOptions] = useState(); const [addressTimeOptions, handleAddressTimeOptions] = useState(); const [addressAggregateOptions, handleAddressAggregateOptions] = useState(); const [loading, handleLoading] = useState(false); const [error, handleError] = useState(false); const [currentLoadedStep, handleCurrentLoadedStep] = useState(0); const [csvData, handleCSVData] = useState(null); const [documentsSignedByNetwork, handleDocumentsSignedByNetwork] = useState(null); const [signaturesSignedByNetwork, handleSignaturesSignedByNetwork] = useState(null); useEffect(()=>{ const documentSignedEle = document.getElementById("documents_sign_num"); documentSignedEle?.setAttribute("style",'cursor:pointer'); documentSignedEle?.addEventListener('click', documentSignedListener); const signatureSignedEle = document.getElementById("signature_sign_num"); signatureSignedEle?.setAttribute("style",'cursor:pointer'); signatureSignedEle?.addEventListener('click', signatureSignedListener); return () => { document.getElementById("documents_sign_num")?.removeEventListener('click', documentSignedListener); document.getElementById("signature_sign_num")?.removeEventListener('click', signatureSignedListener); } },[historyData, addressData, allDocumentSignedData, allSignaturesSignedData, allOptions]); const documentSignedListener = () => { const contractsData = allDocumentSignedData?.reduce((acc, {data})=>{ if (data?.contracts) { return acc.concat(data?.contracts) } return acc; },[]); const timeData = []; contractsData?.sort((a, b) => a?.consensusTimestamp - b?.consensusTimestamp) ?.forEach((contract) => { const { consensusTimestamp } = contract; // parse every day timestamp handleTimestamp(consensusTimestamp, timeData); }); const len = contractsData?.length; const timeTitle = `Documents Signed Count (${len})`; const aggregateTile = `Documents Signed Count (${len}) - Aggregated`; const { timeOpt, aggregateOpt } = generateOptions('Documents Signed', timeData, timeTitle, aggregateTile, CHART_COLOR[TOTAL_DOCUMENTS_SIGNED]); handleTimeOptions(timeOpt); handleAggregateOptions(aggregateOpt); } const signatureSignedListener = () => { const contractsData = allSignaturesSignedData?.reduce((acc, {data: {events}})=>{ return acc.concat(events); },[]); const timeData = []; contractsData?.sort((a, b) => a?.timestamp - b?.timestamp) ?.forEach((contract) => { const { timestamp } = contract; handleTimestamp(timestamp, timeData); }); const len = contractsData?.length; const timeTitle = `Signatures Signed Count (${len})`; const aggregateTile = `Signatures Signed Count (${len}) - Aggregated`; const { timeOpt, aggregateOpt } = generateOptions('Signatures Signed', timeData, timeTitle, aggregateTile, CHART_COLOR[TOTAL_SIGNATURES_SIGNED]); handleTimeOptions(timeOpt); handleAggregateOptions(aggregateOpt); } useEffect(()=> { (async () => { try { handleError(false); handleLoading(true); // load contract data handleCurrentLoadedStep(0); const historyData = await loadAllContractHistoryData(); handleCurrentLoadedStep(1); const {documentsSignedNumOnEachChain, signaturesSignedNumOnEachChain} = parseContractHistoryData(historyData); handleHistoryData(historyData); //load Address Count data handleAddressData(await loadAllAddressCount()); handleCurrentLoadedStep(2); // load document and signature count data const dsbn = []; const ssbn = []; for(let idx in historyData) { dsbn.push(handleDocumentSignedData(await loadDocumentsSignedByNetwork(network[idx]))); ssbn.push(handleSignatureSignedData(await loadSignatureSignedByNetwork(network[idx]))); } handleCurrentLoadedStep(3); handleDocumentsSignedByNetwork(dsbn); handleSignaturesSignedByNetwork(ssbn); // load all signature signed data handleAllSignaturesSignedData(await loadAllSignatureSignedData(signaturesSignedNumOnEachChain)); handleCurrentLoadedStep(4); // load all documents signed data handleAllDocumentSignedData(await loadAllDocumentsSignedData(documentsSignedNumOnEachChain)); handleCurrentLoadedStep(5); // Load default UI statistics from ethereum const { timeOpt, aggregateOpt } = dsbn[0]; handleTimeOptions(timeOpt); handleAggregateOptions(aggregateOpt); handleLoading(false); } catch(err) { console.log(err); handleLoading(false); handleError(true); } })(); },[]); useEffect(() => { if(!historyData || !addressData || !allSignaturesSignedData || !allDocumentSignedData) { return; } // History data portion { let allDocumentsSigned = 0; let allSignaturesSigned = 0; historyData?.forEach(({data:{infos}}) =>{ const {totalDocumentsSigned, totalSignaturesSigned} = infos[0]; allDocumentsSigned = allDocumentsSigned + totalDocumentsSigned; allSignaturesSigned = allSignaturesSigned + totalSignaturesSigned; }) const tmpOptions = { chart: { type: 'column' }, title: { text: "Contract Data", }, subtitle: { text: `<span id ="documents_sign_num">Documents Signed (${allDocumentsSigned}) </span><br/> <span id ="signature_sign_num">Signatures Signed (${allSignaturesSigned}) </span>`, align: "center", useHTML: true }, xAxis: { categories: network }, yAxis: { title: false }, credits: { enabled: false }, series: [] }; const series = Object.keys(statisticalData).map(key => { return { cursor: 'pointer', point: { events: { click: function () { handleChartEvent(this.category, this.series.name); } } }, name: statisticalData[key], data: [] } }); historyData?.forEach(d => { const {data: {infos}} = d; infos.forEach(info => { Object.keys(statisticalData).forEach(key => { const {data} = series.find(serie => serie.name === statisticalData[key]); data.push(info[key]); }) }) }); tmpOptions.series = series; handleAllOptions(tmpOptions); } // Address Data portion { let addressNum = 0 addressData?.forEach((item)=>{ const {data}= item; addressNum = (data?.users.length || 0 )+ addressNum }) const tmpOptionsAddress = { chart: { type: 'column' }, title: { text: `Address Count (${addressNum})`, }, xAxis: { categories: network }, yAxis: { title: false }, credits: { enabled: false }, series: [] }; const {series} = tmpOptionsAddress; const seriesItem = { cursor: 'pointer', point: { events: { click: function () { handleTotalAddressChartEvent(this.category); } } }, name:"Address", data: [] }; addressData?.forEach(address => { const {networkName, data} = address; //special const len = data?.users?.length || 0; seriesItem?.data.push(len); }); series.push(seriesItem); handleAddressTotalOptions(tmpOptionsAddress); if (addressData) { handleTotalAddressChartEvent(network[0]); } } // Update our CSV data variable with the above data if(historyData && addressData) { handleCSVData(getCSVData()); } }, [historyData, addressData, allSignaturesSignedData, allDocumentSignedData]); const handleTotalAddressChartEvent = (networkName) => { const res = addressData?.find((item) => item?.networkName === networkName); const { data: {users}} = res; const timeData = []; users?.sort((a, b) => a?.joinedTimestamp - b?.joinedTimestamp) ?.forEach((user)=>{ const { joinedTimestamp } = user; handleTimestamp(joinedTimestamp, timeData); }); const timeTitle = `Address Count (${networkName})`; const aggragatetTile = `Address Count - Aggregated (${networkName})`; const { timeOpt, aggregateOpt } = generateOptions(networkName, timeData, timeTitle, aggragatetTile); handleAddressTimeOptions(timeOpt); handleAddressAggregateOptions(aggregateOpt); } function completionData (timeData) { if (timeData?.length === 0) return []; const endTime = moment(); const {year, month, date} = timeData[0]; let nextDay = moment([year, month - 1, date]); const dataList = []; while(isValidDate(endTime, nextDay)){ const tmpT = nextDay; const tDate = tmpT.format("DD/MM/YYYY"); const item = timeData.find(({year, month, date}) => moment([year, month - 1, date]).format("DD/MM/YYYY") == tDate); if (item) { dataList.push(item); } else { dataList.push({ time: `${nextDay.year()}-${nextDay.month() + 1}-${nextDay.date()}`, year: nextDay.year(), month: nextDay.month() + 1, date: nextDay.date(), count:0 }) } nextDay = nextDay.add(1, 'days'); } return dataList; } function isValidDate (endTime, nextDay) { return nextDay.isBefore(endTime); } const generateOptions = (seriesTitle, timeData, timeTitle, aggregateTile, color) => { const dataList = completionData(timeData); const aggregateTimeData = handleAggregateData(dataList); const aggregateData = generateChartData(aggregateTimeData); const numData = generateChartData(dataList); const timeOpt = { chart: { type: 'column' }, title: { text:timeTitle, }, xAxis: { type:"datetime", }, yAxis: { title: false }, credits: { enabled: false }, series: [{ title: `${seriesTitle}`, name: `${seriesTitle}`, data: numData, color, }] } const options = { xAxis: { type: 'datetime', }, yAxis: { min: 0, title: false }, credits: { enabled: false }, plotOptions: { series: { events: { legendItemClick: function () { return false; } }, marker: { enabled: false } } }, } const aggregateOpTime = { title: { text: aggregateTile, }, }; const aggregateOpSeries = {series: [ { title: `${seriesTitle}`, name: `${seriesTitle}`, data: aggregateData, color } ]}; const aggregateOpt = {...options, ...aggregateOpTime, ...aggregateOpSeries} return { timeOpt, aggregateOpt }; } const handleTimestamp = (timestamp, timeData) => { const {year, month, date, time} = parseTimestamp(timestamp); const result = timeData.find(item => item?.time === time); if (result) { result.count = result?.count + 1; } else { timeData.push({ time, year, month, date, count: 1 }) } } const parseTimestamp = (timestamp) => { const utcTime = moment.unix(timestamp).utc(); const year = utcTime.year(); const month = utcTime.month() + 1; const date = utcTime.date(); const time = `${year}-${month}-${date}`; return {year, month, date, time}; } const generateChartData = (timeData) => { return sortTimeData(timeData)?.map(({year, month, date, count})=> { return [Date.UTC(year, month-1, date), count] }); } const sortTimeData = (timeData) => { return timeData.sort((a,b)=>{ const aTime = moment([a.year, a.month, a.date]).unix(); const bTime = moment([b.year, b.month, b.date]).unix(); return aTime - bTime; }); } const handleAggregateData = (timeData) => { const tmp = sortTimeData(_.cloneDeep(timeData)); return tmp?.map((item, index) => { let count; if (index !== 0) { count = item.count + tmp[index -1].count; item.count = count; } return item; }) } const handleDocumentSignedData = (documentSignedData) => { const {networkName, data } = documentSignedData; const timeData = []; data?.contracts?.filter(({consensusTimestamp}) => consensusTimestamp !== "0") ?.sort((a, b) => a?.consensusTimestamp - b?.consensusTimestamp) ?.forEach((contract)=>{ const { consensusTimestamp } = contract; // parse every day timestamp handleTimestamp(consensusTimestamp, timeData); }); const timeTitle = `Documents Signed Count (${networkName})`; const aggragatetTile = `Documents Signed Count - Aggregated (${networkName})`; return generateOptions(networkName, timeData, timeTitle, aggragatetTile, CHART_COLOR[TOTAL_DOCUMENTS_SIGNED]); } const handleSignatureSignedData = (signatureSignedData) => { const {networkName, data:{events}} = signatureSignedData; const timeData = []; events?.sort((a,b) => a?.timestamp - b?.timestamp) ?.forEach(({timestamp}) => { handleTimestamp(timestamp, timeData); }); const timeTitle = `Signature Signed Count (${networkName})`; const aggregateTile = `Signature Signed Count - Aggregated (${networkName})`; return generateOptions(networkName, timeData, timeTitle, aggregateTile, CHART_COLOR[TOTAL_SIGNATURES_SIGNED]); } const handleChartEvent = async (networkName, type) => { const result = Object.keys(statisticalData).find(key => statisticalData[key] === type); switch (result) { case TOTAL_DOCUMENTS_SIGNED: const { timeOpt, aggregateOpt } = handleDocumentSignedData(await loadDocumentsSignedByNetwork(networkName)); handleTimeOptions(timeOpt); handleAggregateOptions(aggregateOpt); break; case TOTAL_SIGNATURES_SIGNED: const { timeOpt:timeOp, aggregateOpt:aggregateOp } = handleSignatureSignedData(await loadSignatureSignedByNetwork(networkName)); handleTimeOptions(timeOp); handleAggregateOptions(aggregateOp); break; } } const headers = [ { label: "Network", key: "network" }, { label: "Documents Signed", key: "docsignedlength" }, { label: "Signatures Signed", key: "sigsignedlength" }, { label: "Address Count", key: "addresscountog" }, { label: "", key: "blank" }, { label: "Date", key: "date" }, { label: "Signed Document Count (ethereum)", key: "signeddoccount.ethereum" }, { label: "Signed Document Count (ropsten)", key: "signeddoccount.ropsten" }, { label: "Signed Document Count (bsc)", key: "signeddoccount.bsc" }, { label: "Signed Document Count (avalanche)", key: "signeddoccount.avalanche" }, { label: "Signed Document Count (polygon)", key: "signeddoccount.polygon" }, { label: "Signed Document Count (fantom)", key: "signeddoccount.fantom" }, { label: "", key: "blank" }, { label: "Aggregate Signed Document Count (ethereum)", key: "signeddoccountagg.ethereum" }, { label: "Aggregate Signed Document Count (ropsten)", key: "signeddoccountagg.ropsten" }, { label: "Aggregate Signed Document Count (bsc)", key: "signeddoccountagg.bsc" }, { label: "Aggregate Signed Document Count (avalanche)", key: "signeddoccountagg.avalanche" }, { label: "Aggregate Signed Document Count (polygon)", key: "signeddoccountagg.polygon" }, { label: "Aggregate Signed Document Count (fantom)", key: "signeddoccountagg.fantom" }, { label: "", key: "blank" }, { label: "Signature Count (ethereum)", key: "signaturecount.ethereum" }, { label: "Signature Count (ropsten)", key: "signaturecount.ropsten" }, { label: "Signature Count (bsc)", key: "signaturecount.bsc" }, { label: "Signature Count (avalanche)", key: "signaturecount.avalanche" }, { label: "Signature Count (polygon)", key: "signaturecount.polygon" }, { label: "Signature Count (fantom)", key: "signaturecount.fantom" }, { label: "", key: "blank" }, { label: "Aggregate Signature Count (ethereum)", key: "signaturecountagg.ethereum" }, { label: "Aggregate Signature Count (ropsten)", key: "signaturecountagg.ropsten" }, { label: "Aggregate Signature Count (bsc)", key: "signaturecountagg.bsc" }, { label: "Aggregate Signature Count (avalanche)", key: "signaturecountagg.avalanche" }, { label: "Aggregate Signature Count (polygon)", key: "signaturecountagg.polygon" }, { label: "Aggregate Signature Count (fantom)", key: "signaturecountagg.fantom" }, { label: "", key: "blank" }, { label: "Address Count (ethereum)", key: "addresscount.ethereum" }, { label: "Address Count (ropsten)", key: "addresscount.ropsten" }, { label: "Address Count (bsc)", key: "addresscount.bsc" }, { label: "Address Count (avalanche)", key: "addresscount.avalanche" }, { label: "Address Count (polygon)", key: "addresscount.polygon" }, { label: "Address Count (fantom)", key: "addresscount.fantom" }, { label: "", key: "blank" }, { label: "Aggregate Address Count (ethereum)", key: "addresscountagg.ethereum" }, { label: "Aggregate Address Count (ropsten)", key: "addresscountagg.ropsten" }, { label: "Aggregate Address Count (bsc)", key: "addresscountagg.bsc" }, { label: "Aggregate Address Count (avalanche)", key: "addresscountagg.avalanche" }, { label: "Aggregate Address Count (polygon)", key: "addresscountagg.polygon" }, { label: "Aggregate Address Count (fantom)", key: "addresscountagg.fantom" }, ]; const getCSVData = () => { // Signature counts + Signed document counts const numDataSig = [], aggregateDataSig = [], numDataDoc = [], aggregateDataDoc = []; for(let idx in signaturesSignedByNetwork) { const { timeOpt, aggregateOpt } = signaturesSignedByNetwork[idx]; numDataSig.push(timeOpt.series[0].data); aggregateDataSig.push(aggregateOpt.series[0].data); const { timeOpt: timeOptDoc, aggregateOpt: aggregateOptDoc } = documentsSignedByNetwork[idx]; numDataDoc.push(timeOptDoc.series[0].data); aggregateDataDoc.push(aggregateOptDoc.series[0].data); } // Address Count const csvAddressData = []; for(let idx in addressData) { const { data: {users} } = addressData[idx]; const timeDataAddress = []; users?.sort((a, b) => a?.joinedTimestamp - b?.joinedTimestamp) ?.forEach((user)=>{ const { joinedTimestamp } = user; handleTimestamp(joinedTimestamp, timeDataAddress); }); const dataListAddress = completionData(timeDataAddress); const aggregateTimeDataAddress = handleAggregateData(dataListAddress); const aggregateDataAddress = generateChartData(aggregateTimeDataAddress); const numDataAddress = generateChartData(dataListAddress); csvAddressData.push({ aggregateDataAddress: aggregateDataAddress, numDataAddress: numDataAddress, }); } let contractData = []; // Document and signature counts for(let net in numDataSig[0]) { contractData.push({ network: historyData[net] ? historyData[net].networkName : "", docsignedlength: historyData[net]?.data ? historyData[net].data.infos[0].totalDocumentsSigned : "", sigsignedlength: historyData[net]?.data ? historyData[net].data.infos[0].totalSignaturesSigned : "", blank: "", date: new Date(numDataSig[0][net][0]).toDateString(), signeddoccount: { ethereum: numDataDoc[0][net] ? numDataDoc[0][net][1] : "0", ropsten: numDataDoc[1][net] ? numDataDoc[1][net][1] : "0", bsc: numDataDoc[2][net] ? numDataDoc[2][net][1] : "0", avalanche: numDataDoc[3][net] ? numDataDoc[3][net][1] : "0", polygon: numDataDoc[4][net] ? numDataDoc[4][net][1] : "0", fantom: numDataDoc[0][net] ? numDataDoc[0][net][1] : "0", }, signeddoccountagg: { ethereum: aggregateDataDoc[0][net] ? aggregateDataDoc[0][net][1] : "0", ropsten: aggregateDataDoc[1][net] ? aggregateDataDoc[1][net][1] : "0", bsc: aggregateDataDoc[2][net] ? aggregateDataDoc[2][net][1] : "0", avalanche: aggregateDataDoc[3][net] ? aggregateDataDoc[3][net][1] : "0", polygon: aggregateDataDoc[4][net] ? aggregateDataDoc[4][net][1] : "0", fantom: aggregateDataDoc[0][net] ? aggregateDataDoc[0][net][1] : "0", }, signaturecount: { ethereum: numDataSig[0][net] ? numDataSig[0][net][1] : "0", ropsten: numDataSig[1][net] ? numDataSig[1][net][1] : "0", bsc: numDataSig[2][net] ? numDataSig[2][net][1] : "0", avalanche: numDataSig[3][net] ? numDataSig[3][net][1] : "0", polygon: numDataSig[4][net] ? numDataSig[4][net][1] : "0", fantom: numDataSig[0][net] ? numDataSig[0][net][1] : "0", }, signaturecountagg: { ethereum: aggregateDataSig[0][net] ? aggregateDataSig[0][net][1] : "0", ropsten: aggregateDataSig[1][net] ? aggregateDataSig[1][net][1] : "0", bsc: aggregateDataSig[2][net] ? aggregateDataSig[2][net][1] : "0", avalanche: aggregateDataSig[3][net] ? aggregateDataSig[3][net][1] : "0", polygon: aggregateDataSig[4][net] ? aggregateDataSig[4][net][1] : "0", fantom: aggregateDataSig[0][net] ? aggregateDataSig[0][net][1] : "0", }, addresscount: { ethereum: csvAddressData[0].numDataAddress[net] ? csvAddressData[0].numDataAddress[net][1] : "0", ropsten: csvAddressData[1].numDataAddress[net] ? csvAddressData[1].numDataAddress[net][1] : "0", bsc: csvAddressData[2].numDataAddress[net] ? csvAddressData[2].numDataAddress[net][1] : "0", avalanche: csvAddressData[3].numDataAddress[net] ? csvAddressData[3].numDataAddress[net][1] : "0", polygon: csvAddressData[4].numDataAddress[net] ? csvAddressData[4].numDataAddress[net][1] : "0", fantom: csvAddressData[5].numDataAddress[net] ? csvAddressData[5].numDataAddress[net][1] : "0", }, addresscountagg: { ethereum: csvAddressData[0].aggregateDataAddress[net] ? csvAddressData[0].aggregateDataAddress[net][1] : "0", ropsten: csvAddressData[1].aggregateDataAddress[net] ? csvAddressData[1].aggregateDataAddress[net][1] : "0", bsc: csvAddressData[2].aggregateDataAddress[net] ? csvAddressData[2].aggregateDataAddress[net][1] : "0", avalanche: csvAddressData[3].aggregateDataAddress[net] ? csvAddressData[3].aggregateDataAddress[net][1] : "0", polygon: csvAddressData[4].aggregateDataAddress[net] ? csvAddressData[4].aggregateDataAddress[net][1] : "0", fantom: csvAddressData[5].aggregateDataAddress[net] ? csvAddressData[5].aggregateDataAddress[net][1] : "0", } }); } // Add address counts for(let net in addressData) { contractData[net].addresscountog = addressData[net].data.users.length; } let data = contractData; return data; } if(error) { return ( <div className="m-auto text-lg text-gray-60"> Error loading data. Check console for a detailed error message. </div> ); } if(loading) { return ( <div className="m-auto text-lg text-gray-60"> Loading... ({currentLoadedStep}/5) </div> ); } return ( <div className="flex justify-center flex-wrap mt-16 px-2 sm:px-16 mb-auto max-w-7xl"> <CSVLink className="cursor-pointer text-center ml-auto mt-3 sm:mt-0 sm:w-32 font-bold flex-grow-0 bg-orange-500 focus:outline-none text-gray-40 py-2 rounded-sm hover:bg-orange-600" onClick={(event) => { // Returning false will not export the svg. Since we are still loading data, we don't want to give an incomplete csv file. if(loading) { return false; } }} headers={headers} data={csvData ? csvData : ""} filename={`data-export-${(new Date().toISOString().replace(':', '-').substring(0, 16))}Z.csv`}> {loading ? 'Loading...' : 'Export to CSV'} </CSVLink> <div className="flex justify-around flex-wrap w-full"> <Chart options={allOptions} className="my-4 w-full sm:w-1/3 p-4"/> <Chart options={timeOptions} className="my-4 w-full sm:w-1/3 p-4"/> <Chart options={aggregateOptions} className="my-4 w-full sm:w-1/3 p-4"/> </div> <div className="flex justify-around flex-wrap w-full"> <Chart options={addressTotalOptions} className="my-4 w-full sm:w-1/3 p-4"/> <Chart options={addressTimeOptions} className="my-4 w-full sm:w-1/3 p-4"/> <Chart options={addressAggregateOptions} className="my-4 w-full sm:w-1/3 p-4"/> </div> </div> ) }