diff --git a/.github/workflows/test_backend.yml b/.github/workflows/test_backend.yml
index 28c7b1599..2e01c721b 100644
--- a/.github/workflows/test_backend.yml
+++ b/.github/workflows/test_backend.yml
@@ -162,7 +162,7 @@ jobs:
publish-status:
runs-on: ubuntu-latest
- if: always()
+ if: always() && needs.backend-tests.result != 'skipped'
needs: [backend-tests, publish-report]
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/test_integration_playwright.yml b/.github/workflows/test_integration_playwright.yml
index bbb9c9052..f17e059e8 100644
--- a/.github/workflows/test_integration_playwright.yml
+++ b/.github/workflows/test_integration_playwright.yml
@@ -202,7 +202,7 @@ jobs:
publish-status:
runs-on: ubuntu-latest
- if: always()
+ if: always() && needs.integration-tests.result != 'skipped'
needs: [integration-tests, publish-report]
steps:
- uses: actions/checkout@v4
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f5c444349..980512907 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,8 +18,26 @@ changes.
### Removed
-## [v2.0.17](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.17) 2025-03-18
+## [v2.0.18](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.18) 2025-03-26
+
+### Added
+
+- Add redirection to outcomes when proposal is not found [Issue 3230](https://github.com/IntersectMBO/govtool/issues/3230)
+- Add DRep voting power list endpoint [Issue 3263](https://github.com/IntersectMBO/govtool/issues/3263)
+- Add DRep given name to the voting power list endpoint [Issue 3273](https://github.com/IntersectMBO/govtool/issues/3273)
+- Add DRep voting power list query to PDF Pillar [Issue 3277](https://github.com/IntersectMBO/govtool/issues/3277)
+
+### Fixed
+
+- Fix post-vote navigation to governance action list [Issue 3242](https://github.com/IntersectMBO/govtool/issues/3242)
+### Changed
+
+- Bump CSL to v14 [Issue 3037](https://github.com/IntersectMBO/govtool/issues/3037)
+
+### Removed
+
+## [v2.0.17](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.17) 2025-03-18
### Added
diff --git a/govtool/backend/Dockerfile b/govtool/backend/Dockerfile
index 768cfd7cf..cd0b1ac6c 100644
--- a/govtool/backend/Dockerfile
+++ b/govtool/backend/Dockerfile
@@ -4,4 +4,4 @@ FROM $BASE_IMAGE_REPO:$BASE_IMAGE_TAG
WORKDIR /src
COPY . .
RUN cabal build
-RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-2.0.17/x/vva-be/build/vva-be/vva-be /usr/local/bin
+RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-2.0.18/x/vva-be/build/vva-be/vva-be /usr/local/bin
diff --git a/govtool/backend/Dockerfile.qovery b/govtool/backend/Dockerfile.qovery
index 53a840711..b698475e0 100644
--- a/govtool/backend/Dockerfile.qovery
+++ b/govtool/backend/Dockerfile.qovery
@@ -4,7 +4,7 @@ FROM $BASE_IMAGE_REPO:$BASE_IMAGE_TAG
WORKDIR /src
COPY . .
RUN cabal build
-RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-2.0.17/x/vva-be/build/vva-be/vva-be /usr/local/bin
+RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-2.0.18/x/vva-be/build/vva-be/vva-be /usr/local/bin
# Expose the necessary port
EXPOSE 9876
diff --git a/govtool/backend/app/Main.hs b/govtool/backend/app/Main.hs
index 66514e170..c2d3ca711 100644
--- a/govtool/backend/app/Main.hs
+++ b/govtool/backend/app/Main.hs
@@ -124,6 +124,7 @@ startApp vvaConfig sentryService = do
networkMetricsCache <- newCache
networkInfoCache <- newCache
networkTotalStakeCache <- newCache
+ dRepVotingPowerListCache <- newCache
return $ CacheEnv
{ proposalListCache
, getProposalCache
@@ -137,6 +138,7 @@ startApp vvaConfig sentryService = do
, networkMetricsCache
, networkInfoCache
, networkTotalStakeCache
+ , dRepVotingPowerListCache
}
let connectionString = encodeUtf8 (dbSyncConnectionString $ getter vvaConfig)
diff --git a/govtool/backend/sql/get-dreps-voting-power-list.sql b/govtool/backend/sql/get-dreps-voting-power-list.sql
new file mode 100644
index 000000000..9d3701847
--- /dev/null
+++ b/govtool/backend/sql/get-dreps-voting-power-list.sql
@@ -0,0 +1,37 @@
+WITH LatestExistingVotingAnchor AS (
+ SELECT
+ subquery.drep_registration_id,
+ subquery.drep_hash_id,
+ subquery.voting_anchor_id,
+ subquery.url,
+ subquery.metadata_hash,
+ subquery.ocvd_id
+ FROM (
+ SELECT
+ dr.id AS drep_registration_id,
+ dr.drep_hash_id,
+ va.id AS voting_anchor_id,
+ va.url,
+ encode(va.data_hash, 'hex') AS metadata_hash,
+ ocvd.id AS ocvd_id,
+ ROW_NUMBER() OVER (PARTITION BY dr.drep_hash_id ORDER BY dr.tx_id DESC) AS rn
+ FROM
+ drep_registration dr
+ JOIN voting_anchor va ON dr.voting_anchor_id = va.id
+ JOIN off_chain_vote_data ocvd ON va.id = ocvd.voting_anchor_id
+ WHERE
+ ocvd.voting_anchor_id IS NOT NULL
+ ) subquery
+ WHERE
+ subquery.rn = 1
+)
+SELECT DISTINCT ON (raw)
+ view,
+ encode(raw, 'hex') AS hash_raw,
+ COALESCE(dd.amount, 0) AS voting_power,
+ ocvdd.given_name
+FROM drep_hash dh
+LEFT JOIN drep_distr dd ON dh.id = dd.hash_id AND dd.epoch_no = (SELECT MAX(no) from epoch)
+LEFT JOIN LatestExistingVotingAnchor leva ON leva.drep_hash_id = dh.id
+LEFT JOIN off_chain_vote_data ocvd ON ocvd.id = leva.ocvd_id
+LEFT JOIN off_chain_vote_drep_data ocvdd ON ocvdd.off_chain_vote_data_id = ocvd.id
\ No newline at end of file
diff --git a/govtool/backend/sql/get-filtered-dreps-voting-power.sql b/govtool/backend/sql/get-filtered-dreps-voting-power.sql
new file mode 100644
index 000000000..74c4b4700
--- /dev/null
+++ b/govtool/backend/sql/get-filtered-dreps-voting-power.sql
@@ -0,0 +1,38 @@
+WITH LatestExistingVotingAnchor AS (
+ SELECT
+ subquery.drep_registration_id,
+ subquery.drep_hash_id,
+ subquery.voting_anchor_id,
+ subquery.url,
+ subquery.metadata_hash,
+ subquery.ocvd_id
+ FROM (
+ SELECT
+ dr.id AS drep_registration_id,
+ dr.drep_hash_id,
+ va.id AS voting_anchor_id,
+ va.url,
+ encode(va.data_hash, 'hex') AS metadata_hash,
+ ocvd.id AS ocvd_id,
+ ROW_NUMBER() OVER (PARTITION BY dr.drep_hash_id ORDER BY dr.tx_id DESC) AS rn
+ FROM
+ drep_registration dr
+ JOIN voting_anchor va ON dr.voting_anchor_id = va.id
+ JOIN off_chain_vote_data ocvd ON va.id = ocvd.voting_anchor_id
+ WHERE
+ ocvd.voting_anchor_id IS NOT NULL
+ ) subquery
+ WHERE
+ subquery.rn = 1
+)
+SELECT DISTINCT ON (raw)
+ view,
+ encode(raw, 'hex') AS hash_raw,
+ COALESCE(dd.amount, 0) AS voting_power,
+ ocvdd.given_name
+FROM drep_hash dh
+LEFT JOIN drep_distr dd ON dh.id = dd.hash_id AND dd.epoch_no = (SELECT MAX(no) from epoch)
+LEFT JOIN LatestExistingVotingAnchor leva ON leva.drep_hash_id = dh.id
+LEFT JOIN off_chain_vote_data ocvd ON ocvd.id = leva.ocvd_id
+LEFT JOIN off_chain_vote_drep_data ocvdd ON ocvdd.off_chain_vote_data_id = ocvd.id
+WHERE view = ? OR encode(raw, 'hex') = ?
\ No newline at end of file
diff --git a/govtool/backend/src/VVA/API.hs b/govtool/backend/src/VVA/API.hs
index 5bfbd304d..b95960f52 100644
--- a/govtool/backend/src/VVA/API.hs
+++ b/govtool/backend/src/VVA/API.hs
@@ -15,7 +15,7 @@ import Control.Monad.Reader
import Data.Aeson (Value(..), Array, decode, encode, ToJSON, toJSON)
import Data.Bool (Bool)
-import Data.List (sortOn)
+import Data.List (sortOn, sort)
import qualified Data.Map as Map
import Data.Maybe (Maybe (Nothing), catMaybes, fromMaybe, mapMaybe)
import Data.Ord (Down (..))
@@ -64,6 +64,9 @@ type VVAApi =
:> QueryParam "search" Text
:> Get '[JSON] [VoteResponse]
:<|> "drep" :> "info" :> Capture "drepId" HexText :> Get '[JSON] DRepInfoResponse
+ :<|> "drep" :> "voting-power-list"
+ :> QueryParams "identifiers" Text
+ :> Get '[JSON] [DRepVotingPowerListResponse]
:<|> "ada-holder" :> "get-current-delegation" :> Capture "stakeKey" HexText :> Get '[JSON] (Maybe DelegationResponse)
:<|> "ada-holder" :> "get-voting-power" :> Capture "stakeKey" HexText :> Get '[JSON] Integer
:<|> "proposal" :> "list"
@@ -87,6 +90,7 @@ server = drepList
:<|> getVotingPower
:<|> getVotes
:<|> drepInfo
+ :<|> drepVotingPowerList
:<|> getCurrentDelegation
:<|> getStakeKeyVotingPower
:<|> listProposals
@@ -283,18 +287,20 @@ getVotes (unHexText -> dRepId) selectedTypes sortMode mSearch = do
CacheEnv {dRepGetVotesCache} <- asks vvaCache
(votes, proposals) <- cacheRequest dRepGetVotesCache dRepId $ DRep.getVotes dRepId []
- let voteMap = Map.fromList $ map (\vote@Types.Vote {..} -> (voteProposalId, vote)) votes
-
- processedProposals <- filter (isProposalSearchedFor mSearch) <$> mapSortAndFilterProposals selectedTypes sortMode proposals
-
+ let voteMapByTxHash = Map.fromList $
+ map (\vote -> (pack $ Prelude.takeWhile (/= '#') (unpack $ Types.voteGovActionId vote), vote)) votes
+
+ processedProposals <- filter (isProposalSearchedFor mSearch) <$>
+ mapSortAndFilterProposals selectedTypes sortMode proposals
+
return $
[ VoteResponse
{ voteResponseVote = voteToResponse vote
, voteResponseProposal = proposalResponse
}
- | proposalResponse@ProposalResponse{proposalResponseId} <- processedProposals
- , let proposalIdInt = read (unpack proposalResponseId) :: Int
- , Just vote <- [Map.lookup (toInteger proposalIdInt) voteMap]
+ | proposalResponse <- processedProposals
+ , let txHash = unHexText (proposalResponseTxHash proposalResponse)
+ , Just vote <- [Map.lookup txHash voteMapByTxHash]
]
drepInfo :: App m => HexText -> m DRepInfoResponse
@@ -324,6 +330,25 @@ drepInfo (unHexText -> dRepId) = do
, dRepInfoResponseImageHash = HexText <$> dRepInfoImageHash
}
+drepVotingPowerList :: App m => [Text] -> m [DRepVotingPowerListResponse]
+drepVotingPowerList identifiers = do
+ CacheEnv {dRepVotingPowerListCache} <- asks vvaCache
+
+ let cacheKey = Text.intercalate "," (sort identifiers)
+
+ results <- cacheRequest dRepVotingPowerListCache cacheKey $
+ DRep.getDRepsVotingPowerList identifiers
+
+ return $ map toDRepVotingPowerListResponse results
+ where
+ toDRepVotingPowerListResponse Types.DRepVotingPowerList{..} =
+ DRepVotingPowerListResponse
+ { drepVotingPowerListResponseView = drepView
+ , drepVotingPowerListResponseHashRaw = HexText drepHashRaw
+ , drepVotingPowerListResponseVotingPower = drepVotingPower
+ , drepVotingPowerListResponseGivenName = drepGivenName
+ }
+
getCurrentDelegation :: App m => HexText -> m (Maybe DelegationResponse)
getCurrentDelegation (unHexText -> stakeKey) = do
CacheEnv {adaHolderGetCurrentDelegationCache} <- asks vvaCache
diff --git a/govtool/backend/src/VVA/API/Types.hs b/govtool/backend/src/VVA/API/Types.hs
index ec95925e9..d1c88adb0 100644
--- a/govtool/backend/src/VVA/API/Types.hs
+++ b/govtool/backend/src/VVA/API/Types.hs
@@ -622,6 +622,37 @@ instance ToSchema DRepInfoResponse where
& example
?~ toJSON exampleDRepInfoResponse
+data DRepVotingPowerListResponse
+ = DRepVotingPowerListResponse
+ { drepVotingPowerListResponseView :: Text
+ , drepVotingPowerListResponseHashRaw :: HexText
+ , drepVotingPowerListResponseVotingPower :: Integer
+ , drepVotingPowerListResponseGivenName :: Maybe Text
+ }
+ deriving (Generic, Show)
+
+deriveJSON (jsonOptions "drepVotingPowerListResponse") ''DRepVotingPowerListResponse
+
+exampleDRepVotingPowerListResponse :: Text
+exampleDRepVotingPowerListResponse =
+ "{\"view\": \"drep1qq5n7k0r0ff6lf4qvndw9t7vmdqa9y3q9qtjq879rrk9vcjcdy8a4xf92mqsajf9u3nrsh3r6zrp29kuydmfq45fz88qpzmjkc\","
+ <> "\"hashRaw\": \"9af10e89979e51b8cdc827c963124a1ef4920d1253eef34a1d5cfe76438e3f11\","
+ <> "\"votingPower\": 1000000,"
+ <> "\"givenName\": \"John Doe\"}"
+
+instance ToSchema DRepVotingPowerListResponse where
+ declareNamedSchema proxy = do
+ NamedSchema name_ schema_ <-
+ genericDeclareNamedSchema
+ ( fromAesonOptions $ jsonOptions "drepVotingPowerListResponse" )
+ proxy
+ return $
+ NamedSchema name_ $
+ schema_
+ & description ?~ "DRep Voting Power List Response"
+ & example
+ ?~ toJSON exampleDRepVotingPowerListResponse
+
data GetProposalResponse
= GetProposalResponse
{ getProposalResponseVote :: Maybe VoteParams
diff --git a/govtool/backend/src/VVA/DRep.hs b/govtool/backend/src/VVA/DRep.hs
index 187cdc73d..49482b1e9 100644
--- a/govtool/backend/src/VVA/DRep.hs
+++ b/govtool/backend/src/VVA/DRep.hs
@@ -6,31 +6,33 @@
module VVA.DRep where
-import Control.Monad.Except (MonadError)
+import Control.Monad.Except (MonadError)
import Control.Monad.Reader
import Crypto.Hash
-import Data.ByteString (ByteString)
-import qualified Data.ByteString.Base16 as Base16
-import qualified Data.ByteString.Char8 as C
-import Data.FileEmbed (embedFile)
-import Data.Foldable (Foldable (sum))
-import Data.Has (Has)
-import qualified Data.Map as M
-import Data.Maybe (fromMaybe, isJust, isNothing)
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Base16 as Base16
+import qualified Data.ByteString.Char8 as C
+import Data.FileEmbed (embedFile)
+import Data.Foldable (Foldable (sum))
+import Data.Has (Has)
+import qualified Data.Map as M
+import Data.Maybe (fromMaybe, isJust, isNothing)
import Data.Scientific
-import Data.String (fromString)
-import Data.Text (Text, pack, unpack)
-import qualified Data.Text.Encoding as Text
+import Data.String (fromString)
+import Data.Text (Text, pack, unpack, intercalate)
+import qualified Data.Text.Encoding as Text
import Data.Time
-import qualified Database.PostgreSQL.Simple as SQL
+import qualified Database.PostgreSQL.Simple as SQL
+import Database.PostgreSQL.Simple.Types (In(..))
+
import VVA.Config
-import VVA.Pool (ConnectionPool, withPool)
-import qualified VVA.Proposal as Proposal
-import VVA.Types (AppError, DRepInfo (..), DRepRegistration (..), DRepStatus (..),
- DRepType (..), Proposal (..), Vote (..))
+import VVA.Pool (ConnectionPool, withPool)
+import qualified VVA.Proposal as Proposal
+import VVA.Types (AppError, DRepInfo (..), DRepRegistration (..), DRepStatus (..),
+ DRepType (..), Proposal (..), Vote (..), DRepVotingPowerList (..))
sqlFrom :: ByteString -> SQL.Query
sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs
@@ -115,16 +117,14 @@ getVotes ::
m ([Vote], [Proposal])
getVotes drepId selectedProposals = withPool $ \conn -> do
results <- liftIO $ SQL.query conn getVotesSql (SQL.Only drepId)
-
+
if null results
then return ([], [])
else do
let proposalsToSelect = if null selectedProposals
then [ govActionId | (_, govActionId, _, _, _, _, _, _, _) <- results]
else selectedProposals
-
allProposals <- mapM (Proposal.getProposals . Just . (:[])) proposalsToSelect
-
let proposals = concat allProposals
let proposalMap = M.fromList $ map (\x -> (proposalId x, x)) proposals
@@ -132,7 +132,7 @@ getVotes drepId selectedProposals = withPool $ \conn -> do
timeZone <- liftIO getCurrentTimeZone
let votes =
- [ Vote proposalId' drepId' vote' url' docHash' epochNo' (localTimeToUTC timeZone date') voteTxHash'
+ [ Vote proposalId' govActionId' drepId' vote' url' docHash' epochNo' (localTimeToUTC timeZone date') voteTxHash'
| (proposalId', govActionId', drepId', vote', url', docHash', epochNo', date', voteTxHash') <- results
, govActionId' `elem` proposalsToSelect
]
@@ -200,3 +200,29 @@ getDRepInfo drepId = withPool $ \conn -> do
}
[] -> return $ DRepInfo False False False False False Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
_ -> error "Unexpected result from database query in getDRepInfo"
+
+getAllDRepsVotingPowerSql :: SQL.Query
+getAllDRepsVotingPowerSql = sqlFrom $(embedFile "sql/get-dreps-voting-power-list.sql")
+
+getFilteredDRepVotingPowerSql :: SQL.Query
+getFilteredDRepVotingPowerSql = sqlFrom $(embedFile "sql/get-filtered-dreps-voting-power.sql")
+
+getDRepsVotingPowerList ::
+ (Has ConnectionPool r, Has VVAConfig r, MonadReader r m, MonadIO m) =>
+ [Text] ->
+ m [DRepVotingPowerList]
+getDRepsVotingPowerList identifiers = withPool $ \conn -> do
+ results <- if null identifiers
+ then do
+ liftIO $ SQL.query_ conn getAllDRepsVotingPowerSql
+ else do
+ resultsPerIdentifier <- forM identifiers $ \identifier -> do
+ liftIO $ SQL.query conn getFilteredDRepVotingPowerSql (identifier, identifier)
+
+ return $ concat resultsPerIdentifier
+
+ return
+ [ DRepVotingPowerList view hashRaw votingPower givenName
+ | (view, hashRaw, votingPower', givenName) <- results
+ , let votingPower = floor @Scientific votingPower'
+ ]
\ No newline at end of file
diff --git a/govtool/backend/src/VVA/Types.hs b/govtool/backend/src/VVA/Types.hs
index 5d1f3ca5f..35cdd37bf 100644
--- a/govtool/backend/src/VVA/Types.hs
+++ b/govtool/backend/src/VVA/Types.hs
@@ -61,14 +61,15 @@ instance Exception AppError
data Vote
= Vote
- { voteProposalId :: Integer
- , voteDrepId :: Text
- , voteVote :: Text
- , voteUrl :: Maybe Text
- , voteDocHash :: Maybe Text
- , voteEpochNo :: Integer
- , voteDate :: UTCTime
- , voteTxHash :: Text
+ { voteProposalId :: Integer
+ , voteGovActionId :: Text
+ , voteDrepId :: Text
+ , voteVote :: Text
+ , voteUrl :: Maybe Text
+ , voteDocHash :: Maybe Text
+ , voteEpochNo :: Integer
+ , voteDate :: UTCTime
+ , voteTxHash :: Text
}
data DRepInfo
@@ -95,6 +96,15 @@ data DRepInfo
, dRepInfoImageHash :: Maybe Text
}
+data DRepVotingPowerList
+ = DRepVotingPowerList
+ { drepView :: Text
+ , drepHashRaw :: Text
+ , drepVotingPower :: Integer
+ , drepGivenName :: Maybe Text
+ }
+ deriving (Show, Eq)
+
data DRepStatus = Active | Inactive | Retired deriving (Show, Eq, Ord)
data DRepType = DRep | SoleVoter deriving (Show, Eq)
@@ -215,6 +225,7 @@ data CacheEnv
, networkMetricsCache :: Cache.Cache () NetworkMetrics
, networkInfoCache :: Cache.Cache () NetworkInfo
, networkTotalStakeCache :: Cache.Cache () NetworkTotalStake
+ , dRepVotingPowerListCache :: Cache.Cache Text [DRepVotingPowerList]
}
data NetworkInfo
diff --git a/govtool/backend/vva-be.cabal b/govtool/backend/vva-be.cabal
index 704f1d705..d9a25afbb 100644
--- a/govtool/backend/vva-be.cabal
+++ b/govtool/backend/vva-be.cabal
@@ -1,6 +1,6 @@
cabal-version: 3.6
name: vva-be
-version: 2.0.17
+version: 2.0.18
-- A short (one-line) description of the package.
-- synopsis:
@@ -34,6 +34,8 @@ extra-source-files:
sql/get-network-metrics.sql
sql/get-network-info.sql
sql/get-network-total-stake.sql
+ sql/get-dreps-voting-power-list.sql
+ sql/get-filtered-dreps-voting-power.sql
executable vva-be
main-is: Main.hs
diff --git a/govtool/frontend/package-lock.json b/govtool/frontend/package-lock.json
index 9eb541707..1aa2694d7 100644
--- a/govtool/frontend/package-lock.json
+++ b/govtool/frontend/package-lock.json
@@ -1,19 +1,19 @@
{
"name": "@govtool/frontend",
- "version": "2.0.17",
+ "version": "2.0.18",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@govtool/frontend",
- "version": "2.0.17",
+ "version": "2.0.18",
"hasInstallScript": true,
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
- "@emurgo/cardano-serialization-lib-asmjs": "^12.1.1",
+ "@emurgo/cardano-serialization-lib-asmjs": "^14.1.1",
"@hookform/resolvers": "^3.3.1",
- "@intersect.mbo/govtool-outcomes-pillar-ui": "1.2.5",
+ "@intersect.mbo/govtool-outcomes-pillar-ui": "1.3.0",
"@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8",
"@intersect.mbo/pdf-ui": "0.6.4",
"@mui/icons-material": "^5.14.3",
@@ -2653,9 +2653,9 @@
"license": "MIT"
},
"node_modules/@emurgo/cardano-serialization-lib-asmjs": {
- "version": "12.1.1",
- "resolved": "https://registry.npmjs.org/@emurgo/cardano-serialization-lib-asmjs/-/cardano-serialization-lib-asmjs-12.1.1.tgz",
- "integrity": "sha512-K3f28QUfLDJ7seO6MtKfMYtRm5ccf36TQ5yxyTmZqX1TA85MkriEdxqpgV9KLiLEA95emwnlvU2/WmlHMRPg1A==",
+ "version": "14.1.1",
+ "resolved": "https://registry.npmjs.org/@emurgo/cardano-serialization-lib-asmjs/-/cardano-serialization-lib-asmjs-14.1.1.tgz",
+ "integrity": "sha512-Q2HVpPRt417Quxv3qagGWbkJQU8SiQCl1K/344ZtQMwsLoqTfRlCNzmSWMBN7jyBxbtKoh+vdbSiLqwG1NAjYg==",
"license": "MIT"
},
"node_modules/@esbuild/aix-ppc64": {
@@ -3389,16 +3389,20 @@
}
},
"node_modules/@intersect.mbo/govtool-outcomes-pillar-ui": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@intersect.mbo/govtool-outcomes-pillar-ui/-/govtool-outcomes-pillar-ui-1.2.5.tgz",
- "integrity": "sha512-z0QDUZKj262vvNCtjr2L8ILgeub8da3ryhMhgLJpuxuO1zfEBUhTuwUPkRuLBE1Gf52TW/AfgmR2qXANsjY4/Q==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@intersect.mbo/govtool-outcomes-pillar-ui/-/govtool-outcomes-pillar-ui-1.3.0.tgz",
+ "integrity": "sha512-6+H+QG8kyM2UUEycNsjrF1K5+UGUw6+wy7gRxlyOtFjIrZ9CUdTfwwyD1hrh+g55awZ4t+EmQgYB00An0iXOag==",
"license": "ISC",
"dependencies": {
"@fontsource/poppins": "^5.0.14",
"@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8",
- "axios": "^1.7.9",
+ "axios": "^1.8.4",
"bech32": "^2.0.0",
- "buffer": "^6.0.3"
+ "buffer": "^6.0.3",
+ "react-diff-view": "^3.2.1",
+ "rehype-katex": "^7.0.1",
+ "remark-math": "^6.0.0",
+ "unidiff": "^1.0.4"
},
"peerDependencies": {
"@emotion/react": "^11.11.4",
@@ -3444,6 +3448,12 @@
"sass": "^1.77.2"
}
},
+ "node_modules/@intersect.mbo/pdf-ui/node_modules/@emurgo/cardano-serialization-lib-asmjs": {
+ "version": "12.1.1",
+ "resolved": "https://registry.npmjs.org/@emurgo/cardano-serialization-lib-asmjs/-/cardano-serialization-lib-asmjs-12.1.1.tgz",
+ "integrity": "sha512-K3f28QUfLDJ7seO6MtKfMYtRm5ccf36TQ5yxyTmZqX1TA85MkriEdxqpgV9KLiLEA95emwnlvU2/WmlHMRPg1A==",
+ "license": "MIT"
+ },
"node_modules/@intersect.mbo/pdf-ui/node_modules/@types/hast": {
"version": "2.3.10",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz",
@@ -10025,9 +10035,9 @@
}
},
"node_modules/axios": {
- "version": "1.7.9",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
- "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
+ "version": "1.8.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
+ "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
diff --git a/govtool/frontend/package.json b/govtool/frontend/package.json
index ca68f3066..930773db3 100644
--- a/govtool/frontend/package.json
+++ b/govtool/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "@govtool/frontend",
"private": true,
- "version": "2.0.17",
+ "version": "2.0.18",
"type": "module",
"scripts": {
"build": "vite build",
@@ -25,9 +25,9 @@
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
- "@emurgo/cardano-serialization-lib-asmjs": "^12.1.1",
+ "@emurgo/cardano-serialization-lib-asmjs": "^14.1.1",
"@hookform/resolvers": "^3.3.1",
- "@intersect.mbo/govtool-outcomes-pillar-ui": "1.2.5",
+ "@intersect.mbo/govtool-outcomes-pillar-ui": "1.3.0",
"@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8",
"@intersect.mbo/pdf-ui": "0.6.4",
"@mui/icons-material": "^5.14.3",
diff --git a/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx b/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx
index d7dd7be60..d5ffa61c2 100644
--- a/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx
+++ b/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx
@@ -1,3 +1,4 @@
+import { useEffect } from "react";
import {
useNavigate,
useLocation,
@@ -5,8 +6,9 @@ import {
generatePath,
} from "react-router-dom";
import { Box, CircularProgress, Link, Typography } from "@mui/material";
+import { AxiosError } from "axios";
-import { ICONS, PATHS } from "@consts";
+import { ICONS, OUTCOMES_PATHS, PATHS } from "@consts";
import { useCardano } from "@context";
import {
useGetProposalQuery,
@@ -42,13 +44,24 @@ export const DashboardGovernanceActionDetails = () => {
const shortenedGovActionId =
txHash && getShortenedGovActionId(txHash, +index);
- const { data, isLoading } = useGetProposalQuery(
+ const { data, isLoading, error } = useGetProposalQuery(
fullProposalId ?? "",
!state?.proposal || !state?.vote,
);
const proposal = (data ?? state)?.proposal;
const vote = (data ?? state)?.vote;
+ useEffect(() => {
+ const isProposalNotFound =
+ (error as AxiosError)?.response?.data ===
+ `Proposal with id: ${fullProposalId} not found`;
+ if (isProposalNotFound && fullProposalId) {
+ navigate(
+ OUTCOMES_PATHS.governanceActionOutcomes.replace(":id", fullProposalId),
+ );
+ }
+ }, [error]);
+
return (
{
searchPhrase: debouncedSearchText,
enabled: !isAdjusting,
});
+ const { data: votes, areDRepVotesLoading } = useGetDRepVotesQuery(
+ queryFilters,
+ chosenSorting,
+ debouncedSearchText,
+ );
+
+ // TODO: Black magic - that filtering should be done on the backend
+ const filteredProposals = proposals
+ ?.map((proposalCategory) => {
+ const filteredActions = proposalCategory.actions.filter((action) => {
+ const hasVote = votes?.some((voteCategory) =>
+ voteCategory.actions.some(
+ (voteAction) => voteAction.proposal.txHash === action.txHash,
+ ),
+ );
+
+ return !hasVote;
+ });
+
+ return {
+ ...proposalCategory,
+ actions: filteredActions,
+ };
+ })
+ .filter((category) => category.actions.length > 0);
const { state } = useLocation();
const [content, setContent] = useState(
@@ -189,14 +215,14 @@ export const DashboardGovernanceActions = () => {
onDashboard
searchPhrase={debouncedSearchText}
sorting={chosenSorting}
- proposals={proposals}
+ proposals={filteredProposals}
/>
>
diff --git a/govtool/frontend/src/components/organisms/DashboardGovernanceActionsVotedOn.tsx b/govtool/frontend/src/components/organisms/DashboardGovernanceActionsVotedOn.tsx
index 5d4dcf134..552f63061 100644
--- a/govtool/frontend/src/components/organisms/DashboardGovernanceActionsVotedOn.tsx
+++ b/govtool/frontend/src/components/organisms/DashboardGovernanceActionsVotedOn.tsx
@@ -2,38 +2,34 @@ import { useMemo } from "react";
import { Box, Typography, CircularProgress } from "@mui/material";
import { useCardano } from "@context";
-import {
- useGetDRepVotesQuery,
- useScreenDimension,
- useTranslation,
-} from "@hooks";
+import { useScreenDimension, useTranslation } from "@hooks";
import { GovernanceVotedOnCard } from "@molecules";
import { Slider } from "@organisms";
import { getFullGovActionId, getProposalTypeLabel } from "@utils";
+import { VotedProposal } from "@/models";
type DashboardGovernanceActionsVotedOnProps = {
- filters: string[];
searchPhrase?: string;
- sorting: string;
+ votes: {
+ title: string;
+ actions: VotedProposal[];
+ }[];
+ areDRepVotesLoading: boolean;
};
export const DashboardGovernanceActionsVotedOn = ({
- filters,
searchPhrase,
- sorting,
+ votes,
+ areDRepVotesLoading,
}: DashboardGovernanceActionsVotedOnProps) => {
- const { data, areDRepVotesLoading } = useGetDRepVotesQuery(
- filters,
- sorting,
- searchPhrase,
- );
const { isMobile } = useScreenDimension();
const { pendingTransaction } = useCardano();
const { t } = useTranslation();
+ // TODO: Filtering here is some kind of craziness. It should be done on the backend.
const filteredData = useMemo(() => {
- if (data.length && searchPhrase) {
- return data
+ if (votes.length && searchPhrase) {
+ return votes
.map((entry) => ({
...entry,
actions: entry.actions.filter((action) =>
@@ -44,8 +40,8 @@ export const DashboardGovernanceActionsVotedOn = ({
}))
.filter((entry) => entry.actions?.length > 0);
}
- return data;
- }, [data, searchPhrase, pendingTransaction.vote]);
+ return votes;
+ }, [votes, searchPhrase, pendingTransaction.vote]);
return areDRepVotesLoading ? (
@@ -53,7 +49,7 @@ export const DashboardGovernanceActionsVotedOn = ({
) : (
<>
- {!data.length ? (
+ {!votes.length ? (
{t("govActions.youHaventVotedYet")}
diff --git a/govtool/frontend/src/consts/queryKeys.ts b/govtool/frontend/src/consts/queryKeys.ts
index 825b5f316..722d6d9fa 100644
--- a/govtool/frontend/src/consts/queryKeys.ts
+++ b/govtool/frontend/src/consts/queryKeys.ts
@@ -13,6 +13,7 @@ export const QUERY_KEYS = {
useGetProposalsInfiniteKey: "useGetProposalsInfiniteKey",
useGetProposalsKey: "useGetProposalsKey",
useGetVoteContextFromFile: "useGetVoteContextFromFile",
+ useGetDRepVotingPowerListKey: "useGetDRepVotingPowerListKey",
};
export const MUTATION_KEYS = {
diff --git a/govtool/frontend/src/context/wallet.tsx b/govtool/frontend/src/context/wallet.tsx
index 77c3de83a..a9a38f2a9 100644
--- a/govtool/frontend/src/context/wallet.tsx
+++ b/govtool/frontend/src/context/wallet.tsx
@@ -540,6 +540,7 @@ const CardanoProvider = (props: Props) => {
.max_value_size(epochParams.max_val_size)
.max_tx_size(epochParams.max_tx_size)
.prefer_pure_change(true)
+ .do_not_burn_extra_change(true)
.ex_unit_prices(
ExUnitPrices.new(
UnitInterval.new(
diff --git a/govtool/frontend/src/hooks/queries/index.ts b/govtool/frontend/src/hooks/queries/index.ts
index db72778d2..73dea9952 100644
--- a/govtool/frontend/src/hooks/queries/index.ts
+++ b/govtool/frontend/src/hooks/queries/index.ts
@@ -13,3 +13,4 @@ export * from "./useGetProposalsInfiniteQuery";
export * from "./useGetProposalsQuery";
export * from "./useGetVoteContextTextFromFile";
export * from "./useGetVoterInfoQuery";
+export * from "./useGetDRepVotingPowerList";
diff --git a/govtool/frontend/src/hooks/queries/useGetDRepVotesQuery.ts b/govtool/frontend/src/hooks/queries/useGetDRepVotesQuery.ts
index bec791c61..0878191bc 100644
--- a/govtool/frontend/src/hooks/queries/useGetDRepVotesQuery.ts
+++ b/govtool/frontend/src/hooks/queries/useGetDRepVotesQuery.ts
@@ -30,6 +30,8 @@ export const useGetDRepVotesQuery = (
},
}),
enabled: !!dRepID,
+ refetchOnWindowFocus: true,
+ keepPreviousData: true,
});
const groupedByType = data?.reduce((groups, item) => {
diff --git a/govtool/frontend/src/hooks/queries/useGetDRepVotingPowerList.ts b/govtool/frontend/src/hooks/queries/useGetDRepVotingPowerList.ts
new file mode 100644
index 000000000..3c40d6ca9
--- /dev/null
+++ b/govtool/frontend/src/hooks/queries/useGetDRepVotingPowerList.ts
@@ -0,0 +1,32 @@
+import { useQuery, useQueryClient } from "react-query";
+import { getDRepVotingPowerList } from "@/services";
+import { QUERY_KEYS } from "@/consts";
+
+export const useGetDRepVotingPowerList = () => {
+ const queryClient = useQueryClient();
+
+ const {
+ data: dRepVotingPowerList,
+ isError,
+ error,
+ isLoading,
+ } = useQuery({
+ queryKey: [QUERY_KEYS.useGetDRepVotingPowerListKey],
+ queryFn: () => getDRepVotingPowerList([]),
+ enabled: false,
+ });
+
+ const fetchDRepVotingPowerList = async (identifiers: string[]) =>
+ queryClient.fetchQuery({
+ queryKey: [QUERY_KEYS.useGetDRepVotingPowerListKey],
+ queryFn: () => getDRepVotingPowerList(identifiers),
+ });
+
+ return {
+ dRepVotingPowerList,
+ fetchDRepVotingPowerList,
+ isError,
+ error,
+ isLoading,
+ };
+};
diff --git a/govtool/frontend/src/hooks/queries/useGetProposalQuery.ts b/govtool/frontend/src/hooks/queries/useGetProposalQuery.ts
index 309d6e48e..9828c6d59 100644
--- a/govtool/frontend/src/hooks/queries/useGetProposalQuery.ts
+++ b/govtool/frontend/src/hooks/queries/useGetProposalQuery.ts
@@ -7,7 +7,7 @@ import { getProposal } from "@services";
export const useGetProposalQuery = (proposalId: string, enabled?: boolean) => {
const { dRepID } = useCardano();
- const { data, isLoading, refetch, isRefetching } = useQuery(
+ const { data, isLoading, refetch, isRefetching, error } = useQuery(
[QUERY_KEYS.useGetProposalKey, dRepID, proposalId],
() => getProposal(proposalId, dRepID),
{
@@ -21,5 +21,6 @@ export const useGetProposalQuery = (proposalId: string, enabled?: boolean) => {
isLoading,
refetch,
isFetching: isRefetching,
+ error,
};
};
diff --git a/govtool/frontend/src/hooks/queries/useGetProposalsQuery.ts b/govtool/frontend/src/hooks/queries/useGetProposalsQuery.ts
index 6843f76af..0ebbe8677 100644
--- a/govtool/frontend/src/hooks/queries/useGetProposalsQuery.ts
+++ b/govtool/frontend/src/hooks/queries/useGetProposalsQuery.ts
@@ -12,7 +12,7 @@ export const useGetProposalsQuery = ({
sorting,
enabled,
}: GetProposalsArguments) => {
- const { dRepID, pendingTransaction } = useCardano();
+ const { dRepID } = useCardano();
const { voter } = useGetVoterInfo();
const fetchProposals = async (): Promise => {
@@ -34,17 +34,12 @@ export const useGetProposalsQuery = ({
};
const { data, isLoading } = useQuery(
- [
- QUERY_KEYS.useGetProposalsKey,
- filters,
- searchPhrase,
- sorting,
- dRepID,
- pendingTransaction.vote?.transactionHash,
- ],
+ [QUERY_KEYS.useGetProposalsKey, filters, searchPhrase, sorting, dRepID],
fetchProposals,
{
enabled,
+ refetchOnWindowFocus: true,
+ keepPreviousData: true,
},
);
diff --git a/govtool/frontend/src/models/api.ts b/govtool/frontend/src/models/api.ts
index 60890fd6d..f298b41f0 100644
--- a/govtool/frontend/src/models/api.ts
+++ b/govtool/frontend/src/models/api.ts
@@ -265,3 +265,12 @@ export type Infinite = {
pageSize: number;
total: number;
};
+
+type DRepVotingPower = {
+ view: string;
+ hashRaw: string;
+ votingPower: number;
+ givenName: string | null;
+};
+
+export type DRepVotingPowerListResponse = DRepVotingPower[];
diff --git a/govtool/frontend/src/pages/GovernanceActionOutComes.tsx b/govtool/frontend/src/pages/GovernanceActionOutComes.tsx
index cf703d9b0..79db53f63 100644
--- a/govtool/frontend/src/pages/GovernanceActionOutComes.tsx
+++ b/govtool/frontend/src/pages/GovernanceActionOutComes.tsx
@@ -3,6 +3,7 @@ import React, { Suspense } from "react";
import { Footer, TopNav } from "@/components/organisms";
import { useCardano } from "@/context";
import { useScreenDimension } from "@/hooks";
+import { Background } from "@/components/atoms";
const GovernanceActionsOutcomes = React.lazy(
() => import("@intersect.mbo/govtool-outcomes-pillar-ui/dist/esm"),
@@ -10,45 +11,49 @@ const GovernanceActionsOutcomes = React.lazy(
export const GovernanceActionOutComesPillar = () => {
const { pagePadding } = useScreenDimension();
- const { ...context } = useCardano();
+ const { walletApi, ...context } = useCardano();
return (
-
- {!context.isEnabled && }
+
-
-
-
- }
+ {!context.isEnabled && }
+
-
-
+
+
+
+ }
+ >
+
+
+
+ {!context.isEnabled && }
- {!context.isEnabled && }
-
+
);
};
diff --git a/govtool/frontend/src/pages/ProposalDiscussion.tsx b/govtool/frontend/src/pages/ProposalDiscussion.tsx
index bc98f170d..25d3c67bf 100644
--- a/govtool/frontend/src/pages/ProposalDiscussion.tsx
+++ b/govtool/frontend/src/pages/ProposalDiscussion.tsx
@@ -5,7 +5,7 @@ import { useCardano, useGovernanceActions } from "@/context";
import { useValidateMutation } from "@/hooks/mutations";
import { useScreenDimension } from "@/hooks/useScreenDimension";
import { Footer, TopNav } from "@/components/organisms";
-import { useGetVoterInfo } from "@/hooks";
+import { useGetDRepVotingPowerList, useGetVoterInfo } from "@/hooks";
const ProposalDiscussion = React.lazy(
() => import("@intersect.mbo/pdf-ui/cjs"),
@@ -17,6 +17,7 @@ export const ProposalDiscussionPillar = () => {
const { walletApi, ...context } = useCardano();
const { voter } = useGetVoterInfo();
const { createGovernanceActionJsonLD, createHash } = useGovernanceActions();
+ const { fetchDRepVotingPowerList } = useGetDRepVotingPowerList();
return (
{
typeof ProposalDiscussion
>["validateMetadata"]
}
+ fetchDRepVotingPowerList={fetchDRepVotingPowerList}
/>
diff --git a/govtool/frontend/src/services/index.ts b/govtool/frontend/src/services/index.ts
index f1b95ff25..d834a8762 100644
--- a/govtool/frontend/src/services/index.ts
+++ b/govtool/frontend/src/services/index.ts
@@ -1,3 +1,3 @@
export * from "./API";
-export * from "./requests/index";
+export * from "./requests";
diff --git a/govtool/frontend/src/services/requests/getDRepVotingPowerList.ts b/govtool/frontend/src/services/requests/getDRepVotingPowerList.ts
new file mode 100644
index 000000000..e0b1b1f04
--- /dev/null
+++ b/govtool/frontend/src/services/requests/getDRepVotingPowerList.ts
@@ -0,0 +1,15 @@
+import { DRepVotingPowerListResponse } from "@/models";
+import { API } from "../API";
+
+export const getDRepVotingPowerList = async (
+ identifiers: string[],
+): Promise => {
+ const params = new URLSearchParams();
+ identifiers.forEach((id: string) => params.append("identifiers", id));
+
+ const response = await API.get(
+ `/drep/voting-power-list?${params.toString()}`,
+ );
+
+ return response.data;
+};
diff --git a/govtool/frontend/src/services/requests/index.ts b/govtool/frontend/src/services/requests/index.ts
index 20047744c..0448747f2 100644
--- a/govtool/frontend/src/services/requests/index.ts
+++ b/govtool/frontend/src/services/requests/index.ts
@@ -21,3 +21,4 @@ export * from "./postDRepRegister";
export * from "./postDRepRemoveVote";
export * from "./postDRepRetire";
export * from "./postDRepVote";
+export * from "./getDRepVotingPowerList";
diff --git a/govtool/frontend/src/types/@intersect.mbo.d.ts b/govtool/frontend/src/types/@intersect.mbo.d.ts
index 9c8409f65..39af24c64 100644
--- a/govtool/frontend/src/types/@intersect.mbo.d.ts
+++ b/govtool/frontend/src/types/@intersect.mbo.d.ts
@@ -24,10 +24,16 @@ type ProposalDiscussionProps = {
| { status?: MetadataValidationStatus; metadata?: any; valid: boolean }
| undefined
>;
+ fetchDRepVotingPowerList: (
+ identifiers: string[],
+ ) => Promise;
};
type GovernanceActionsOutcomesProps = {
apiUrl?: string;
+ ipfsGateway?: string;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ walletAPI?: any;
};
declare module "@intersect.mbo/pdf-ui/cjs" {
diff --git a/govtool/frontend/src/types/global.d.ts b/govtool/frontend/src/types/global.d.ts
index 7f990a3d3..8e8ebaea5 100644
--- a/govtool/frontend/src/types/global.d.ts
+++ b/govtool/frontend/src/types/global.d.ts
@@ -14,6 +14,7 @@ declare global {
type ActionTypeFromAPI = {
id: string;
+ txHash: string;
type: string;
details: string;
expiryDate: string;
@@ -36,7 +37,7 @@ declare global {
type ToVoteDataType = {
title: string;
- actions: ActionTypeToDsiplay[];
+ actions: ProposalData[];
}[];
type NestedKeys = T extends Record
diff --git a/govtool/frontend/src/utils/mapDtoToDrep.ts b/govtool/frontend/src/utils/mapDtoToDrep.ts
index ab5655025..b0f1e4b1f 100644
--- a/govtool/frontend/src/utils/mapDtoToDrep.ts
+++ b/govtool/frontend/src/utils/mapDtoToDrep.ts
@@ -32,6 +32,8 @@ export const mapDtoToDrep = async (dto: DrepDataDTO): Promise => {
let base64Image = null;
const isIPFSImage = dto.imageUrl?.startsWith("ipfs://") || false;
if (dto.imageUrl) {
+ // eslint-disable-next-line no-console
+ console.debug("Fetching image", dto.imageUrl);
fetch(
isIPFSImage
? `${import.meta.env.VITE_IPFS_GATEWAY}/${dto.imageUrl?.slice(7)}`
diff --git a/govtool/frontend/src/utils/openInNewTab.ts b/govtool/frontend/src/utils/openInNewTab.ts
index 6f888f4b1..9ad418d86 100644
--- a/govtool/frontend/src/utils/openInNewTab.ts
+++ b/govtool/frontend/src/utils/openInNewTab.ts
@@ -7,6 +7,10 @@ export const openInNewTab = (url: string) => {
? `${import.meta.env.VITE_IPFS_GATEWAY}/${url.slice(7)}`
: `https://${url}`;
+ // eslint-disable-next-line no-console
+ console.debug("Opening in new tab", fullUrl);
+
+ // Open the URL in a new tab
const newWindow = window.open(fullUrl, "_blank", "noopener,noreferrer");
if (newWindow) newWindow.opener = null;
};
diff --git a/govtool/frontend/yarn.lock b/govtool/frontend/yarn.lock
index 3f245350d..1a9020e6a 100644
--- a/govtool/frontend/yarn.lock
+++ b/govtool/frontend/yarn.lock
@@ -1367,15 +1367,20 @@
resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz"
integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==
-"@emurgo/cardano-serialization-lib-asmjs@^12.0.0-beta.2", "@emurgo/cardano-serialization-lib-asmjs@^12.1.1":
+"@emurgo/cardano-serialization-lib-asmjs@^12.0.0-beta.2":
version "12.1.1"
resolved "https://registry.npmjs.org/@emurgo/cardano-serialization-lib-asmjs/-/cardano-serialization-lib-asmjs-12.1.1.tgz"
integrity sha512-K3f28QUfLDJ7seO6MtKfMYtRm5ccf36TQ5yxyTmZqX1TA85MkriEdxqpgV9KLiLEA95emwnlvU2/WmlHMRPg1A==
-"@esbuild/linux-x64@0.25.0":
+"@emurgo/cardano-serialization-lib-asmjs@^14.1.1":
+ version "14.1.1"
+ resolved "https://registry.npmjs.org/@emurgo/cardano-serialization-lib-asmjs/-/cardano-serialization-lib-asmjs-14.1.1.tgz"
+ integrity sha512-Q2HVpPRt417Quxv3qagGWbkJQU8SiQCl1K/344ZtQMwsLoqTfRlCNzmSWMBN7jyBxbtKoh+vdbSiLqwG1NAjYg==
+
+"@esbuild/darwin-arm64@0.25.0":
version "0.25.0"
- resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz"
- integrity sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==
+ resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz"
+ integrity sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
version "4.4.1"
@@ -1487,16 +1492,20 @@
resolved "https://registry.npmjs.org/@inquirer/type/-/type-3.0.4.tgz"
integrity sha512-2MNFrDY8jkFYc9Il9DgLsHhMzuHnOYM1+CUYVWbzu9oT0hC7V7EcYvdCKeoll/Fcci04A+ERZ9wcc7cQ8lTkIA==
-"@intersect.mbo/govtool-outcomes-pillar-ui@1.2.5":
- version "1.2.5"
- resolved "https://registry.npmjs.org/@intersect.mbo/govtool-outcomes-pillar-ui/-/govtool-outcomes-pillar-ui-1.2.5.tgz"
- integrity sha512-z0QDUZKj262vvNCtjr2L8ILgeub8da3ryhMhgLJpuxuO1zfEBUhTuwUPkRuLBE1Gf52TW/AfgmR2qXANsjY4/Q==
+"@intersect.mbo/govtool-outcomes-pillar-ui@1.3.0":
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/@intersect.mbo/govtool-outcomes-pillar-ui/-/govtool-outcomes-pillar-ui-1.3.0.tgz"
+ integrity sha512-6+H+QG8kyM2UUEycNsjrF1K5+UGUw6+wy7gRxlyOtFjIrZ9CUdTfwwyD1hrh+g55awZ4t+EmQgYB00An0iXOag==
dependencies:
"@fontsource/poppins" "^5.0.14"
"@intersect.mbo/intersectmbo.org-icons-set" "^1.0.8"
- axios "^1.7.9"
+ axios "^1.8.4"
bech32 "^2.0.0"
buffer "^6.0.3"
+ react-diff-view "^3.2.1"
+ rehype-katex "^7.0.1"
+ remark-math "^6.0.0"
+ unidiff "^1.0.4"
"@intersect.mbo/intersectmbo.org-icons-set@^1.0.8", "@intersect.mbo/intersectmbo.org-icons-set@^1.1.0":
version "1.1.0"
@@ -2164,15 +2173,10 @@
resolved "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz"
integrity sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==
-"@parcel/watcher-linux-x64-glibc@2.5.0":
+"@parcel/watcher-darwin-arm64@2.5.0":
version "2.5.0"
- resolved "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz"
- integrity sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==
-
-"@parcel/watcher-linux-x64-musl@2.5.0":
- version "2.5.0"
- resolved "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz"
- integrity sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==
+ resolved "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz"
+ integrity sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==
"@parcel/watcher@^2.4.1":
version "2.5.0"
@@ -2285,15 +2289,10 @@
estree-walker "^2.0.2"
picomatch "^4.0.2"
-"@rollup/rollup-linux-x64-gnu@4.34.9":
- version "4.34.9"
- resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz"
- integrity sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==
-
-"@rollup/rollup-linux-x64-musl@4.34.9":
+"@rollup/rollup-darwin-arm64@4.34.9":
version "4.34.9"
- resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz"
- integrity sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==
+ resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz"
+ integrity sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==
"@rtsao/scc@^1.1.0":
version "1.1.0"
@@ -2896,15 +2895,10 @@
"@svgr/plugin-svgo" "^5.5.0"
loader-utils "^2.0.0"
-"@swc/core-linux-x64-gnu@1.9.3":
- version "1.9.3"
- resolved "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.9.3.tgz"
- integrity sha512-ivXXBRDXDc9k4cdv10R21ccBmGebVOwKXT/UdH1PhxUn9m/h8erAWjz5pcELwjiMf27WokqPgaWVfaclDbgE+w==
-
-"@swc/core-linux-x64-musl@1.9.3":
+"@swc/core-darwin-arm64@1.9.3":
version "1.9.3"
- resolved "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.9.3.tgz"
- integrity sha512-ILsGMgfnOz1HwdDz+ZgEuomIwkP1PHT6maigZxaCIuC6OPEhKE8uYna22uU63XvYcLQvZYDzpR3ms47WQPuNEg==
+ resolved "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.9.3.tgz"
+ integrity sha512-hGfl/KTic/QY4tB9DkTbNuxy5cV4IeejpPD4zo+Lzt4iLlDWIeANL4Fkg67FiVceNJboqg48CUX+APhDHO5G1w==
"@swc/core@*", "@swc/core@^1.5.22", "@swc/core@^1.7.26":
version "1.9.3"
@@ -4486,10 +4480,10 @@ axe-core@^4.10.0:
resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz"
integrity sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==
-axios@^1.4.0, axios@^1.6.1, axios@^1.7.2, axios@^1.7.9:
- version "1.7.9"
- resolved "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz"
- integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==
+axios@^1.4.0, axios@^1.6.1, axios@^1.7.2, axios@^1.8.4:
+ version "1.8.4"
+ resolved "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz"
+ integrity sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.0"
@@ -7207,6 +7201,16 @@ fs@^0.0.1-security:
resolved "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz"
integrity sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==
+fsevents@^2.3.2, fsevents@~2.3.2, fsevents@~2.3.3:
+ version "2.3.3"
+ resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
+fsevents@2.3.2:
+ version "2.3.2"
+ resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz"
+ integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
diff --git a/govtool/metadata-validation/package-lock.json b/govtool/metadata-validation/package-lock.json
index c13718059..38b4d2ba5 100644
--- a/govtool/metadata-validation/package-lock.json
+++ b/govtool/metadata-validation/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@govtool/metadata-validation",
- "version": "2.0.17",
+ "version": "2.0.18",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@govtool/metadata-validation",
- "version": "2.0.17",
+ "version": "2.0.18",
"license": "UNLICENSED",
"dependencies": {
"@nestjs/axios": "^3.0.2",
diff --git a/govtool/metadata-validation/package.json b/govtool/metadata-validation/package.json
index 693b2c7ca..ac5a8ec96 100644
--- a/govtool/metadata-validation/package.json
+++ b/govtool/metadata-validation/package.json
@@ -1,6 +1,6 @@
{
"name": "@govtool/metadata-validation",
- "version": "2.0.17",
+ "version": "2.0.18",
"description": "",
"author": "",
"private": true,
diff --git a/govtool/metadata-validation/src/main.ts b/govtool/metadata-validation/src/main.ts
index d6656ba7e..5d1d15d80 100644
--- a/govtool/metadata-validation/src/main.ts
+++ b/govtool/metadata-validation/src/main.ts
@@ -13,7 +13,7 @@ async function bootstrap() {
const config = new DocumentBuilder()
.setTitle('Metadata Validation Tool')
.setDescription('The Metadata Validation Tool API description')
- .setVersion("2.0.17")
+ .setVersion("2.0.18")
.build();
const document = SwaggerModule.createDocument(app, config);
diff --git a/tests/govtool-frontend/playwright/lib/_mock/scriptDRep.json b/tests/govtool-frontend/playwright/lib/_mock/scriptDRep.json
new file mode 100644
index 000000000..b07dacdd4
--- /dev/null
+++ b/tests/govtool-frontend/playwright/lib/_mock/scriptDRep.json
@@ -0,0 +1,28 @@
+{
+ "page": 0,
+ "pageSize": 10,
+ "total": 1,
+ "elements": [
+ {
+ "isScriptBased": true,
+ "drepId": "429b12461640cefd3a4a192f7c531d8f6c6d33610b727f481eb22d39",
+ "view": "drep1g2d3y3skgr806wj2ryhhc5ca3akx6vmppde87jq7kgknjmv589e",
+ "url": null,
+ "metadataHash": null,
+ "deposit": 500000000,
+ "votingPower": 83414740266257,
+ "status": "Active",
+ "type": "SoleVoter",
+ "latestTxHash": "8de2a5f9074679de947549ea36c3980496503ffc40f0cbce5ce1ee3df66306e9",
+ "latestRegistrationDate": "2024-11-20T01:59:20Z",
+ "metadataError": null,
+ "paymentAddress": null,
+ "givenName": null,
+ "objectives": null,
+ "motivations": null,
+ "qualifications": null,
+ "imageUrl": null,
+ "imageHash": null
+ }
+ ]
+}
diff --git a/tests/govtool-frontend/playwright/lib/helpers/dRep.ts b/tests/govtool-frontend/playwright/lib/helpers/dRep.ts
index bbe9f56ff..40d9e694d 100644
--- a/tests/govtool-frontend/playwright/lib/helpers/dRep.ts
+++ b/tests/govtool-frontend/playwright/lib/helpers/dRep.ts
@@ -88,10 +88,14 @@ export function tohex(drepId: string) {
).toString("hex");
}
-export function convertDRepToCIP129(drepId: string, script = false): string {
+export function convertDRep(
+ drepId: string,
+ script = false
+): { cip129: string; cip105: string } {
const hexPattern = /^[0-9a-fA-F]+$/;
let cip129DRep: string;
let cip129DrepHex: string;
+ let cip105DRep: string;
const prefix = script ? "23" : "22";
const addPrefix = (hex: string) => {
if (hex.length === 56) {
@@ -102,8 +106,8 @@ export function convertDRepToCIP129(drepId: string, script = false): string {
throw new Error("Invalid DRep hex length");
}
};
- const drepIdFromHex = (hex: string) => {
- return fromHex("drep", hex);
+ const drepIdFromHex = (prefix: string, hex: string) => {
+ return fromHex(prefix, hex);
};
if (hexPattern.test(drepId)) {
cip129DrepHex = addPrefix(drepId);
@@ -115,6 +119,10 @@ export function convertDRepToCIP129(drepId: string, script = false): string {
throw new Error("Invalid DRep Bech32 format");
}
}
- cip129DRep = drepIdFromHex(cip129DrepHex);
- return cip129DRep;
+ cip105DRep = drepIdFromHex(
+ cip129DrepHex.slice(0, 2) == "22" ? "drep" : "drep_script",
+ cip129DrepHex.slice(-56)
+ );
+ cip129DRep = drepIdFromHex("drep", cip129DrepHex);
+ return { cip129: cip129DRep, cip105: cip105DRep };
}
diff --git a/tests/govtool-frontend/playwright/lib/pages/dRepDirectoryPage.ts b/tests/govtool-frontend/playwright/lib/pages/dRepDirectoryPage.ts
index 83dae3ba1..384863fb7 100644
--- a/tests/govtool-frontend/playwright/lib/pages/dRepDirectoryPage.ts
+++ b/tests/govtool-frontend/playwright/lib/pages/dRepDirectoryPage.ts
@@ -1,4 +1,4 @@
-import { convertDRepToCIP129 } from "@helpers/dRep";
+import { convertDRep } from "@helpers/dRep";
import { functionWaitedAssert, waitedLoop } from "@helpers/waitedLoop";
import { Locator, Page, expect } from "@playwright/test";
import { IDRep } from "@types";
@@ -156,8 +156,12 @@ export default class DRepDirectoryPage {
const cip105DRepListFE = await this.getAllListedCIP105DRepIds();
const cip129DRepListFE = await this.getAllListedCIP129DRepIds();
- const cip129DRepListApi = dRepList.map((dRep) =>
- convertDRepToCIP129(dRep.drepId, dRep.isScriptBased)
+ const cip129DRepListApi = dRepList.map(
+ (dRep) => convertDRep(dRep.drepId, dRep.isScriptBased).cip129
+ );
+
+ const cip105DRepListApi = dRepList.map(
+ (dRep) => convertDRep(dRep.drepId, dRep.isScriptBased).cip105
);
for (let i = 0; i <= cip105DRepListFE.length - 1; i++) {
@@ -165,8 +169,8 @@ export default class DRepDirectoryPage {
message: `Cip129 dRep Id from Api:${cip129DRepListApi[i]} is not equal to ${await cip129DRepListFE[i].textContent()} on sort ${option}`,
}).toHaveText(cip129DRepListApi[i]);
await expect(cip105DRepListFE[i], {
- message: `Cip105 dRep Id from Api:${dRepList[i].view} is not equal to ${await cip105DRepListFE[i].textContent()} on sort ${option}`,
- }).toHaveText(`(CIP-105) ${dRepList[i].view}`);
+ message: `Cip105 dRep Id from Api:${cip105DRepListApi} is not equal to ${await cip105DRepListFE[i].textContent()} on sort ${option}`,
+ }).toHaveText(`(CIP-105) ${cip105DRepListApi[i]}`);
}
},
{ name: `frontend sort validation of ${option}` }
diff --git a/tests/govtool-frontend/playwright/lib/types.ts b/tests/govtool-frontend/playwright/lib/types.ts
index 4639885e7..84cc17c38 100644
--- a/tests/govtool-frontend/playwright/lib/types.ts
+++ b/tests/govtool-frontend/playwright/lib/types.ts
@@ -118,6 +118,13 @@ export enum FullGovernanceDRepVoteActionsType {
export type DRepStatus = "Active" | "Inactive" | "Retired";
+export interface PaginatedDRepResponse {
+ page: number;
+ pageSize: number;
+ total: number;
+ elements: IDRep[];
+}
+
export type IDRep = {
isScriptBased: boolean;
drepId: string;
diff --git a/tests/govtool-frontend/playwright/tests/2-delegation/delegation.spec.ts b/tests/govtool-frontend/playwright/tests/2-delegation/delegation.spec.ts
index 9f83b7178..1bab08af8 100644
--- a/tests/govtool-frontend/playwright/tests/2-delegation/delegation.spec.ts
+++ b/tests/govtool-frontend/playwright/tests/2-delegation/delegation.spec.ts
@@ -1,13 +1,13 @@
import environments from "@constants/environments";
import { setAllureEpic } from "@helpers/allure";
import { skipIfNotHardFork } from "@helpers/cardano";
-import { fetchFirstActiveDRepDetails } from "@helpers/dRep";
+import { convertDRep, fetchFirstActiveDRepDetails } from "@helpers/dRep";
import { functionWaitedAssert } from "@helpers/waitedLoop";
import DRepDetailsPage from "@pages/dRepDetailsPage";
import DRepDirectoryPage from "@pages/dRepDirectoryPage";
import { expect, Locator } from "@playwright/test";
import { test } from "@fixtures/walletExtension";
-import { DRepStatus, IDRep } from "@types";
+import { DRepStatus, IDRep, PaginatedDRepResponse } from "@types";
test.beforeEach(async () => {
await setAllureEpic("2. Delegation");
@@ -27,6 +27,8 @@ const statusRank: Record = {
Retired: 3,
};
+const scripDRepId: PaginatedDRepResponse = require("../../lib/_mock/scriptDRep.json");
+
test("2K_2. Should sort DReps", async ({ page }) => {
test.slow();
@@ -223,3 +225,41 @@ test.describe("DRep dependent tests", () => {
expect(copiedTextDRepDirectory).toEqual(dRepId);
});
});
+
+Object.values(["script drep", "drep"]).forEach((type, index) => {
+ test(`2Y_${index + 1}. Should correctly convert CIP-129/CIP-105 ${type}`, async ({
+ page,
+ }) => {
+ const dRepId = scripDRepId.elements[0]["drepId"];
+ const dRepResponse = {
+ ...scripDRepId,
+ elements: [{ ...scripDRepId.elements[0], isScriptBased: false }],
+ };
+ await page.route("**/drep/list?page=0&pageSize=10&**", async (route) => {
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ body: JSON.stringify(type === "drep" ? dRepResponse : scripDRepId),
+ });
+ });
+
+ const responsePromise = page.waitForResponse(
+ "**/drep/list?page=0&pageSize=10&**"
+ );
+
+ const { cip129, cip105 } = convertDRep(
+ dRepId,
+ type === "drep" ? false : true
+ );
+
+ await page.goto(`/drep_directory/${dRepId}`);
+ await responsePromise;
+
+ await expect(
+ page.getByTestId("cip-129-drep-id-info-item-description")
+ ).toHaveText(cip129, { timeout: 60_000 });
+ await expect(
+ page.getByTestId("cip-105-drep-id-info-item-description")
+ ).toHaveText(cip105);
+ });
+});
diff --git a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts
index fb0bf4c96..0ad6f67b4 100644
--- a/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts
+++ b/tests/govtool-frontend/playwright/tests/5-proposal-functionality/proposalFunctionality.dRep.spec.ts
@@ -264,7 +264,7 @@ test.describe("Perform voting", () => {
window.scrollTo(0, 500)
);
await expect(
- govActionDetailsPage.currentPage.getByTestId("my-vote").getByText("No"),
+ govActionDetailsPage.currentPage.getByTestId("my-vote").getByText("Yes"),
{
message:
!isYesVoteVisible &&