Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Release] - Governance Voter Display. Governance Subgraph Improvements #3212

Merged
merged 11 commits into from
Oct 7, 2024
9 changes: 9 additions & 0 deletions src/helpers/environment/Environment/Environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ export class Environment {
}),
);

public static getGovernanceSubgraphUrl = (): string => {
const subgraphApiKey = this.getSubgraphApiKey();
return this._get({
first: true,
key: "VITE_GOVERNANCE_SUBGRAPH_URL",
fallback: `https://gateway.thegraph.com/api/${subgraphApiKey}/subgraphs/id/AQoLCXebY1Ga7DrqVaVQ85KMwS7iFof73tv9XMVGRtyJ`,
});
};

public static getNodeUrls = (networkId: NetworkId) => {
switch (networkId) {
case NetworkId.MAINNET:
Expand Down
4 changes: 2 additions & 2 deletions src/views/Governance/Components/Status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { Paper } from "@olympusdao/component-library";
import { useGetCanceledTime } from "src/views/Governance/hooks/useGetCanceledTime";
import { useGetExecutedTime } from "src/views/Governance/hooks/useGetExecutedTime";
import { useGetProposalDetails } from "src/views/Governance/hooks/useGetProposalDetails";
import { useGetProposal } from "src/views/Governance/hooks/useGetProposals";
import { useGetProposalFromSubgraph } from "src/views/Governance/hooks/useGetProposalFromSubgraph";
import { useGetQueuedTime } from "src/views/Governance/hooks/useGetQueuedTime";
import { useGetVetoedTime } from "src/views/Governance/hooks/useGetVetoedTime";

export const Status = ({ proposalId }: { proposalId: number }) => {
const { data: proposal } = useGetProposal({ proposalId });
const { data: proposal } = useGetProposalFromSubgraph({ proposalId: proposalId.toString() });
const { data: proposalDetails } = useGetProposalDetails({ proposalId });
const { data: queueTime } = useGetQueuedTime({ proposalId });
const { data: executedTime } = useGetExecutedTime({ proposalId, status: proposalDetails?.status });
Expand Down
99 changes: 99 additions & 0 deletions src/views/Governance/Proposals/VoteDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import {
Box,
Paper,
Tab,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Tabs,
Typography,
} from "@mui/material";
import React, { useState } from "react";
import { useParams } from "react-router-dom";
import { useGetVotes } from "src/views/Governance/hooks/useGetVotes";
import { VoteRow } from "src/views/Governance/Proposals/VoteRow";

// Data for the tables, including a 'reason' field for each row
const tablesData = [
{
id: "For",
},
{
id: "Against",
},
{
id: "Abstain",
},
];

const TabPanel = (props: { children: React.ReactNode; value: number; index: number }) => {
const { children, value, index, ...other } = props;
return (
<div role="tabpanel" hidden={value !== index} id={`tabpanel-${index}`} aria-labelledby={`tab-${index}`} {...other}>
{value === index && <Box p={3}>{children}</Box>}
</div>
);
};

export default function GovernanceTable() {
const { id } = useParams();
const [tabIndex, setTabIndex] = useState(0);
const { data: voteData } = useGetVotes({ proposalId: id, support: tabIndex + 1 });

const handleTabChange = (event: React.SyntheticEvent, newIndex: number) => {
setTabIndex(newIndex);
};

return (
<Box sx={{ width: "100%" }}>
<Tabs
textColor="primary"
aria-label="proposal tabs"
indicatorColor="primary"
value={tabIndex}
onChange={handleTabChange}
//hides the tab underline sliding animation in while <Zoom> is loading
TabIndicatorProps={{ style: { display: "none" } }}
centered
>
{" "}
{tablesData.map((table, index) => (
<Tab label={table.id} key={index} />
))}
</Tabs>

{tablesData.map((table, index) => (
<TabPanel value={tabIndex} index={index} key={index}>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>{tablesData[tabIndex].id}</TableCell>
<TableCell align="right">Votes</TableCell>
</TableRow>
</TableHead>
<TableBody>
{voteData?.length ? (
voteData.map((row, i) => (
<TableRow key={i}>
<VoteRow voter={row.voter} reason={row.reason} votes={row.votes} tx={row.transactionHash} />
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={2} align="center">
<Typography>No votes yet</Typography>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
</TabPanel>
))}
</Box>
);
}
37 changes: 37 additions & 0 deletions src/views/Governance/Proposals/VoteRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Box, Link, TableCell, Tooltip, Typography } from "@mui/material";
import { formatEther } from "ethers/lib/utils.js";
import { abbreviatedNumber } from "src/helpers";
import { truncateEthereumAddress } from "src/helpers/truncateAddress";
import { useEnsName } from "wagmi";

export const VoteRow = ({
voter,
reason,
votes,
tx,
}: {
voter: string;
reason?: string;
votes: string;
tx: string;
}) => {
const { data: ensName } = useEnsName({ address: voter as `0x${string}` });
return (
<>
<TableCell>
<Link href={`https://etherscan.io/tx/${tx}`} target="_blank" rel="noopener noreferrer">
<Tooltip title={voter}>
<Box>{ensName || truncateEthereumAddress(voter)}</Box>
</Tooltip>
</Link>
{/* Render the reason if provided, and style it as a comment */}
{reason && (
<Typography variant="body2" sx={{ color: "gray", fontStyle: "italic", mt: 1 }}>
"{reason}"
</Typography>
)}
</TableCell>
<TableCell align="right">{abbreviatedNumber.format(Number(formatEther(votes) || 0))} gOHM</TableCell>
</>
);
};
12 changes: 5 additions & 7 deletions src/views/Governance/Proposals/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ import { useActivateProposal } from "src/views/Governance/hooks/useActivatePropo
import { useExecuteProposal } from "src/views/Governance/hooks/useExecuteProposal";
import { useGetCurrentBlockTime } from "src/views/Governance/hooks/useGetCurrentBlockTime";
import { useGetProposalDetails } from "src/views/Governance/hooks/useGetProposalDetails";
import { useGetProposal } from "src/views/Governance/hooks/useGetProposals";
import { useGetProposalFromSubgraph } from "src/views/Governance/hooks/useGetProposalFromSubgraph";
import { useQueueProposal } from "src/views/Governance/hooks/useQueueProposal";
import VoteDetails from "src/views/Governance/Proposals/VoteDetails";
import { useEnsName, useNetwork, useSwitchNetwork } from "wagmi";

export const ProposalPage = () => {
const { id } = useParams();
const { data: proposal } = useGetProposal({ proposalId: Number(id) });
const { data: proposal } = useGetProposalFromSubgraph({ proposalId: id });
const { data: proposalDetails } = useGetProposalDetails({ proposalId: Number(id) });
const { data: ensAddress } = useEnsName({ address: proposalDetails?.proposer as `0x${string}` });
const [voteModalOpen, setVoteModalOpen] = useState(false);
Expand Down Expand Up @@ -160,7 +161,7 @@ export const ProposalPage = () => {
>
<Tab label="Description" />
<Tab label="Executable Code" />
{/* <Tab label="Comments" /> */}
<Tab label="Participation" />
</Tabs>
</Box>
<Grid container spacing={"24px"}>
Expand Down Expand Up @@ -197,11 +198,8 @@ export const ProposalPage = () => {
)}
{tabIndex === 2 && (
<>
<Typography fontSize="21px" fontWeight={600} mb="15px">
Comments
</Typography>
<Box display="flex" flexDirection="column" gap="15px">
<Typography>No comments yet</Typography>
<VoteDetails />
</Box>
</>
)}
Expand Down
20 changes: 20 additions & 0 deletions src/views/Governance/helpers/normalizeProposal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Proposal } from "src/views/Governance/hooks/useGetProposalFromSubgraph";

// Normalizes the proposal data to match the onchain format
export const normalizeProposal = (proposal: Proposal) => {
return {
createdAtBlock: new Date(Number(proposal.blockTimestamp) * 1000),
details: {
id: proposal.proposalId,
proposer: proposal.proposer,
targets: proposal.targets,
values: proposal.values,
signatures: proposal.signatures,
calldatas: proposal.calldatas,
startBlock: proposal.startBlock,
description: proposal.description,
},
title: proposal.description.split(/#+\s|\n/g)[1] || `${proposal.description.slice(0, 20)}...`,
txHash: proposal.transactionHash,
};
};
60 changes: 60 additions & 0 deletions src/views/Governance/hooks/useGetProposalFromSubgraph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useQuery } from "@tanstack/react-query";
import request, { gql } from "graphql-request";
import { Environment } from "src/helpers/environment/Environment/Environment";
import { normalizeProposal } from "src/views/Governance/helpers/normalizeProposal";

export type Proposal = {
proposalId: string;
proposer: string;
targets: string[];
signatures: string[];
calldatas: string[];
transactionHash: string;
description: string;
blockTimestamp: string;
blockNumber: string;
startBlock: string;
values: string[];
};

type ProposalResponse = {
proposalCreated: Proposal;
};

export const useGetProposalFromSubgraph = ({ proposalId }: { proposalId?: string }) => {
const query = gql`
query {
proposalCreated(id: ${proposalId}) {
proposalId
proposer
targets
signatures
calldatas
transactionHash
description
blockTimestamp
blockNumber
startBlock
values
}
}
`;

return useQuery(
["getProposal", proposalId],
async () => {
try {
const subgraphUrl = Environment.getGovernanceSubgraphUrl();
const response = await request<ProposalResponse>(subgraphUrl, query);
if (!response.proposalCreated) {
return null;
}
return normalizeProposal(response.proposalCreated);
} catch (error) {
console.error("useGetProposalFromSubgraph", error);
return null;
}
},
{ enabled: !!proposalId },
);
};
87 changes: 0 additions & 87 deletions src/views/Governance/hooks/useGetProposals.tsx

This file was deleted.

Loading
Loading