diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b4f8ef10..a12869c1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ changes. ### Added - Preserve maintenance ending banner state on the wallet connection change [Issue 3681](https://github.com/IntersectMBO/govtool/issues/3681) +- Add authors for Live Voting Governance Actions [Issue 3745](https://github.com/IntersectMBO/govtool/issues/3745) ### Fixed diff --git a/govtool/backend/sql/list-proposals.sql b/govtool/backend/sql/list-proposals.sql index 8ccbfd3d1..6f20e706f 100644 --- a/govtool/backend/sql/list-proposals.sql +++ b/govtool/backend/sql/list-proposals.sql @@ -303,7 +303,18 @@ SELECT COALESCE(cv.ccNoVotes, 0) cc_no_votes, COALESCE(cv.ccAbstainVotes, 0) cc_abstain_votes, prev_gov_action.index as prev_gov_action_index, - encode(prev_gov_action_tx.hash, 'hex') as prev_gov_action_tx_hash + encode(prev_gov_action_tx.hash, 'hex') as prev_gov_action_tx_hash, + COALESCE( + json_agg( + json_build_object( + 'name', off_chain_vote_author.name, + 'witness_algorithm', off_chain_vote_author.witness_algorithm, + 'public_key', off_chain_vote_author.public_key, + 'signature', off_chain_vote_author.signature + ) + ) FILTER (WHERE off_chain_vote_author.id IS NOT NULL), + '[]' + ) authors FROM gov_action_proposal JOIN ActiveProposals ON gov_action_proposal.id = ActiveProposals.id @@ -314,6 +325,7 @@ FROM LEFT JOIN block AS creator_block ON creator_block.id = creator_tx.block_id LEFT JOIN voting_anchor ON voting_anchor.id = gov_action_proposal.voting_anchor_id LEFT JOIN off_chain_vote_data ON off_chain_vote_data.voting_anchor_id = voting_anchor.id + lEFT JOIN off_chain_vote_author ON off_chain_vote_author.off_chain_vote_data_id = off_chain_vote_data.id LEFT JOIN off_chain_vote_gov_action_data ON off_chain_vote_gov_action_data.off_chain_vote_data_id = off_chain_vote_data.id LEFT JOIN param_proposal AS proposal_params ON gov_action_proposal.param_proposal = proposal_params.id LEFT JOIN cost_model AS cost_model ON proposal_params.cost_model_id = cost_model.id diff --git a/govtool/backend/src/VVA/API.hs b/govtool/backend/src/VVA/API.hs index ebc4d9609..f8424d149 100644 --- a/govtool/backend/src/VVA/API.hs +++ b/govtool/backend/src/VVA/API.hs @@ -244,7 +244,8 @@ proposalToResponse timeZone Types.Proposal {..} = proposalResponseCcNoVotes = proposalCcNoVotes, proposalResponseCcAbstainVotes = proposalCcAbstainVotes, proposalResponsePrevGovActionIndex = proposalPrevGovActionIndex, - proposalResponsePrevGovActionTxHash = HexText <$> proposalPrevGovActionTxHash + proposalResponsePrevGovActionTxHash = HexText <$> proposalPrevGovActionTxHash, + proposalResponseAuthors = ProposalAuthors <$> proposalAuthors } voteToResponse :: Types.Vote -> VoteParams diff --git a/govtool/backend/src/VVA/API/Types.hs b/govtool/backend/src/VVA/API/Types.hs index a270a2872..163ff7c68 100644 --- a/govtool/backend/src/VVA/API/Types.hs +++ b/govtool/backend/src/VVA/API/Types.hs @@ -401,9 +401,39 @@ data ProposalResponse , proposalResponseCcAbstainVotes :: Integer , proposalResponsePrevGovActionIndex :: Maybe Integer , proposalResponsePrevGovActionTxHash :: Maybe HexText + , proposalResponseAuthors :: Maybe ProposalAuthors } deriving (Generic, Show) +newtype ProposalAuthors = ProposalAuthors { getProposalAuthors :: Value } + deriving newtype (Show) + +instance FromJSON ProposalAuthors where + parseJSON v@(Array _) = pure $ ProposalAuthors v + parseJSON _ = fail "ProposalAuthors must be a JSON array" + +instance ToJSON ProposalAuthors where + toJSON (ProposalAuthors v) = v + +instance ToSchema ProposalAuthors where + declareNamedSchema _ = pure $ NamedSchema (Just "ProposalAuthors") $ mempty + & type_ ?~ OpenApiArray + & description ?~ "A JSON array of proposal authors" + & example ?~ toJSON + [ object + [ "name" .= ("Alice" :: Text) + , "witness_algorithm" .= ("algo" :: Text) + , "public_key" .= ("key" :: Text) + , "signature" .= ("sig" :: Text) + ] + , object + [ "name" .= ("Bob" :: Text) + , "witness_algorithm" .= ("algo2" :: Text) + , "public_key" .= ("key2" :: Text) + , "signature" .= ("sig2" :: Text) + ] + ] + deriveJSON (jsonOptions "proposalResponse") ''ProposalResponse exampleProposalResponse :: Text diff --git a/govtool/backend/src/VVA/Types.hs b/govtool/backend/src/VVA/Types.hs index 217b19a5a..190eb8a78 100644 --- a/govtool/backend/src/VVA/Types.hs +++ b/govtool/backend/src/VVA/Types.hs @@ -208,6 +208,7 @@ data Proposal , proposalCcAbstainVotes :: Integer , proposalPrevGovActionIndex :: Maybe Integer , proposalPrevGovActionTxHash :: Maybe Text + , proposalAuthors :: Maybe Value } deriving (Show) @@ -241,6 +242,7 @@ instance FromRow Proposal where <*> (floor @Scientific <$> field) -- proposalCcAbstainVotes <*> field -- prevGovActionIndex <*> field -- prevGovActionTxHash + <*> field -- proposalAuthors data TransactionStatus = TransactionStatus { transactionConfirmed :: Bool diff --git a/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx b/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx index b6a0ae992..c06b9c1c6 100644 --- a/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx +++ b/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx @@ -15,7 +15,7 @@ import { useModal } from "@/context"; type BaseProps = { label: string; - text?: string | number; + text?: React.ReactNode; dataTestId?: string; isSliderCard?: boolean; tooltipProps?: Omit; @@ -108,7 +108,7 @@ export const GovernanceActionCardElement = ({ ...(isSemiTransparent && { opacity: 0.75 }), }} > - {isMarkdown ? removeMarkdown(text) : text} + {typeof text === "string" && isMarkdown ? removeMarkdown(text) : text} ); diff --git a/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx b/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx index 2d7524e4f..cd844d375 100644 --- a/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx +++ b/govtool/frontend/src/components/organisms/GovernanceActionDetailsCardData.tsx @@ -1,8 +1,8 @@ -import { useMemo, useState } from "react"; +import { useMemo, useState, Fragment } from "react"; import { Box, Tabs, Tab, styled, Skeleton } from "@mui/material"; import { useLocation } from "react-router-dom"; -import { CopyButton, ExternalModalButton, Typography } from "@atoms"; +import { CopyButton, ExternalModalButton, Tooltip, Typography } from "@atoms"; import { GovernanceActionCardElement, GovernanceActionDetailsCardLinks, @@ -76,6 +76,7 @@ export const GovernanceActionDetailsCardData = ({ isValidating, proposal: { abstract, + authors, createdDate, createdEpochNo, details, @@ -365,6 +366,35 @@ export const GovernanceActionDetailsCardData = ({ /> )} + ( + + + {author.name} + + {idx < arr.length - 1 && } + + )) + } + textVariant="longText" + dataTestId="authors" + /> diff --git a/govtool/frontend/src/i18n/locales/en.json b/govtool/frontend/src/i18n/locales/en.json index 2bfe893ef..439d4ce0b 100644 --- a/govtool/frontend/src/i18n/locales/en.json +++ b/govtool/frontend/src/i18n/locales/en.json @@ -421,6 +421,13 @@ "amount": "Amount:", "anchorURL": "Metadata anchor link", "anchorHash": "Metadata anchor hash", + "authors": { + "noDataAvailable": "No data available", + "title": "Authors", + "publicKey": "Public Key", + "signature": "Signature", + "witnessAlgorithm": "Witness Algorithm" + }, "backToGovActions": "Back to Governance Actions", "castVote": "<0>You voted {{vote}} on this proposal\non {{date}} (Epoch {{epoch}})", "castVoteDeadline": "You can change your vote up to {{date}} (Epoch {{epoch}})", diff --git a/govtool/frontend/src/models/api.ts b/govtool/frontend/src/models/api.ts index 0b6dbd473..0ade19812 100644 --- a/govtool/frontend/src/models/api.ts +++ b/govtool/frontend/src/models/api.ts @@ -234,6 +234,12 @@ export type ProposalData = { references?: Reference[]; title?: string; protocolParams: EpochParams | null; + authors?: { + name?: string; + witnessAlgorithm?: string; + publicKey?: string; + signature?: string; + }[]; } & SubmittedVotesData; export type NewConstitutionAnchor = { @@ -270,8 +276,8 @@ type DRepVotingPower = { export type DRepVotingPowerListResponse = DRepVotingPower[]; export type Account = { - id: number, - view: string, - isRegistered: boolean, - isScriptBased: boolean -} + id: number; + view: string; + isRegistered: boolean; + isScriptBased: boolean; +}; diff --git a/govtool/frontend/src/stories/GovernanceActionDetailsCard.stories.ts b/govtool/frontend/src/stories/GovernanceActionDetailsCard.stories.ts index 7d1b14eab..7f004fb0a 100644 --- a/govtool/frontend/src/stories/GovernanceActionDetailsCard.stories.ts +++ b/govtool/frontend/src/stories/GovernanceActionDetailsCard.stories.ts @@ -64,6 +64,20 @@ const commonArgs = { label: "Example label", }, ], + authors: [ + { + name: "Alice Cardana", + witnessAlgorithm: "Ed25519", + publicKey: "ed25519_pk1qwertyuiopasdfghjklzxcvbnm1234567890abcdef", + signature: "ed25519_sig1abcdef1234567890qwertyuiopasdfghjklzxcvbnm", + }, + { + name: "Bob Stakepool", + witnessAlgorithm: "Ed25519", + publicKey: "ed25519_pk1asdfghjklqwertyuiopzxcvbnm0987654321abcdf", + signature: "ed25519_sig1zxcvbnm0987654321asdfghjklqwertyuiop", + }, + ], } satisfies ProposalData, };