From 2602abbe25211043bad4c4f8cb5f8f36d5a6636d Mon Sep 17 00:00:00 2001 From: Jorge Navarro Bendicho <53904337+jorgenavben@users.noreply.github.com> Date: Thu, 23 Nov 2023 09:47:18 +0000 Subject: [PATCH] Feature/optimize api calls hydra datum button (#524) --- ui/summit-2023/src/App.tsx | 55 +- .../src/common/api/leaderboardService.ts | 12 +- ui/summit-2023/src/i18n/locales/en/en.json | 8 +- .../src/pages/Leaderboard/Leaderboard.tsx | 36 +- .../components/AwardsTile/AwardsTile.tsx | 40 +- .../components/HydraTile/HydraTile.tsx | 96 ++- ui/summit-2023/src/store/userSlice.ts | 1 + .../src/types/voting-ledger-follower-types.ts | 702 ++++++++++++------ 8 files changed, 625 insertions(+), 325 deletions(-) diff --git a/ui/summit-2023/src/App.tsx b/ui/summit-2023/src/App.tsx index 0a46d7f5b..295c662be 100644 --- a/ui/summit-2023/src/App.tsx +++ b/ui/summit-2023/src/App.tsx @@ -22,7 +22,7 @@ import SUMMIT2023CONTENT from 'common/resources/data/summit2023Content.json'; import { resolveCardanoNetwork } from './utils/utils'; import { parseError } from 'common/constants/errors'; import { getUserVotes } from 'common/api/voteService'; -import { getCategoryLevelStats } from 'common/api/leaderboardService'; +import { getVotingResults } from 'common/api/leaderboardService'; import { ProposalContent } from 'pages/Nominees/Nominees.type'; import { setWinners } from 'store/userSlice'; import './App.scss'; @@ -44,47 +44,48 @@ function App() { const { isConnected, stakeAddress } = useCardano({ limitNetwork: resolveCardanoNetwork(env.TARGET_NETWORK) }); - async function loadWinners(filteredCategory) { + async function loadWinners(votingResults, filteredCategory) { const filteredCategoryProposals: ProposalContent[] = filteredCategory?.proposals; try { - await getCategoryLevelStats(filteredCategory?.id).then((response) => { - const updatedAwards = filteredCategoryProposals.map((proposal) => { - const id = proposal.id; - const votes = response?.proposals[id] ? response?.proposals[id].votes : 0; - const rank = 0; - return { ...proposal, votes, rank }; - }); - - updatedAwards.sort((a, b) => b.votes - a.votes); + const categoryResults = votingResults?.find((category) => category.category === filteredCategory.id); - updatedAwards.forEach((item, index, array) => { - if (index > 0 && item.votes === array[index - 1].votes) { - item.rank = array[index - 1].rank; - } else { - item.rank = index + 1; - } - }); + const updatedAwards = filteredCategoryProposals.map((proposal) => { + const id = proposal.id; + const votes = categoryResults?.proposals[id] ? categoryResults?.proposals[id].votes : 0; + const rank = 0; + return { ...proposal, votes, rank }; + }); - const categoryWinners = updatedAwards - .filter((winner) => (winner.rank === 1 && winner.votes > 0)) - .map((winner) => { - return { categoryId: filteredCategory.id, proposalId: winner.id }; - }); + updatedAwards.sort((a, b) => b.votes - a.votes); - dispatch(setWinners({ winners: categoryWinners })); + updatedAwards.forEach((item, index, array) => { + if (index > 0 && item.votes === array[index - 1].votes) { + item.rank = array[index - 1].rank; + } else { + item.rank = index + 1; + } }); + + const categoryWinners = updatedAwards + .filter((winner) => (winner.rank === 1 && winner.votes > 0)) + .map((winner) => { + return { categoryId: filteredCategory.id, proposalId: winner.id }; + }); + + dispatch(setWinners({ winners: categoryWinners })); } catch (error) { - const message = `Failed to fecth Nominee stats: ${error?.message || error?.toString()}`; + const message = `Failed to fetch Nominee stats: ${error?.message || error?.toString()}`; if (process.env.NODE_ENV === 'development') { console.log(message); } - eventBus.publish('showToast', i18n.t('toast.failedToFecthNomineeStats'), 'error'); + eventBus.publish('showToast', i18n.t('toast.failedToFetchNomineeStats'), 'error'); } } const fetchEvent = useCallback(async () => { try { const event = await getEvent(env.EVENT_ID); + const votingResults = await getVotingResults(); const staticCategories: CategoryContent[] = SUMMIT2023CONTENT.categories; @@ -93,7 +94,7 @@ function App() { const joinedCategory = staticCategories.find((staticCategory) => staticCategory.id === category.id); if (joinedCategory) { if ('proposalsReveal' in event && event.proposalsReveal) { - loadWinners(joinedCategory); + loadWinners(votingResults, joinedCategory); } return { ...category, ...joinedCategory }; } diff --git a/ui/summit-2023/src/common/api/leaderboardService.ts b/ui/summit-2023/src/common/api/leaderboardService.ts index 44290f518..1b3df5bd0 100644 --- a/ui/summit-2023/src/common/api/leaderboardService.ts +++ b/ui/summit-2023/src/common/api/leaderboardService.ts @@ -1,22 +1,24 @@ import { ByEventStats, ByProposalsInCategoryStats } from 'types/voting-app-types'; import { DEFAULT_CONTENT_TYPE_HEADERS, doRequest, HttpMethods } from '../handlers/httpHandler'; import { env } from '../constants/env'; +import { TallyResults } from 'types/voting-ledger-follower-types'; const LEADERBOARD_URL = `${env.VOTING_APP_SERVER_URL}/api/leaderboard`; +const HYDRATALLY_URL = `${env.VOTING_LEDGER_FOLLOWER_APP_SERVER_URL}/api/tally/voting-results`; const getStats = async () => await doRequest(HttpMethods.GET, `${LEADERBOARD_URL}/${env.EVENT_ID}`, { ...DEFAULT_CONTENT_TYPE_HEADERS, }); -const getCategoryLevelStats = async (categoryId) => - await doRequest(HttpMethods.GET, `${LEADERBOARD_URL}/${env.EVENT_ID}/${categoryId}`, { +const getVotingResults = async () => + await doRequest(HttpMethods.GET, `${LEADERBOARD_URL}/${env.EVENT_ID}/results?source=db`, { ...DEFAULT_CONTENT_TYPE_HEADERS, }); -const getHydraTallyStats = async (categoryId) => - await doRequest(HttpMethods.GET, `${LEADERBOARD_URL}/${env.EVENT_ID}/${categoryId}?source=l1`, { +const getHydraTallyStats = async () => + await doRequest(HttpMethods.GET, `${HYDRATALLY_URL}/${env.EVENT_ID}/Hydra_Tally_Experiment`, { ...DEFAULT_CONTENT_TYPE_HEADERS, }); -export { getStats, getCategoryLevelStats, getHydraTallyStats }; +export { getStats, getVotingResults, getHydraTallyStats }; diff --git a/ui/summit-2023/src/i18n/locales/en/en.json b/ui/summit-2023/src/i18n/locales/en/en.json index 9e0b3c7bf..3b899828f 100644 --- a/ui/summit-2023/src/i18n/locales/en/en.json +++ b/ui/summit-2023/src/i18n/locales/en/en.json @@ -142,8 +142,8 @@ "copyError": "Copied to clipboard failed", "voteVerified": "Vote proof verified", "voteNotVerified": "Vote proof not verified", - "failedToFecthStats": "Failed to fecth stats", - "failedToFecthNomineeStats": "Failed to fecth Nominee stats" + "failedToFetchStats": "Failed to fetch stats", + "failedToFetchNomineeStats": "Failed to fetch Nominee stats" }, "leaderboard": { "title": "Leaderboard", @@ -183,7 +183,9 @@ "tableHeadings": { "column1": "Winner", "column2": "Votes" - } + }, + "datumInspectorButton": "View Hydra Tally", + "copyAddressButtonTooltip": "Copy Smart Contract Address" } } } diff --git a/ui/summit-2023/src/pages/Leaderboard/Leaderboard.tsx b/ui/summit-2023/src/pages/Leaderboard/Leaderboard.tsx index 8d743e766..d4420c3fb 100644 --- a/ui/summit-2023/src/pages/Leaderboard/Leaderboard.tsx +++ b/ui/summit-2023/src/pages/Leaderboard/Leaderboard.tsx @@ -5,8 +5,8 @@ import cn from 'classnames'; import { i18n } from 'i18n'; import { makeStyles } from 'tss-react/mui'; import { PieChart } from 'react-minimal-pie-chart'; -import { ByCategoryStats } from 'types/voting-app-types'; -import { EventPresentation } from 'types/voting-ledger-follower-types'; +import { ByCategoryStats, ByProposalsInCategoryStats } from 'types/voting-app-types'; +import { EventPresentation, TallyResults } from 'types/voting-ledger-follower-types'; import * as leaderboardService from '../../common/api/leaderboardService'; import { categoryColorsMap, getPercentage } from './utils'; import { StatItem } from './types'; @@ -83,6 +83,8 @@ const Leaderboard = () => { const classes = useStyles(); const summitEvent = useSelector((state: RootState) => state.user.event); const [stats, setStats] = useState(); + const [votingResults, setVotingResults] = useState(); + const [hydraTallyStats, setHydraTallyStats] = useState(); const [value, setValue] = useState('2'); const [winnersAvailable, setWinnersAvailable] = useState(Boolean); const [hydraTallyAvailable, setHydraTallyAvailable] = useState(Boolean); @@ -95,11 +97,35 @@ const Leaderboard = () => { setStats(response.categories); }); } catch (error) { - const message = `Failed to fecth stats: ${error?.message || error?.toString()}`; + const message = `Failed to fetch stats: ${error?.message || error?.toString()}`; if (process.env.NODE_ENV === 'development') { console.log(message); } - eventBus.publish('showToast', i18n.t('toast.failedToFecthStats'), 'error'); + eventBus.publish('showToast', i18n.t('toast.failedToFetchStats'), 'error'); + } + + try { + await leaderboardService.getVotingResults().then((response) => { + setVotingResults(response); + }); + } catch (error) { + const message = `Failed to fetch results stats: ${error?.message || error?.toString()}`; + if (process.env.NODE_ENV === 'development') { + console.log(message); + } + eventBus.publish('showToast', i18n.t('toast.failedToFetchStats'), 'error'); + } + + try { + await leaderboardService.getHydraTallyStats().then((response) => { + setHydraTallyStats(response); + }); + } catch (error) { + const message = `Failed to fetch Hydra Tally stats: ${error?.message || error?.toString()}`; + if (process.env.NODE_ENV === 'development') { + console.log(message); + } + eventBus.publish('showToast', i18n.t('toast.failedToFetchStats'), 'error'); } }, []); @@ -263,6 +289,7 @@ const Leaderboard = () => { title={item.label} counter={index} categoryId={item.id} + votingResults={votingResults} /> ))} @@ -475,6 +502,7 @@ const Leaderboard = () => { title={item.label} counter={index} categoryId={item.id} + hydraTallyStats={hydraTallyStats} /> ))} diff --git a/ui/summit-2023/src/pages/Leaderboard/components/AwardsTile/AwardsTile.tsx b/ui/summit-2023/src/pages/Leaderboard/components/AwardsTile/AwardsTile.tsx index 323aecc66..df208a5fc 100644 --- a/ui/summit-2023/src/pages/Leaderboard/components/AwardsTile/AwardsTile.tsx +++ b/ui/summit-2023/src/pages/Leaderboard/components/AwardsTile/AwardsTile.tsx @@ -4,7 +4,6 @@ import { Link } from 'react-router-dom'; import Card from '@mui/material/Card'; import { i18n } from 'i18n'; import CardContent from '@mui/material/CardContent'; -import * as leaderboardService from '../../../../common/api/leaderboardService'; import { eventBus } from 'utils/EventBus'; import SUMMIT2023CONTENT from '../../../../common/resources/data/summit2023Content.json'; import { ProposalContent } from 'pages/Nominees/Nominees.type'; @@ -12,8 +11,7 @@ import { CategoryContent } from 'pages/Categories/Category.types'; import styles from './AwardsTile.module.scss'; import cn from 'classnames'; import CATEGORY_IMAGES from '../../../../common/resources/data/categoryImages.json'; - -const AwardsTile = ({ counter, title, categoryId }) => { +const AwardsTile = ({ counter, title, categoryId, votingResults }) => { const summit2023Category: CategoryContent = SUMMIT2023CONTENT.categories.find( (category) => category.id === categoryId ); @@ -23,32 +21,32 @@ const AwardsTile = ({ counter, title, categoryId }) => { const init = useCallback(async () => { try { - await leaderboardService.getCategoryLevelStats(categoryId).then((response) => { - const updatedAwards = summit2023Proposals.map((proposal) => { - const id = proposal.id; - const votes = response?.proposals[id] ? response?.proposals[id].votes : 0; - const rank = 0; - return { ...proposal, votes, rank }; - }); + const categoryResults = votingResults?.find((category) => category.category === categoryId); + + const updatedAwards = summit2023Proposals.map((proposal) => { + const id = proposal.id; + const votes = categoryResults?.proposals[id] ? categoryResults?.proposals[id].votes : 0; + const rank = 0; + return { ...proposal, votes, rank }; + }); - updatedAwards.sort((a, b) => b.votes - a.votes); + updatedAwards.sort((a, b) => b.votes - a.votes); - updatedAwards.forEach((item, index, array) => { - if (index > 0 && item.votes === array[index - 1].votes) { - item.rank = array[index - 1].rank; - } else { - item.rank = index + 1; - } - }); - setAwards(updatedAwards); + updatedAwards.forEach((item, index, array) => { + if (index > 0 && item.votes === array[index - 1].votes) { + item.rank = array[index - 1].rank; + } else { + item.rank = index + 1; + } }); + setAwards(updatedAwards); setLoaded(true); } catch (error) { - const message = `Failed to fecth Nominee stats: ${error?.message || error?.toString()}`; + const message = `Failed to fetch Nominee stats: ${error?.message || error?.toString()}`; if (process.env.NODE_ENV === 'development') { console.log(message); } - eventBus.publish('showToast', i18n.t('toast.failedToFecthNomineeStats'), 'error'); + eventBus.publish('showToast', i18n.t('toast.failedToFetchNomineeStats'), 'error'); } }, []); diff --git a/ui/summit-2023/src/pages/Leaderboard/components/HydraTile/HydraTile.tsx b/ui/summit-2023/src/pages/Leaderboard/components/HydraTile/HydraTile.tsx index bfebf47e1..d15c72468 100644 --- a/ui/summit-2023/src/pages/Leaderboard/components/HydraTile/HydraTile.tsx +++ b/ui/summit-2023/src/pages/Leaderboard/components/HydraTile/HydraTile.tsx @@ -1,9 +1,8 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { Avatar, Box, Button, CardActions, Chip, CircularProgress, Grid, Typography } from '@mui/material'; +import { Avatar, Box, Button, ButtonGroup, CardActions, Chip, CircularProgress, Grid, Tooltip, Typography } from '@mui/material'; import { Link } from 'react-router-dom'; import Card from '@mui/material/Card'; import CardContent from '@mui/material/CardContent'; -import * as leaderboardService from '../../../../common/api/leaderboardService'; import { eventBus } from 'utils/EventBus'; import SUMMIT2023CONTENT from '../../../../common/resources/data/summit2023Content.json'; import { ProposalContent } from 'pages/Nominees/Nominees.type'; @@ -12,42 +11,53 @@ import styles from './HydraTile.module.scss'; import cn from 'classnames'; import { i18n } from 'i18n'; import CATEGORY_IMAGES from '../../../../common/resources/data/categoryImages.json'; +import { CustomButton } from 'components/common/Button/CustomButton'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import { copyToClipboard } from 'utils/utils'; -const HydraTile = ({ counter, title, categoryId }) => { +const HydraTile = ({ counter, title, categoryId, hydraTallyStats }) => { const summit2023Category: CategoryContent = SUMMIT2023CONTENT.categories.find( (category) => category.id === categoryId ); const summit2023Proposals: ProposalContent[] = summit2023Category.proposals; const [awards, setAwards] = useState([]); const [loaded, setLoaded] = useState(false); + const [categoryResultsDatum, setCategoryResultsDatum] = useState(''); + const [contractAddress, setContractAddress] = useState(''); + + const datumInspectorURL = 'https://cardanoscan.io/datumInspector?datum='; const init = useCallback(async () => { try { - await leaderboardService.getHydraTallyStats(categoryId).then((response) => { - const updatedAwards = summit2023Proposals.map((proposal) => { - const id = proposal.id; - const votes = response?.proposals[id] ? response?.proposals[id].votes : 0; - const rank = 0; - return { ...proposal, votes, rank }; - }); + const categoryStats = hydraTallyStats?.find((category) => category.categoryId === categoryId) + + const updatedAwards = summit2023Proposals.map((proposal) => { + const id = proposal.id; + const votes = categoryStats?.results[id] ? categoryStats?.results[id] : 0; + const rank = 0; + return { ...proposal, votes, rank }; + }); - updatedAwards.sort((a, b) => b.votes - a.votes); - updatedAwards.forEach((item, index, array) => { - if (index > 0 && item.votes === array[index - 1].votes) { - item.rank = array[index - 1].rank; - } else { - item.rank = index + 1; - } - }); - setAwards(updatedAwards); + updatedAwards.sort((a, b) => b.votes - a.votes); + updatedAwards.forEach((item, index, array) => { + if (index > 0 && item.votes === array[index - 1].votes) { + item.rank = array[index - 1].rank; + } else { + item.rank = index + 1; + } }); + setAwards(updatedAwards); + + setCategoryResultsDatum(categoryStats.metadata.categoryResultsDatum); + setContractAddress(categoryStats.metadata.contractAddress); + setLoaded(true); } catch (error) { - const message = `Failed to fecth Nominee stats: ${error?.message || error?.toString()}`; + const message = `Failed to fetch Nominee stats: ${error?.message || error?.toString()}`; if (process.env.NODE_ENV === 'development') { console.log(message); } - eventBus.publish('showToast', i18n.t('toast.failedToFecthNomineeStats'), 'error'); + eventBus.publish('showToast', i18n.t('toast.failedToFetchNomineeStats'), 'error'); } }, []); @@ -55,6 +65,12 @@ const HydraTile = ({ counter, title, categoryId }) => { init(); }, [init]); + const handleCopyToClipboard = (text: string) => { + copyToClipboard(text) + .then(() => eventBus.publish('showToast', i18n.t('toast.copy'))) + .catch(() => eventBus.publish('showToast', i18n.t('toast.copyError'), 'error')); + }; + return (
{loaded ? ( @@ -127,6 +143,42 @@ const HydraTile = ({ counter, title, categoryId }) => { ))} + + + window.location.href = `${datumInspectorURL}${categoryResultsDatum}`} + /> + + + + +